feat(gui): multi-nozzle UI for H2C/A2L

Multi-nozzle sync widget, AMS rack-nozzle mapping popup, calibration rework, send-dialog nozzle mapping and extruder-count UI. Includes the fix to persist the AMS sync badge on filament cards (H2C/A2L and direct-sync printers).
This commit is contained in:
SoftFever
2026-07-09 01:16:26 +08:00
parent a098b483f6
commit 28b7127150
52 changed files with 6136 additions and 250 deletions
+2
View File
@@ -494,6 +494,8 @@ set(SLIC3R_GUI_SOURCES
GUI/Widgets/AMSControl.hpp
GUI/Widgets/AMSItem.cpp
GUI/Widgets/AMSItem.hpp
GUI/Widgets/MultiNozzleSync.cpp
GUI/Widgets/MultiNozzleSync.hpp
GUI/Widgets/AxisCtrlButton.cpp
GUI/Widgets/AxisCtrlButton.hpp
GUI/SafetyOptionsDialog.hpp
+20 -2
View File
@@ -1024,7 +1024,8 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type,
const Transform3d& projection_matrix,
const GUI::Size& cnv_size,
std::function<bool(const GLVolume &)> filter_func,
bool partly_inside_enable) const
bool partly_inside_enable,
std::vector<double> * printable_heights) const
{
GLVolumeWithIdAndZList to_render = volumes_to_render(volumes, type, view_matrix, filter_func);
if (to_render.empty())
@@ -1108,7 +1109,24 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type,
//use -1 ad a invalid type
shader->set_uniform("print_volume.type", -1);
}
// Per-extruder printable-height shading. The flag is set to
// 2.0 only for multi-extruder printers (two per-extruder heights); otherwise it is forced to 0.0
// on every render so no stale flag survives a multi->single-extruder plate switch, keeping the
// shared gouraud shader pixel-identical for single-extruder printers. When active the height
// branch reads print_volume.xy_data (the bed rect), so set it explicitly here.
std::array<float, 3> extruder_printable_heights = {0.0f, 0.0f, 0.0f};
if (printable_heights != nullptr && printable_heights->size() > 1) {
extruder_printable_heights[0] = 2.0f;
extruder_printable_heights[1] = static_cast<float>((*printable_heights)[0]);
extruder_printable_heights[2] = static_cast<float>((*printable_heights)[1]);
shader->set_uniform("extruder_printable_heights", extruder_printable_heights);
shader->set_uniform("print_volume.xy_data", m_print_volume.data);
}
else {
shader->set_uniform("extruder_printable_heights", extruder_printable_heights);
}
shader->set_uniform("volume_world_matrix", volume.first->world_matrix());
shader->set_uniform("slope.actived", m_slope.isGlobalActive && !volume.first->is_modifier && !volume.first->is_wipe_tower);
shader->set_uniform("slope.volume_world_normal_matrix", static_cast<Matrix3f>(volume.first->world_matrix().matrix().block(0, 0, 3, 3).inverse().transpose().cast<float>()));
+4 -1
View File
@@ -497,7 +497,10 @@ public:
const Transform3d& projection_matrix,
const GUI::Size& cnv_size,
std::function<bool(const GLVolume &)> filter_func = std::function<bool(const GLVolume &)>(),
bool partly_inside_enable =true
bool partly_inside_enable =true,
// Per-extruder printable heights (extruder_printable_height); null / size<=1
// leaves the shader's extruder_printable_heights flag at 0.0 (single-extruder = inert).
std::vector<double> * printable_heights = nullptr
) const;
// Clear the geometry
+108 -40
View File
@@ -16,6 +16,9 @@
#include "DeviceCore/DevExtruderSystem.h"
#include "DeviceCore/DevFilaBlackList.h"
#include "DeviceCore/DevFilaSystem.h"
#include "DeviceCore/DevFilaSwitch.h"
#include "DeviceCore/DevNozzleSystem.h" // DevNozzle / GetNozzleByPosId / GetNozzleRack
#include "DeviceCore/DevNozzleRack.h" // DevNozzleRack::IsSupported (rack gating)
#define FILAMENT_MAX_TEMP 300
#define FILAMENT_MIN_TEMP 120
@@ -531,56 +534,121 @@ void AMSMaterialsSetting::on_select_reset(wxCommandEvent& event) {
Close();
}
// Nozzle-context builder for the AMS-edit blacklist check. Factors the blacklist evaluation out of
// on_select_ok and threads rack-gated per-nozzle context (extruder id + nozzle flow + nozzle diameter)
// into CheckFilamentInfo, returning the accumulate-all CheckResult. As a side effect it resolves
// ams_filament_id/ams_setting_id from the matched preset. fila_name is kept as the preset name (not
// the short alias) so the name / name_suffix rules match on the non-rack fleet (no unintended
// activation of e.g. the PLA Glow rule).
static DevFilaBlacklist::CheckResult
sCheckFilamentInfo(PresetBundle* preset_bundle,
MachineObject* obj,
int ams_id,
int slot_id,
const std::string& filament_id,
std::string& ams_filament_id,
std::string& ams_setting_id)
{
DevFilaBlacklist::CheckResult result;
if (!preset_bundle || !obj)
return result;
// Orca: there is no lookup struct that also carries setting_id, so resolve the (root) preset by
// scanning the filaments collection for a matching filament_id.
Preset* fila_preset = nullptr;
for (auto it = preset_bundle->filaments.begin(); it != preset_bundle->filaments.end(); ++it) {
if (it->filament_id == filament_id) {
fila_preset = &(*it);
break;
}
}
if (!fila_preset)
return result;
if (wxGetApp().app_config->get("skip_ams_blacklist_check") != "true") {
std::string filamnt_type;
fila_preset->get_filament_type(filamnt_type);
DevFilaBlacklist::CheckFilamentInfo check_info;
check_info.dev_id = obj->get_dev_id();
check_info.model_id = obj->printer_type;
check_info.fila_id = fila_preset->filament_id;
check_info.fila_type = filamnt_type;
check_info.fila_name = fila_preset->name;
check_info.has_filament_switch = obj->GetFilaSwitch()->IsInstalled();
check_info.ams_id = ams_id;
check_info.slot_id = slot_id;
if (auto vendor = dynamic_cast<ConfigOptionStrings*>(fila_preset->config.option("filament_vendor"));
vendor && !vendor->values.empty()) {
check_info.fila_vendor = vendor->values[0];
}
check_info.extruder_id = obj->GetFilaSystem()->GetExtruderIdByAmsId(std::to_string(ams_id));
// Rack-gated per-nozzle context. For a multi-nozzle main extruder on a rack printer (H2C) the
// specific nozzle is resolved later by the print-dispatch rack mapping, so leave
// nozzle_flow/nozzle_diameter unset here to keep the high-flow nozzle rules dormant. Every
// non-rack printer takes the else branch, threading the reported nozzle flow + diameter and
// thereby activating the high-flow blacklist warnings in the AMS-edit dialog for H2D/H2S/X2D/P2S
// (and the E3D-kit X1C/P1x).
DevNozzleSystem* nozzle_system = obj->GetNozzleSystem();
const bool rack_supported = nozzle_system && nozzle_system->GetNozzleRack() &&
nozzle_system->GetNozzleRack()->IsSupported();
if (check_info.extruder_id == MAIN_EXTRUDER_ID && rack_supported) {
; // multi-nozzle main extruder on a rack printer — nozzle resolved later by print dispatch
} else {
check_info.nozzle_flow = obj->GetFilaSystem()->GetNozzleFlowStringByAmsId(std::to_string(ams_id));
if (nozzle_system) {
DevNozzle nozzle = nozzle_system->GetNozzleByPosId(check_info.extruder_id.value_or(-1));
if (!nozzle.IsEmpty())
check_info.nozzle_diameter = nozzle.GetNozzleDiameter();
}
}
result = DevFilaBlacklist::check_filaments_in_blacklist(check_info);
}
ams_filament_id = fila_preset->filament_id;
ams_setting_id = fila_preset->setting_id;
return result;
}
void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event)
{
if (!obj)
return;
//get filament id
ams_filament_id = "";
ams_setting_id = "";
// the combobox item
auto filament_item = map_filament_items[m_comboBox_filament->GetValue().ToStdString()];
// check filament info (rack-gated per-nozzle blacklist evaluation)
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
if (preset_bundle) {
for (auto it = preset_bundle->filaments.begin(); it != preset_bundle->filaments.end(); it++) {
const auto& fila_check_res = sCheckFilamentInfo(preset_bundle, obj, ams_id, slot_id,
filament_item.filament_id, ams_filament_id, ams_setting_id);
auto filament_item = map_filament_items[m_comboBox_filament->GetValue().ToStdString()];
std::string filament_id = filament_item.filament_id;
if (it->filament_id.compare(filament_id) == 0) {
if (const auto& prohibit_items = fila_check_res.get_items_by_action("prohibition"); !prohibit_items.empty()) {
wxString info_msg;
for (const auto& item : prohibit_items) { info_msg += item.info_msg + "\n"; }
MessageDialog msg_wingow(nullptr, info_msg, _L("Error"), wxICON_WARNING | wxOK);
msg_wingow.ShowModal();
return;
}
//check is it in the filament blacklist
if (wxGetApp().app_config->get("skip_ams_blacklist_check") != "true") {
bool in_blacklist = false;
std::string action;
wxString info;
std::string filamnt_type;
std::string filamnt_name;
it->get_filament_type(filamnt_type);
auto vendor = dynamic_cast<ConfigOptionStrings *>(it->config.option("filament_vendor"));
if (vendor && (vendor->values.size() > 0)) {
std::string vendor_name = vendor->values[0];
DevFilaBlacklist::check_filaments_in_blacklist(obj->printer_type, vendor_name, filamnt_type, it->filament_id, ams_id, slot_id, it->name, in_blacklist, action, info);
}
if (in_blacklist) {
if (action == "prohibition") {
MessageDialog msg_wingow(nullptr, info, _L("Error"), wxICON_WARNING | wxOK);
msg_wingow.ShowModal();
//m_comboBox_filament->SetSelection(m_filament_selection);
return;
}
else if (action == "warning") {
MessageDialog msg_wingow(nullptr, info, _L("Warning"), wxICON_INFORMATION | wxOK);
msg_wingow.ShowModal();
}
}
}
ams_filament_id = it->filament_id;
ams_setting_id = it->setting_id;
break;
}
if (const auto& warning_items = fila_check_res.get_items_by_action("warning"); !warning_items.empty()) {
std::vector<FilamentWarningInfo> infos;
for (const auto& item : warning_items) {
FilamentWarningInfo winfo;
winfo.info_msg = item.info_msg;
winfo.wiki_url = item.wiki_url;
infos.emplace_back(winfo);
}
FilamentWarningDialog msg_window(nullptr, _L("Warning"), infos);
msg_window.ShowModal();
}
wxString nozzle_temp_min = m_input_nozzle_min->GetTextCtrl()->GetValue();
+203 -11
View File
@@ -24,6 +24,9 @@
#include "BindDialog.hpp"
#include "DeviceCore/DevFilaSystem.h"
#include "DeviceCore/DevNozzleRack.h"
#include "DeviceCore/DevMappingNozzle.h"
#include "DeviceTab/wgtDeviceNozzleSelect.h"
namespace Slic3r { namespace GUI {
#define MATERIAL_ITEM_SIZE wxSize(FromDIP(65), FromDIP(50))
@@ -64,6 +67,7 @@ static void _add_containers(const AmsMapingPopup * win,
//m_ams_wheel_mitem = ScalableBitmap(this, "ams_wheel", FromDIP(25));
m_ams_wheel_mitem = ScalableBitmap(this, "ams_wheel_narrow", 25);
m_ams_not_match = ScalableBitmap(this, "filament_not_mactch", 25);
m_rack_nozzle_bitmap = ScalableBitmap(this, "dev_rack_nozzle_print_job", 22);
m_material_coloul = mcolour;
m_material_name = mname;
@@ -73,9 +77,7 @@ static void _add_containers(const AmsMapingPopup * win,
SetDoubleBuffered(true);
#endif //__WINDOWS__
SetSize(MATERIAL_ITEM_SIZE);
SetMinSize(MATERIAL_ITEM_SIZE);
SetMaxSize(MATERIAL_ITEM_SIZE);
messure_size();
SetBackgroundColour(*wxWHITE);
Bind(wxEVT_PAINT, &MaterialItem::paintEvent, this);
@@ -89,6 +91,7 @@ void MaterialItem::msw_rescale() {
m_arraw_bitmap_white.msw_rescale();
m_transparent_mitem.msw_rescale();
m_ams_wheel_mitem.msw_rescale();
m_rack_nozzle_bitmap.msw_rescale();
}
void MaterialItem::allow_paint_dropdown(bool flag) {
@@ -114,11 +117,22 @@ void MaterialItem::set_ams_info(wxColour col, wxString txt, int ctype, std::vect
BOOST_LOG_TRIVIAL(info) << "set_ams_info " << m_ams_name;
}
void MaterialItem::set_nozzle_info(const wxString& mapped_nozzle_str)
{
if (m_mapped_nozzle_str != mapped_nozzle_str) {
m_mapped_nozzle_str = mapped_nozzle_str;
messure_size(); // a mapped nozzle adds/removes a row, changing the card height
Refresh();
}
}
void MaterialItem::reset_ams_info() {
m_ams_name = "-";
m_ams_coloul = wxColour(0xCE, 0xCE, 0xCE);
m_ams_cols.clear();
m_ams_ctype = 0;
m_mapped_nozzle_str.Clear();
messure_size();
Refresh();
}
@@ -215,6 +229,11 @@ void MaterialItem::render(wxDC &dc)
acolor = wxColour(0x90, 0x90, 0x90);
}
// Header + AMS name keep their fixed-card positions; a mapped nozzle only adds a row below, so
// position this text against the base card height, not the grown one (else it slides down onto
// the nozzle row). Identical to GetSize().y when no nozzle row is present.
const float base_y = m_mapped_nozzle_str.IsEmpty() ? (float) GetSize().y : (float) MATERIAL_ITEM_SIZE.y;
// materials name
dc.SetFont(::Label::Body_13);
@@ -227,7 +246,7 @@ void MaterialItem::render(wxDC &dc)
}
auto material_txt_size = dc.GetTextExtent(m_material_name);
dc.DrawText(m_material_name, wxPoint((GetSize().x - material_txt_size.x) / 2, ((float)GetSize().y * 2 / 5 - material_txt_size.y) / 2));
dc.DrawText(m_material_name, wxPoint((GetSize().x - material_txt_size.x) / 2, (base_y * 2 / 5 - material_txt_size.y) / 2));
@@ -238,11 +257,11 @@ void MaterialItem::render(wxDC &dc)
if (mapping_txt.size() >= 4) {
mapping_txt.insert(mapping_txt.size() / 2, "\n");
mapping_txt_size = dc.GetTextExtent(mapping_txt);
m_text_pos_y = ((float) GetSize().y * 3 / 5 - mapping_txt_size.y) / 2 + (float) GetSize().y * 2 / 5 - mapping_txt_size.y / 2;
m_text_pos_y = (base_y * 3 / 5 - mapping_txt_size.y) / 2 + base_y * 2 / 5 - mapping_txt_size.y / 2;
m_text_pos_x = mapping_txt_size.x / 4;
} else {
mapping_txt_size = dc.GetTextExtent(mapping_txt);
m_text_pos_y = ((float) GetSize().y * 3 / 5 - mapping_txt_size.y) / 2 + (float) GetSize().y * 2 / 5;
m_text_pos_y = (base_y * 3 / 5 - mapping_txt_size.y) / 2 + base_y * 2 / 5;
m_text_pos_x = 0;
}
@@ -258,12 +277,73 @@ void MaterialItem::match(bool mat)
Refresh();
}
// A filament can map to several rack nozzles; the joined "R1, R2, ..." string is wrapped to a few
// slots per line so the label stays readable inside the narrow card.
static constexpr size_t MAPPED_NOZZLE_STR_MAX_CHARS_PER_LINE = 7;
static wxString s_wrap_mapped_nozzle_str(const wxString& text)
{
if (text.length() <= MAPPED_NOZZLE_STR_MAX_CHARS_PER_LINE)
return text;
wxArrayString parts = wxSplit(text, ',');
wxString wrapped;
std::vector<wxString> buffers;
for (auto& part : parts) {
if (!part.empty())
buffers.push_back(part.Trim(false).Trim(true));
if (buffers.size() > 2) {
if (!wrapped.empty())
wrapped += "\n";
wrapped += buffers[0] + " " + buffers[1] + " " + buffers[2];
buffers.clear();
}
}
if (!wrapped.empty())
wrapped += "\n";
if (buffers.size() > 0)
wrapped += buffers[0];
if (buffers.size() > 1)
wrapped += " " + buffers[1];
return wrapped;
}
static int s_get_mapped_nozzle_str_line_count(const wxString& text)
{
if (text.empty())
return 0;
return std::max(1, static_cast<int>(wxSplit(s_wrap_mapped_nozzle_str(text), '\n', '\0').size()));
}
void MaterialItem::messure_size()
{
// Without a mapped nozzle the card is a fixed swatch + AMS wheel; a mapped nozzle adds a row
// below (glyph + slot label), growing the height by one text line per wrapped line.
if (m_mapped_nozzle_str.empty()) {
SetSize(MATERIAL_ITEM_SIZE);
SetMinSize(MATERIAL_ITEM_SIZE);
SetMaxSize(MATERIAL_ITEM_SIZE);
} else {
const int line_count = s_get_mapped_nozzle_str_line_count(m_mapped_nozzle_str);
const wxSize item_size(MATERIAL_ITEM_SIZE.x, FromDIP(84 + std::max(0, line_count - 1) * 10));
SetSize(item_size);
SetMinSize(item_size);
SetMaxSize(item_size);
}
}
void MaterialItem::doRender(wxDC& dc)
{
wxSize size = GetSize();
auto mcolor = m_material_coloul;
auto acolor = m_ams_coloul;
change_the_opacity(acolor);
// Header + wheel keep their fixed-card geometry; when a nozzle row is present the extra height
// below is used for it, so lay out the top of the card against the base height, not the grown one.
const int base_y = m_mapped_nozzle_str.IsEmpty() ? size.y : MATERIAL_ITEM_SIZE.y;
if (mcolor.Alpha() == 0) {
dc.DrawBitmap(m_transparent_mitem.bmp(), FromDIP(1), FromDIP(1));
@@ -288,7 +368,7 @@ void MaterialItem::doRender(wxDC& dc)
//bottom rectangle in wheel bitmap, size is MATERIAL_REC_WHEEL_SIZE(22)
auto left = (size.x / 2 - MATERIAL_REC_WHEEL_SIZE.x) / 2 + FromDIP(3);
auto up = (size.y * 0.4 + (size.y * 0.6 - MATERIAL_REC_WHEEL_SIZE.y) / 2);
auto up = (base_y * 0.4 + (base_y * 0.6 - MATERIAL_REC_WHEEL_SIZE.y) / 2);
auto right = left + MATERIAL_REC_WHEEL_SIZE.x - FromDIP(3);
dc.SetPen(*wxTRANSPARENT_PEN);
//bottom
@@ -350,12 +430,12 @@ void MaterialItem::doRender(wxDC& dc)
}
dc.SetBrush(*wxTRANSPARENT_BRUSH);
dc.DrawRoundedRectangle(FromDIP(0), FromDIP(0), MATERIAL_ITEM_SIZE.x - FromDIP(0), MATERIAL_ITEM_SIZE.y - FromDIP(0), 5);
dc.DrawRoundedRectangle(FromDIP(0), FromDIP(0), MATERIAL_ITEM_SIZE.x - FromDIP(0), size.y - FromDIP(0), 5);
if (m_selected) {
dc.SetPen(wxPen(AMS_CONTROL_BRAND_COLOUR, FromDIP(2)));
dc.SetBrush(*wxTRANSPARENT_BRUSH);
dc.DrawRoundedRectangle(FromDIP(1), FromDIP(1), MATERIAL_ITEM_SIZE.x - FromDIP(1), MATERIAL_ITEM_SIZE.y - FromDIP(1), 5);
dc.DrawRoundedRectangle(FromDIP(1), FromDIP(1), MATERIAL_ITEM_SIZE.x - FromDIP(1), size.y - FromDIP(1), 5);
}
//#endif
if (m_text_pos_y > 0 && m_match) {
@@ -368,7 +448,7 @@ void MaterialItem::doRender(wxDC& dc)
}
auto wheel_left = (GetSize().x / 2 - m_ams_wheel_mitem.GetBmpSize().x) / 2 + FromDIP(2);
auto wheel_top = ((float)GetSize().y * 0.6 - m_ams_wheel_mitem.GetBmpSize().y) / 2 + (float)GetSize().y * 0.4;
auto wheel_top = ((float)base_y * 0.6 - m_ams_wheel_mitem.GetBmpSize().y) / 2 + (float)base_y * 0.4;
if (!m_match) {
wheel_left += m_ams_wheel_mitem.GetBmpSize().x;
@@ -380,6 +460,29 @@ void MaterialItem::doRender(wxDC& dc)
dc.DrawBitmap(m_ams_wheel_mitem.bmp(), wheel_left - FromDIP(LEFT_OFFSET), wheel_top);
}
}
// Rack / filament-switcher printers: draw the physical nozzle the print-dispatch mapping assigned
// to this filament — a divider, the rack-nozzle glyph, and the slot label ("R1", "L R", ...) in
// the extra row below the wheel. Nothing is drawn when no nozzle is mapped (all other printers).
if (!m_mapped_nozzle_str.IsEmpty()) {
int row_top = wheel_top + m_ams_wheel_mitem.GetBmpSize().y + FromDIP(4);
dc.SetPen(wxPen(wxColour("#CECECE"), 1, wxPENSTYLE_SHORT_DASH)); // Orca: WXCOLOUR_GREY400 macro not in scope here
dc.DrawLine(FromDIP(1), row_top, FromDIP(size.x), row_top);
int bitmap_y = row_top + (size.y - row_top - m_rack_nozzle_bitmap.GetBmpHeight()) / 2;
int bitmap_l = wheel_left + (m_ams_wheel_mitem.GetBmpWidth() - m_rack_nozzle_bitmap.GetBmpWidth()) / 2 + FromDIP(2);
dc.DrawBitmap(m_rack_nozzle_bitmap.bmp(), bitmap_l, bitmap_y);
// Orca: BBS width-fits this label via a get_suitable_font_size overload Orca lacks; the wrapped
// slot string is short, so a fixed label font renders it cleanly without that helper.
const wxString wrapped = s_wrap_mapped_nozzle_str(m_mapped_nozzle_str);
dc.SetFont(::Label::Head_12);
dc.SetTextForeground(StateColor::darkModeColorFor(wxColour(0x26, 0x2E, 0x30)));
const wxSize text_size = dc.GetMultiLineTextExtent(wrapped);
int text_x = (size.x + bitmap_l + m_rack_nozzle_bitmap.GetBmpWidth() - text_size.x) / 2;
int text_y = row_top + (size.y - row_top - text_size.y) / 2;
dc.DrawText(wrapped, text_x, text_y);
}
}
void MaterialItem::reset_valid_info() {
@@ -725,6 +828,16 @@ AmsMapingPopup::AmsMapingPopup(wxWindow *parent, bool use_in_sync_dialog) :
m_right_first_text_panel->SetMaxSize(wxSize(-1, FromDIP(same_height)));
m_sizer_ams_right->Add(m_right_first_text_panel, 0, wxEXPAND | wxBOTTOM | wxTOP, FromDIP(8));
// Flush-waste warning (rack printers only). Orca: uses a plain Label here.
// Hidden by default => zero layout effect for non-rack printers.
m_flush_warning_panel = new Label(m_right_marea_panel);
m_flush_warning_panel->SetFont(::Label::Body_12);
m_flush_warning_panel->SetForegroundColour(StateColor::darkModeColorFor("#F09A17"));
m_flush_warning_panel->SetBackgroundColour(StateColor::darkModeColorFor("#FFFFE0"));
m_flush_warning_panel->Show(false);
m_sizer_ams_right->Add(m_flush_warning_panel, 0, wxBOTTOM | wxALIGN_LEFT, FromDIP(8));
m_right_split_ams_sizer = create_split_sizer(m_right_marea_panel, _L("Right AMS"));
m_sizer_ams_right->Add(m_right_split_ams_sizer, 0, wxEXPAND, 0);
m_sizer_ams_right->Add(m_sizer_ams_basket_right, 0, wxEXPAND|wxTOP, FromDIP(8));
@@ -740,6 +853,14 @@ AmsMapingPopup::AmsMapingPopup(wxWindow *parent, bool use_in_sync_dialog) :
m_sizer_ams->Add(0, 0, 0, wxEXPAND, FromDIP(15));
m_sizer_ams->Add(m_right_marea_panel, 1, wxEXPAND, FromDIP(0));
// Rack nozzle manual-pick, alongside the AMS area (rack printers only). Hidden by default
// => contributes nothing to layout for non-rack printers.
m_rack_nozzle_select = new wgtDeviceNozzleRackSelect(this);
m_rack_nozzle_select->Bind(EVT_NOZZLE_SELECT_CHANGED, &AmsMapingPopup::OnNozzleMappingSelected, this);
m_rack_nozzle_select->Bind(EVT_NOZZLE_SELECT_CLICKED, [this](wxCommandEvent& e) { this->Dismiss(); });
m_rack_nozzle_select->Show(false);
m_sizer_ams->Add(m_rack_nozzle_select, 0, wxEXPAND | wxTOP | wxLEFT, FromDIP(15));
m_sizer_main->Add(title_panel, 0, wxEXPAND | wxALL, FromDIP(2));
m_sizer_main->Add(m_sizer_ams, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(14));
m_sizer_main->Add( 0, 0, 0, wxTOP, FromDIP(14));
@@ -782,6 +903,7 @@ void AmsMapingPopup::msw_rescale()
m_right_extra_slot->msw_rescale();
for (auto item : m_mapping_item_list) { item->msw_rescale(); }
for (auto container : m_amsmapping_container_list) { container->msw_rescale(); }
if (m_rack_nozzle_select) { m_rack_nozzle_select->Rescale(); }
Fit();
Refresh();
@@ -1038,7 +1160,7 @@ void AmsMapingPopup::update_items_check_state(const std::vector<FilamentInfo>& a
}
}
void AmsMapingPopup::update(MachineObject* obj, const std::vector<FilamentInfo>& ams_mapping_result)
void AmsMapingPopup::update(MachineObject* obj, const std::vector<FilamentInfo>& ams_mapping_result, bool use_dynamic_switch, std::optional<PrintFromType> print_type)
{
//BOOST_LOG_TRIVIAL(info) << "ams_mapping nozzle count " << obj->get_extder_system()->nozzle.size();
BOOST_LOG_TRIVIAL(info) << "ams_mapping total count " << obj->GetFilaSystem()->GetAmsCount();
@@ -1252,11 +1374,68 @@ void AmsMapingPopup::update(MachineObject* obj, const std::vector<FilamentInfo>&
} else {
m_right_split_ams_sizer->Show(false);
}
/*rack (manual nozzle pick; rack printers only)*/
update_rack_select(obj, use_dynamic_switch, print_type);
update_flush_waste(obj);
Layout();
Fit();
Refresh();
}
void AmsMapingPopup::update_rack_select(MachineObject* obj, bool use_dynamic_switch, std::optional<PrintFromType> print_from_type)
{
// Orca: MachineObject has no GetNozzleRack() convenience; route through the nozzle system instead.
m_rack = obj ? obj->GetNozzleSystem()->GetNozzleRack() : nullptr;
bool show_rack_select_area = false;
if (!m_mapping_from_multi_machines && !m_use_in_sync_dialog &&
obj && obj->GetNozzleSystem()->GetNozzleRack()->IsSupported() &&
print_from_type.has_value() && print_from_type.value() == PrintFromType::FROM_NORMAL) {
const auto& nozzle_pos_vec = obj->get_nozzle_mapping_result()->GetMappedNozzlePosVecByFilaId(m_current_filament_id);
m_rack_nozzle_select->UpdatSelectedNozzles(obj->GetNozzleSystem()->GetNozzleRack(), nozzle_pos_vec, use_dynamic_switch, print_from_type);
show_rack_select_area = true;
}
if (show_rack_select_area != m_rack_nozzle_select->IsShown()) {
m_right_tip_text = show_rack_select_area ? _L("Select Filament && Hotends") : _L("Select Filament");
m_right_tips->SetLabel(m_right_tip_text);
m_rack_nozzle_select->Show(show_rack_select_area);
Layout();
Fit();
}
}
void AmsMapingPopup::update_flush_waste(MachineObject* obj)
{
if (!obj) {
m_flush_warning_panel->Hide();
return;
};
float flush_waste_base = obj->get_nozzle_mapping_result()->GetFlushWeightBase();
float flush_waste_current = obj->get_nozzle_mapping_result()->GetFlushWeightCurrent();
if ((flush_waste_base != -1) && (flush_waste_current != -1) && flush_waste_current > flush_waste_base) {
m_flush_warning_panel->SetLabel(wxString::Format(_L("Printing with the current nozzle may produce an extra %0.2f g of waste."), flush_waste_current - flush_waste_base));
m_flush_warning_panel->Show();
} else {
m_flush_warning_panel->Hide();
}
}
void AmsMapingPopup::OnNozzleMappingSelected(wxCommandEvent& evt)
{
if (auto ptr = m_rack.lock()) {
MachineObject* obj = ptr->GetNozzleSystem()->GetOwner();
obj->get_nozzle_mapping_result()->SetManualNozzleMappingByFila(m_current_filament_id, m_rack_nozzle_select->GetSelectedNozzlePosID());
update_flush_waste(obj);
}
evt.Skip();
Dismiss();
}
std::vector<TrayData> AmsMapingPopup::parse_ams_mapping(const std::map<std::string, DevAms*, NumericStrCompare>& amsList)
{
std::vector<TrayData> m_tray_data;
@@ -2305,12 +2484,25 @@ _GetBackupStatus(unsigned int fila_back_group)
for (int j = 16; j < 32; j++)/* single ams is from 128*/
{
// N9 / AMS-Lite-mixed reports its four backup slots in bits 24-27, which decode directly to
// tray ids 24-27 (handled in the loop below). Keep those bits out of the single-ams (128+)
// range; all other bits keep their existing mapping unchanged.
if (j >= 24 && j <= 27)
continue;
if (fila_back_group & (1 << j))
{
trayid_group[128 + j - 16] = true;
}
}
for (int i = 24; i <= 27; i++)/* ams lite for N9 */
{
if (fila_back_group & (1 << i))
{
trayid_group[i] = true;
}
}
return trayid_group;
}
+25 -1
View File
@@ -41,8 +41,15 @@
#include "slic3r/GUI/DeviceCore/DevUtil.h"
#include <optional>
#define MAPPING_ITEM_INVALID_REMAIN -1
namespace Slic3r {
class DevNozzleRack;
namespace GUI { class wgtDeviceNozzleRackSelect; }
}
namespace Slic3r { namespace GUI {
@@ -93,6 +100,9 @@ public:
//info
wxColour m_ams_coloul;
wxString m_ams_name;
// Physical nozzle(s) the print-dispatch mapping assigned to this filament (e.g. "R1", "L R").
// Empty on printers without a nozzle rack or filament switcher; when set, the card grows a row.
wxString m_mapped_nozzle_str;
int m_ams_ctype = 0;
std::vector<wxColour> m_ams_cols = std::vector<wxColour>();
//reset
@@ -107,6 +117,7 @@ public:
ScalableBitmap m_filament_wheel_transparent;
ScalableBitmap m_ams_wheel_mitem;
ScalableBitmap m_ams_not_match;
ScalableBitmap m_rack_nozzle_bitmap;
bool m_selected {false};
bool m_warning{false};
@@ -117,6 +128,10 @@ public:
void allow_paint_dropdown(bool flag);
void set_ams_info(wxColour col, wxString txt, int ctype=0, std::vector<wxColour> cols= std::vector<wxColour>(),bool record_back_info = false);
void reset_ams_info();
// Set the mapped-nozzle label ("R1", "L R", ...); grows/shrinks the card as the row appears/clears.
void set_nozzle_info(const wxString& mapped_nozzle_str);
// Size the card: base swatch height, plus one row when a nozzle label is present.
void messure_size();
void disable();
void enable();
@@ -259,12 +274,19 @@ public:
wxBoxSizer* m_sizer_split_ams_right;
bool m_mapping_from_multi_machines {false};
// Rack nozzle manual-pick (rack printers only; hidden by default via Show(false)).
wgtDeviceNozzleRackSelect* m_rack_nozzle_select{nullptr};
Label* m_flush_warning_panel{nullptr}; // Orca: uses a plain Label for the flush warning
std::weak_ptr<DevNozzleRack> m_rack;
void set_sizer_title(wxBoxSizer *sizer, wxString text);
wxBoxSizer* create_split_sizer(wxWindow* parent, wxString text);
void set_send_win(wxWindow* win) {send_win = win;};
void update_materials_list(std::vector<std::string> list);
void set_tag_texture(std::string texture);
void update(MachineObject* obj, const std::vector<FilamentInfo>& ams_mapping_result);
void update(MachineObject* obj, const std::vector<FilamentInfo>& ams_mapping_result, bool use_dynamic_switch = false, std::optional<PrintFromType> print_type = std::nullopt);
void update_rack_select(MachineObject* obj, bool use_dynamic_switch, std::optional<PrintFromType> print_type);
void update_flush_waste(MachineObject* obj);
void update_title(MachineObject* obj);
void update_items_check_state(const std::vector<FilamentInfo>& ams_mapping_result);
void update_ams_data_multi_machines();
@@ -295,6 +317,8 @@ public:
void EnableExtMappingFilaTypeCheck(bool to_check = true) { m_ext_mapping_filatype_check = to_check;} ;
private:
void OnNozzleMappingSelected(wxCommandEvent& evt);
ResetCallback m_reset_callback{nullptr};
std::string m_material_index;
bool m_only_show_ext_spool{false};
@@ -241,6 +241,9 @@ void BackgroundSlicingProcess::process_fff()
//BBS: add plate index into render params
m_temp_output_path = this->get_current_plate()->get_tmp_gcode_path();
m_fff_print->export_gcode(m_temp_output_path, m_gcode_result, [this](const ThumbnailsParams& params) { return this->render_thumbnails(params); });
// Orca: BBL printers post-process the g-code in place here and never re-parse it into a fresh
// GCodeProcessorResult, so m_gcode_result->nozzle_group_result (consumed by the H2C print-dispatch
// nozzle mapping) survives post-processing. No preservation guard is needed on this path.
if(m_fff_print->is_BBL_printer())
run_post_process_scripts(m_temp_output_path, false, "File", m_temp_output_path, m_fff_print->full_print_config());
+141 -24
View File
@@ -13,6 +13,9 @@
#include "Plater.hpp"
#include "DeviceCore/DevExtruderSystem.h"
#include "DeviceCore/DevManager.h"
#include "DeviceCore/DevConfigUtil.h"
#include "DeviceCore/DevNozzleSystem.h"
#include "DeviceCore/DevNozzleRack.h"
namespace Slic3r {
namespace GUI {
@@ -26,6 +29,7 @@ namespace GUI {
enum CaliColumnType : int {
Cali_Name = 0,
Cali_Filament,
Cali_Nozzle_ID,
Cali_Nozzle,
Cali_K_Value,
Cali_Delete,
@@ -33,6 +37,9 @@ enum CaliColumnType : int {
Cali_Type_Count
};
// Orca: derive nozzle-volume support from the machine preset's nozzle_volume array
// rather than a live device-capability query, to avoid flipping the Nozzle-Flow column
// visibility on shipping printers (H2D/X1/P1/A1).
bool support_nozzle_volume(const MachineObject* obj)
{
if (!obj)
@@ -49,14 +56,17 @@ bool support_nozzle_volume(const MachineObject* obj)
return false;
}
int get_colume_idx(CaliColumnType type, MachineObject* obj)
// Rack hotend position code -> label: 0 = fixed ("R"); >=0x10 = rack slot number; else N/A.
static wxString nozzle_id_code_to_string(int code)
{
if (!support_nozzle_volume(obj)
&& (type > CaliColumnType::Cali_Nozzle)) {
return type - 1;
if (code == 0) {
return "R";
} else if (code >= 0x10) {
return wxString::Format("%d", code - 0x10 + 1);
} else {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << "Nozzle position id is -1 or invalid.";
return "N/A";
}
return type;
}
static wxString get_preset_name_by_filament_id(std::string filament_id)
@@ -244,6 +254,10 @@ void HistoryWindow::on_device_connected(MachineObject* obj)
}
m_comboBox_nozzle_dia->SetSelection(selection);
m_extruder_switch_btn->SetLabels(
_L(DevPrinterConfigUtil::get_toolhead_display_name(obj->printer_type, DEPUTY_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::TitleCase)),
_L(DevPrinterConfigUtil::get_toolhead_display_name(obj->printer_type, MAIN_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::TitleCase)));
if (obj->is_multi_extruders())
m_extruder_switch_btn->Show();
else
@@ -320,7 +334,21 @@ void HistoryWindow::enbale_action_buttons(bool enable) {
}
}
// The main extruder can carry a nozzle rack (H2C); the Nozzle-ID column only shows for it.
bool HistoryWindow::support_nozzle_id_column()
{
if (!curr_obj || get_extruder_id() != MAIN_EXTRUDER_ID)
return false;
auto ns = curr_obj->GetNozzleSystem();
if (!ns)
return false;
auto rack = ns->GetNozzleRack();
return rack && rack->IsSupported();
}
void HistoryWindow::sync_history_data() {
int column_idx = 0;
Freeze();
m_history_data_panel->DestroyChildren();
m_history_data_panel->Enable();
@@ -331,24 +359,35 @@ void HistoryWindow::sync_history_data() {
m_history_data_panel->SetSizer(gbSizer, true);
const bool has_nozzle_id = support_nozzle_id_column();
auto title_name = new Label(m_history_data_panel, _L("Name"));
title_name->SetFont(Label::Head_14);
gbSizer->Add(title_name, {0, get_colume_idx(CaliColumnType::Cali_Name, curr_obj) }, {1, 1}, wxBOTTOM, FromDIP(15));
BOOST_LOG_TRIVIAL(info) << "=====================" << title_name->GetLabelText().ToStdString();
gbSizer->Add(title_name, {0, column_idx++ }, {1, 1}, wxBOTTOM, FromDIP(15));
auto title_preset_name = new Label(m_history_data_panel, _L("Filament"));
title_preset_name->SetFont(Label::Head_14);
gbSizer->Add(title_preset_name, { 0, get_colume_idx(CaliColumnType::Cali_Filament, curr_obj) }, { 1, 1 }, wxBOTTOM, FromDIP(15));
gbSizer->Add(title_preset_name, { 0, column_idx++ }, { 1, 1 }, wxBOTTOM, FromDIP(15));
if (has_nozzle_id) {
auto nozzle_id_name = new Label(m_history_data_panel, _L("Nozzle ID"));
nozzle_id_name->SetFont(Label::Head_14);
{
wxString chd_ext_name = _L(DevPrinterConfigUtil::get_toolhead_display_name(curr_obj->printer_type, MAIN_EXTRUDER_ID, ToolHeadComponent::Extruder, ToolHeadNameCase::LowerCase));
nozzle_id_name->SetToolTip(wxString::Format(_L("Note: The hotend number on the %s is tied to the holder. When the hotend is moved to a new holder, its number will update automatically."), chd_ext_name));
}
gbSizer->Add(nozzle_id_name, {0, column_idx++}, {1, 1}, wxBOTTOM, FromDIP(15));
}
if (support_nozzle_volume(curr_obj)) {
auto nozzle_name = new Label(m_history_data_panel, _L("Nozzle Flow"));
nozzle_name->SetFont(Label::Head_14);
gbSizer->Add(nozzle_name, {0, get_colume_idx(CaliColumnType::Cali_Nozzle, curr_obj)}, {1, 1}, wxBOTTOM, FromDIP(15));
gbSizer->Add(nozzle_name, {0, column_idx++}, {1, 1}, wxBOTTOM, FromDIP(15));
}
auto title_k = new Label(m_history_data_panel, _L("Factor K"));
title_k->SetFont(Label::Head_14);
gbSizer->Add(title_k, { 0, get_colume_idx(CaliColumnType::Cali_K_Value,curr_obj) }, { 1, 1 }, wxBOTTOM, FromDIP(15));
gbSizer->Add(title_k, { 0, column_idx++ }, { 1, 1 }, wxBOTTOM, FromDIP(15));
// Hide
//auto title_n = new Label(m_history_data_panel, wxID_ANY, _L("N"));
@@ -357,7 +396,12 @@ void HistoryWindow::sync_history_data() {
auto title_action = new Label(m_history_data_panel, _L("Action"));
title_action->SetFont(Label::Head_14);
gbSizer->Add(title_action, {0, get_colume_idx(CaliColumnType::Cali_Delete, curr_obj)}, {1, 1});
gbSizer->Add(title_action, {0, column_idx++}, {1, 1});
// grid item num in row (Delete + Edit share one "Action" header)
const int column_count = column_idx + 1;
// reset column_idx for the data rows
column_idx = 0;
int i = 1;
for (auto& result : m_calib_results_history) {
@@ -373,13 +417,13 @@ void HistoryWindow::sync_history_data() {
n_value->Hide();
auto delete_button = new Button(m_history_data_panel, _L("Delete"));
delete_button->SetStyle(ButtonStyle::Alert, ButtonType::Window);
delete_button->Bind(wxEVT_BUTTON, [this, gbSizer, i, &result](auto& e) {
delete_button->Bind(wxEVT_BUTTON, [this, gbSizer, i, &result, column_count](auto& e) {
if (m_ui_op_lock) {
return;
} else {
m_ui_op_lock = true;
}
for (int j = 0; j < HISTORY_WINDOW_ITEMS_COUNT; j++) {
for (int j = 0; j < column_count; j++) {
auto item = gbSizer->FindItemAtPosition({ i, j });
if (item && item->GetWindow())
item->GetWindow()->Hide();
@@ -392,6 +436,8 @@ void HistoryWindow::sync_history_data() {
cali_info.cali_idx = result.cali_idx;
cali_info.nozzle_diameter = result.nozzle_diameter;
cali_info.filament_id = result.filament_id;
cali_info.nozzle_pos_id = result.nozzle_pos_id;
cali_info.nozzle_sn = result.nozzle_sn;
CalibUtils::delete_PA_calib_result(cali_info);
});
@@ -418,17 +464,23 @@ void HistoryWindow::sync_history_data() {
}
});
gbSizer->Add(name_value, {i, get_colume_idx(CaliColumnType::Cali_Name, curr_obj)}, {1, 1}, wxBOTTOM, FromDIP(15));
gbSizer->Add(preset_name_value, {i, get_colume_idx(CaliColumnType::Cali_Filament, curr_obj)}, {1, 1}, wxBOTTOM, FromDIP(15));
gbSizer->Add(name_value, {i, column_idx++}, {1, 1}, wxBOTTOM, FromDIP(15));
gbSizer->Add(preset_name_value, {i, column_idx++}, {1, 1}, wxBOTTOM, FromDIP(15));
if (has_nozzle_id) {
wxString nozzle_id = nozzle_id_code_to_string(result.nozzle_pos_id);
auto nozzle_id_label = new Label(m_history_data_panel, nozzle_id);
gbSizer->Add(nozzle_id_label, {i, column_idx++}, {1, 1}, wxBOTTOM, FromDIP(15));
}
if (support_nozzle_volume(curr_obj)) {
wxString nozzle_name = get_nozzle_volume_type_name(result.nozzle_volume_type);
auto nozzle_name_label = new Label(m_history_data_panel, nozzle_name);
gbSizer->Add(nozzle_name_label, {i, get_colume_idx(CaliColumnType::Cali_Nozzle, curr_obj)}, {1, 1}, wxBOTTOM, FromDIP(15));
gbSizer->Add(nozzle_name_label, {i, column_idx++}, {1, 1}, wxBOTTOM, FromDIP(15));
}
gbSizer->Add(k_value, {i, get_colume_idx(CaliColumnType::Cali_K_Value, curr_obj)}, {1, 1}, wxBOTTOM, FromDIP(15));
gbSizer->Add(k_value, {i, column_idx++}, {1, 1}, wxBOTTOM, FromDIP(15));
//gbSizer->Add(n_value, { i, 3 }, { 1, 1 }, wxBOTTOM, FromDIP(15));
gbSizer->Add(delete_button, {i, get_colume_idx(CaliColumnType::Cali_Delete, curr_obj)}, {1, 1}, wxBOTTOM, FromDIP(15));
gbSizer->Add(edit_button, {i, get_colume_idx(CaliColumnType::Cali_Edit, curr_obj)}, {1, 1}, wxBOTTOM, FromDIP(15));
gbSizer->Add(delete_button, {i, column_idx++}, {1, 1}, wxBOTTOM, FromDIP(15));
gbSizer->Add(edit_button, {i, column_idx++}, {1, 1}, wxBOTTOM, FromDIP(15));
column_idx = 0;
i++;
m_ui_op_lock = false;
}
@@ -523,13 +575,22 @@ EditCalibrationHistoryDialog::EditCalibrationHistoryDialog(wxWindow
if (obj && obj->is_multi_extruders()) {
Label *extruder_name_title = new Label(top_panel, _L("Extruder"));
int extruder_index = obj->is_main_extruder_on_left() ? result.extruder_id : 1 - result.extruder_id;
wxString extruder_name = extruder_index == 0 ? _L("Left") : _L("Right");
wxString extruder_name = _L(DevPrinterConfigUtil::get_toolhead_display_name(
obj->printer_type, result.extruder_id, ToolHeadComponent::Extruder, ToolHeadNameCase::TitleCase, true));
Label *extruder_name_value = new Label(top_panel, extruder_name);
flex_sizer->Add(extruder_name_title);
flex_sizer->Add(extruder_name_value);
}
if (obj && obj->GetNozzleSystem() && obj->GetNozzleSystem()->GetNozzleRack()
&& obj->GetNozzleSystem()->GetNozzleRack()->IsSupported() && result.extruder_id == MAIN_EXTRUDER_ID) {
Label* nozzle_id_title = new Label(top_panel, _L("Nozzle ID"));
wxString nozzle_id = nozzle_id_code_to_string(result.nozzle_pos_id);
Label* nozzle_id_value = new Label(top_panel, nozzle_id);
flex_sizer->Add(nozzle_id_title);
flex_sizer->Add(nozzle_id_value);
}
if (support_nozzle_volume(curr_obj)) {
Label *nozzle_name_title = new Label(top_panel, _L("Nozzle"));
wxString nozzle_name;
@@ -768,14 +829,40 @@ NewCalibrationHistoryDialog::NewCalibrationHistoryDialog(wxWindow *parent, const
Label *extruder_name_title = new Label(top_panel, _L("Extruder"));
m_comboBox_extruder = new ::ComboBox(top_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, NEW_HISTORY_DIALOG_INPUT_SIZE, 0, nullptr, wxCB_READONLY);
wxArrayString extruder_items;
extruder_items.push_back(_L("Left"));
extruder_items.push_back(_L("Right"));
extruder_items.push_back(_L(DevPrinterConfigUtil::get_toolhead_display_name(
curr_obj->printer_type, DEPUTY_EXTRUDER_ID, ToolHeadComponent::Extruder, ToolHeadNameCase::TitleCase, true)));
extruder_items.push_back(_L(DevPrinterConfigUtil::get_toolhead_display_name(
curr_obj->printer_type, MAIN_EXTRUDER_ID, ToolHeadComponent::Extruder, ToolHeadNameCase::TitleCase, true)));
m_comboBox_extruder->Set(extruder_items);
m_comboBox_extruder->SetSelection(-1);
flex_sizer->Add(extruder_name_title);
flex_sizer->Add(m_comboBox_extruder);
}
// Nozzle ID (rack printers only): choose the hotend position this manual record belongs to.
if (curr_obj->GetNozzleSystem() && curr_obj->GetNozzleSystem()->GetNozzleRack()
&& curr_obj->GetNozzleSystem()->GetNozzleRack()->IsSupported()) {
auto rack = curr_obj->GetNozzleSystem()->GetNozzleRack();
Label *nozzle_id_title = new Label(top_panel, _L("Nozzle ID"));
m_comboBox_nozzle_id = new ::ComboBox(top_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, NEW_HISTORY_DIALOG_INPUT_SIZE, 0, nullptr, wxCB_READONLY);
m_comboBox_nozzle_id->Bind(wxEVT_COMMAND_COMBOBOX_SELECTED, &NewCalibrationHistoryDialog::on_select_nozzle_pos, this);
auto main_extder = curr_obj->GetExtderSystem()->GetExtderById(MAIN_EXTRUDER_ID);
int r_nozzle_id = main_extder ? main_extder->GetNozzleId() : 0;
auto r_nozzle = curr_obj->GetNozzleSystem()->GetExtNozzle(r_nozzle_id);
if (r_nozzle.IsNormal()) {
m_comboBox_nozzle_id->Append("R", wxNullBitmap, new int{0});
}
for (auto nozzle_item : rack->GetRackNozzles()) {
if (nozzle_item.second.IsNormal()) {
m_comboBox_nozzle_id->Append(wxString::Format("%d", nozzle_item.first + 1), wxNullBitmap, new int{0x10 | nozzle_item.first});
}
}
m_comboBox_nozzle_id->SetSelection(-1);
flex_sizer->Add(nozzle_id_title);
flex_sizer->Add(m_comboBox_nozzle_id);
}
if (support_nozzle_volume(curr_obj)) {
Label *nozzle_name_title = new Label(top_panel, _L("Nozzle"));
m_comboBox_nozzle_type = new ::ComboBox(top_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, NEW_HISTORY_DIALOG_INPUT_SIZE, 0, nullptr, wxCB_READONLY);
@@ -839,6 +926,35 @@ int NewCalibrationHistoryDialog::get_extruder_id(int extruder_index)
return 0;
}
int NewCalibrationHistoryDialog::get_nozzle_combo_id_code() const
{
if (!m_comboBox_nozzle_id)
return -1;
auto sel = m_comboBox_nozzle_id->GetSelection();
if (sel != wxNOT_FOUND && m_comboBox_nozzle_id->GetClientData(sel))
return *(reinterpret_cast<int*>(m_comboBox_nozzle_id->GetClientData(sel)));
return -1;
}
void NewCalibrationHistoryDialog::on_select_nozzle_pos(wxCommandEvent &event)
{
// Mirror the picked hotend's flow onto the (Orca index-based) nozzle-type combo.
if (!curr_obj || !m_comboBox_nozzle_id || !m_comboBox_nozzle_type || !curr_obj->GetNozzleSystem())
return;
int pos = get_nozzle_combo_id_code();
if (pos < 0)
return;
DevNozzle nozzle = curr_obj->GetNozzleSystem()->GetNozzleByPosId(pos);
if (nozzle.IsNormal()) {
NozzleVolumeType volume_type = DevNozzle::ToNozzleVolumeType(nozzle.GetNozzleFlowType());
m_comboBox_nozzle_type->SetSelection(static_cast<int>(volume_type));
}
}
void NewCalibrationHistoryDialog::on_ok(wxCommandEvent &event)
{
wxString name = m_name_value->GetTextCtrl()->GetValue();
@@ -897,6 +1013,7 @@ void NewCalibrationHistoryDialog::on_ok(wxCommandEvent &event)
m_new_result.nozzle_diameter = nozzle_value;
m_new_result.filament_id = filament_id;
m_new_result.setting_id = setting_id;
m_new_result.nozzle_pos_id = get_nozzle_combo_id_code();
// Check for duplicate names from history
{
+4
View File
@@ -27,6 +27,7 @@ protected:
void enbale_action_buttons(bool enable);
float get_nozzle_value();
int get_extruder_id();
bool support_nozzle_id_column();
void on_click_new_button(wxCommandEvent &event);
@@ -76,10 +77,12 @@ public:
protected:
virtual void on_ok(wxCommandEvent &event);
virtual void on_cancel(wxCommandEvent &event);
void on_select_nozzle_pos(wxCommandEvent &event);
wxArrayString get_all_filaments(const MachineObject *obj);
int get_extruder_id(int extruder_index); // extruder_index 0 : left, 1 : right
int get_nozzle_combo_id_code() const; // rack hotend position code, -1 if none
protected:
PACalibResult m_new_result;
@@ -93,6 +96,7 @@ protected:
ComboBox *m_comboBox_filament;
ComboBox *m_comboBox_extruder;
ComboBox *m_comboBox_nozzle_type;
ComboBox *m_comboBox_nozzle_id{nullptr};
struct FilamentInfos
{
+34 -21
View File
@@ -9,7 +9,9 @@
#include "DeviceCore/DevExtruderSystem.h"
#include "DeviceCore/DevFilaBlackList.h"
#include "DeviceCore/DevFilaSystem.h"
#include "DeviceCore/DevFilaSwitch.h"
#include "DeviceCore/DevManager.h"
#include "DeviceCore/DevNozzleSystem.h"
#include "DeviceCore/DevStorage.h"
#define CALIBRATION_LABEL_SIZE wxSize(FromDIP(150), FromDIP(24))
@@ -1457,31 +1459,40 @@ bool CalibrationPresetPage::is_filament_in_blacklist(int tray_id, Preset* preset
get_tray_ams_and_slot_id(curr_obj, tray_id, ams_id, slot_id, out_tray_id);
if (wxGetApp().app_config->get("skip_ams_blacklist_check") != "true") {
bool in_blacklist = false;
std::string action;
wxString info;
std::string filamnt_type;
preset->get_filament_type(filamnt_type);
DevFilaBlacklist::CheckFilamentInfo check_info;
check_info.dev_id = curr_obj->get_dev_id();
check_info.model_id = curr_obj->printer_type;
check_info.fila_id = preset->filament_id;
preset->get_filament_type(check_info.fila_type);
check_info.ams_id = ams_id;
check_info.slot_id = slot_id;
check_info.has_filament_switch = curr_obj->GetFilaSwitch()->IsInstalled();
// fila_name intentionally left empty: the engine recovers it from the selected AMS slot,
// preserving the name-match behavior.
auto vendor = dynamic_cast<ConfigOptionStrings*> (preset->config.option("filament_vendor"));
if (vendor && (vendor->values.size() > 0)) {
std::string vendor_name = vendor->values[0];
DevFilaBlacklist::check_filaments_in_blacklist(curr_obj->printer_type, vendor_name, filamnt_type, preset->filament_id, ams_id, slot_id, "", in_blacklist, action, info);
check_info.fila_vendor = vendor->values[0];
}
if (in_blacklist) {
error_tips = info.ToUTF8().data();
if (action == "prohibition") {
return false;
}
else if (action == "warning") {
return true;
}
const auto &result = DevFilaBlacklist::check_filaments_in_blacklist(check_info);
if (const auto &prohibition_items = result.get_items_by_action("prohibition"); !prohibition_items.empty()) {
wxString combined_msg;
for (const auto &item : prohibition_items) { combined_msg += item.info_msg + "\n"; }
error_tips = combined_msg.ToUTF8().data();
return false;
}
else {
error_tips = "";
if (const auto &warning_items = result.get_items_by_action("warning"); !warning_items.empty()) {
wxString combined_msg;
for (const auto &item : warning_items) { combined_msg += item.info_msg + "\n"; }
error_tips = combined_msg.ToUTF8().data();
return true;
}
error_tips = "";
return true;
}
if (devPrinterUtil::IsVirtualSlot(ams_id)) {
if (m_cali_mode == CalibMode::Calib_PA_Line && (m_cali_method == CalibrationMethod::CALI_METHOD_AUTO || m_cali_method == CalibrationMethod::CALI_METHOD_NEW_AUTO)) {
@@ -1663,7 +1674,9 @@ bool CalibrationPresetPage::is_nozzle_info_synced() const
if (curr_obj->is_nozzle_flow_type_supported()) {
if (extruder.GetNozzleFlowType() == NozzleFlowType::NONE_FLOWTYPE)
return false;
if (int(extruder.GetNozzleFlowType()) - 1 != int(get_nozzle_volume_type(extruder_id)))
// Map device flow -> volume type via DevNozzle::ToNozzleVolumeType so U_FLOW resolves to
// nvtTPUHighFlow(3); the naive flow-1 would yield nvtHybrid(2). Identical to flow-1 for S/H flow.
if (int(DevNozzle::ToNozzleVolumeType(extruder.GetNozzleFlowType())) != int(get_nozzle_volume_type(extruder_id)))
return false;
}
}
@@ -2124,7 +2137,7 @@ void CalibrationPresetPage::init_with_machine(MachineObject* obj)
}
if (obj->GetExtderSystem()->GetNozzleFlowType(i) != NozzleFlowType::NONE_FLOWTYPE) {
m_left_comboBox_nozzle_volume->SetSelection(obj->GetExtderSystem()->GetNozzleFlowType(i) - 1);
m_left_comboBox_nozzle_volume->SetSelection(int(DevNozzle::ToNozzleVolumeType(obj->GetExtderSystem()->GetNozzleFlowType(i))));
} else {
m_left_comboBox_nozzle_volume->SetSelection(0);
}
@@ -2144,7 +2157,7 @@ void CalibrationPresetPage::init_with_machine(MachineObject* obj)
}
if (obj->GetExtderSystem()->GetNozzleFlowType(i) != NozzleFlowType::NONE_FLOWTYPE) {
m_right_comboBox_nozzle_volume->SetSelection(obj->GetExtderSystem()->GetNozzleFlowType(i) - 1);
m_right_comboBox_nozzle_volume->SetSelection(int(DevNozzle::ToNozzleVolumeType(obj->GetExtderSystem()->GetNozzleFlowType(i))));
} else {
m_right_comboBox_nozzle_volume->SetSelection(0);
}
@@ -2182,7 +2195,7 @@ void CalibrationPresetPage::init_with_machine(MachineObject* obj)
else {
if ((obj->GetExtderSystem()->GetTotalExtderCount() > 0) && (obj->GetExtderSystem()->GetNozzleFlowType(0) != NozzleFlowType::NONE_FLOWTYPE))
{
m_comboBox_nozzle_volume->SetSelection(obj->GetExtderSystem()->GetNozzleFlowType(0) - 1);
m_comboBox_nozzle_volume->SetSelection(int(DevNozzle::ToNozzleVolumeType(obj->GetExtderSystem()->GetNozzleFlowType(0))));
} else {
m_comboBox_nozzle_volume->SetSelection(0);
}
+132 -89
View File
@@ -2,6 +2,9 @@
#include "I18N.hpp"
#include "Widgets/Label.hpp"
#include "MsgDialog.hpp"
#include "DeviceCore/DevConfigUtil.h"
#include "DeviceCore/DevNozzleSystem.h"
#include "DeviceCore/DevNozzleRack.h"
namespace Slic3r { namespace GUI {
@@ -95,35 +98,43 @@ enum class GridTextInputType {
class GridTextInput : public TextInput
{
public:
GridTextInput(wxWindow* parent, wxString text, wxString label, wxSize size, int col_idx, GridTextInputType type);
GridTextInput(wxWindow* parent, wxString text, wxString label, wxSize size, int col_idx, GridTextInputType type, int extruder_id = MAIN_EXTRUDER_ID);
int get_col_idx() { return m_col_idx; }
void set_col_idx(int idx) { m_col_idx = idx; }
int get_extruder_id() { return m_extruder_id; }
void set_extruder_id(int id) { m_extruder_id = id; }
GridTextInputType get_type() { return m_type; }
void set_type(GridTextInputType type) { m_type = type; }
private:
int m_col_idx;
int m_extruder_id{MAIN_EXTRUDER_ID};
GridTextInputType m_type;
};
GridTextInput::GridTextInput(wxWindow* parent, wxString text, wxString label, wxSize size, int col_idx, GridTextInputType type)
GridTextInput::GridTextInput(wxWindow* parent, wxString text, wxString label, wxSize size, int col_idx, GridTextInputType type, int extruder_id)
: TextInput(parent, text, label, "", wxDefaultPosition, size, wxTE_PROCESS_ENTER)
, m_col_idx(col_idx)
, m_extruder_id(extruder_id)
, m_type(type)
{
}
class GridComboBox : public ComboBox {
public:
GridComboBox(wxWindow* parent, wxSize size, int col_idx);
GridComboBox(wxWindow* parent, wxSize size, int col_idx, int extruder_id = MAIN_EXTRUDER_ID);
int get_col_idx() { return m_col_idx; }
void set_col_idx(int idx) { m_col_idx = idx; }
int get_extruder_id() { return m_extruder_id; }
void set_extruder_id(int id) { m_extruder_id = id; }
private:
int m_col_idx;
int m_extruder_id{MAIN_EXTRUDER_ID};
};
GridComboBox::GridComboBox(wxWindow* parent, wxSize size, int col_idx)
GridComboBox::GridComboBox(wxWindow* parent, wxSize size, int col_idx, int extruder_id)
: ComboBox(parent, wxID_ANY, "", wxDefaultPosition, size, 0, nullptr)
, m_col_idx(col_idx)
, m_extruder_id(extruder_id)
{
}
@@ -227,7 +238,7 @@ void CaliPASaveAutoPanel::sync_cali_result(const std::vector<PACalibResult>& cal
m_calib_results.clear();
for (auto& item : cali_result) {
if (item.confidence == 0)
m_calib_results[item.tray_id] = item;
m_calib_results.push_back(item);
}
m_grid_panel->DestroyChildren();
auto grid_sizer = new wxBoxSizer(wxHORIZONTAL);
@@ -281,14 +292,14 @@ void CaliPASaveAutoPanel::sync_cali_result(const std::vector<PACalibResult>& cal
wxString tray_name = get_tray_name_by_tray_id(item.tray_id);
tray_title->SetLabel(tray_name);
auto k_value = new GridTextInput(m_grid_panel, "", "", CALIBRATION_SAVE_INPUT_SIZE, item.tray_id, GridTextInputType::K);
auto n_value = new GridTextInput(m_grid_panel, "", "", CALIBRATION_SAVE_INPUT_SIZE, item.tray_id, GridTextInputType::N);
auto k_value = new GridTextInput(m_grid_panel, "", "", CALIBRATION_SAVE_INPUT_SIZE, item.tray_id, GridTextInputType::K, item.extruder_id);
auto n_value = new GridTextInput(m_grid_panel, "", "", CALIBRATION_SAVE_INPUT_SIZE, item.tray_id, GridTextInputType::N, item.extruder_id);
k_value->GetTextCtrl()->SetValidator(wxTextValidator(wxFILTER_NUMERIC));
n_value->GetTextCtrl()->SetValidator(wxTextValidator(wxFILTER_NUMERIC));
auto k_value_failed = new Label(m_grid_panel, _L("Failed"));
auto n_value_failed = new Label(m_grid_panel, _L("Failed"));
auto comboBox_tray_name = new GridComboBox(m_grid_panel, CALIBRATION_SAVE_INPUT_SIZE, item.tray_id);
auto comboBox_tray_name = new GridComboBox(m_grid_panel, CALIBRATION_SAVE_INPUT_SIZE, item.tray_id, item.extruder_id);
auto tray_name_failed = new Label(m_grid_panel, " - ");
wxArrayString selections;
static std::vector<PACalibResult> filtered_results;
@@ -395,17 +406,26 @@ void CaliPASaveAutoPanel::save_to_result_from_widgets(wxWindow* window, bool* ou
return;
//operate
// The grid can hold two rows with the same tray_id under different extruders
// (rack / dual-address configs), so results must be keyed by (tray_id, extruder_id).
auto find_result = [this](int tray_id, int extruder_id) -> PACalibResult* {
auto it = std::find_if(m_calib_results.begin(), m_calib_results.end(), [tray_id, extruder_id](const PACalibResult& r) {
return r.tray_id == tray_id && r.extruder_id == extruder_id;
});
return it != m_calib_results.end() ? &(*it) : nullptr;
};
auto input = dynamic_cast<GridTextInput*>(window);
if (input && input->IsShown()) {
int tray_id = input->get_col_idx();
int tray_id = input->get_col_idx();
int extruder_id = input->get_extruder_id();
if (input->get_type() == GridTextInputType::K) {
float k = 0.0f;
if (!CalibUtils::validate_input_k_value(input->GetTextCtrl()->GetValue(), &k)) {
*out_msg = wxString::Format(_L("Please input a valid value (K in %.1f~%.1f)"), MIN_PA_K_VALUE, MAX_PA_K_VALUE);
*out_is_valid = false;
}
else
m_calib_results[tray_id].k_value = k;
else if (auto* result = find_result(tray_id, extruder_id))
result->k_value = k;
}
else if (input->get_type() == GridTextInputType::N) {
}
@@ -413,7 +433,8 @@ void CaliPASaveAutoPanel::save_to_result_from_widgets(wxWindow* window, bool* ou
auto comboBox = dynamic_cast<GridComboBox*>(window);
if (comboBox && comboBox->IsShown()) {
int tray_id = comboBox->get_col_idx();
int tray_id = comboBox->get_col_idx();
int extruder_id = comboBox->get_extruder_id();
wxString name = comboBox->GetTextCtrl()->GetValue().ToStdString();
if (name.IsEmpty()) {
*out_msg = _L("Please enter the name you want to save to printer.");
@@ -423,7 +444,8 @@ void CaliPASaveAutoPanel::save_to_result_from_widgets(wxWindow* window, bool* ou
*out_msg = _L("The name cannot exceed 40 characters.");
*out_is_valid = false;
}
m_calib_results[tray_id].name = into_u8(name);
if (auto* result = find_result(tray_id, extruder_id))
result->name = into_u8(name);
}
auto childern = window->GetChildren();
@@ -441,52 +463,7 @@ bool CaliPASaveAutoPanel::get_result(std::vector<PACalibResult>& out_result) {
else
save_to_result_from_widgets(m_grid_panel, &is_valid, &err_msg);
if (is_valid) {
/*
std::vector<PACalibResult> to_save_result;
for (auto &result : m_calib_results) {
auto iter = std::find_if(to_save_result.begin(), to_save_result.end(), [this, &result](const PACalibResult &item) {
bool has_same_name = (item.name == result.second.name && item.filament_id == result.second.filament_id);
if (m_obj && m_obj->is_multi_extruders()) {
has_same_name &= (item.extruder_id == result.second.extruder_id && item.nozzle_volume_type == result.second.nozzle_volume_type);
}
return has_same_name;
});
if (iter != to_save_result.end()) {
MessageDialog msg_dlg(nullptr, _L("Only one of the results with the same name will be saved. Are you sure you want to overwrite the other results?"),
wxEmptyString, wxICON_WARNING | wxYES_NO);
if (msg_dlg.ShowModal() != wxID_YES) {
return false;
} else {
break;
}
}
}
for (auto &result : m_history_results) {
auto iter = std::find_if(m_history_results.begin(), m_history_results.end(), [this, &result](const PACalibResult &item) {
bool has_same_name = (item.name == result.name && item.filament_id == result.filament_id);
if (m_obj && m_obj->is_multi_extruders()) {
has_same_name &= (item.extruder_id == result.extruder_id && item.nozzle_volume_type == result.nozzle_volume_type);
}
return has_same_name;
});
if (iter != m_history_results.end()) {
MessageDialog msg_dlg(nullptr,
wxString::Format(_L("There is already a historical calibration result with the same name: %s. Are you sure you want to override the historical result?"),
result.name),
wxEmptyString, wxICON_WARNING | wxYES_NO);
if (msg_dlg.ShowModal() != wxID_YES) {
return false;
}
}
}
*/
for (auto& result : m_calib_results) {
out_result.push_back(result.second);
}
out_result = m_calib_results;
return true;
}
else {
@@ -517,11 +494,12 @@ void CaliPASaveAutoPanel::sync_cali_result_for_multi_extruder(const std::vector<
m_calib_results.clear();
for (auto &item : cali_result) {
if (item.confidence == 0) {
int tray_id = 4 * item.ams_id + item.slot_id;
PACalibResult result = item;
result.tray_id = 4 * item.ams_id + item.slot_id;
if (item.ams_id == VIRTUAL_TRAY_MAIN_ID || item.ams_id == VIRTUAL_TRAY_DEPUTY_ID) {
tray_id = item.ams_id;
result.tray_id = item.ams_id;
}
m_calib_results[tray_id] = item;
m_calib_results.push_back(result);
}
}
m_multi_extruder_grid_panel->DestroyChildren();
@@ -532,14 +510,21 @@ void CaliPASaveAutoPanel::sync_cali_result_for_multi_extruder(const std::vector<
m_multi_extruder_grid_panel->SetSizer(grid_sizer, true);
m_multi_extruder_grid_panel->Bind(wxEVT_LEFT_DOWN, [this](auto &e) { SetFocusIgnoringChildren(); });
wxStaticBoxSizer *left_sizer = new wxStaticBoxSizer(wxVERTICAL, m_multi_extruder_grid_panel, _L("Left extruder"));
wxStaticBoxSizer *right_sizer = new wxStaticBoxSizer(wxVERTICAL, m_multi_extruder_grid_panel, _L("Right extruder"));
const std::string& cwsp_pt = m_obj->printer_type;
wxStaticBoxSizer *left_sizer = new wxStaticBoxSizer(wxVERTICAL, m_multi_extruder_grid_panel, _L(DevPrinterConfigUtil::get_toolhead_display_name(cwsp_pt, DEPUTY_EXTRUDER_ID, ToolHeadComponent::Extruder, ToolHeadNameCase::SentenceCase)));
wxStaticBoxSizer *right_sizer = new wxStaticBoxSizer(wxVERTICAL, m_multi_extruder_grid_panel, _L(DevPrinterConfigUtil::get_toolhead_display_name(cwsp_pt, MAIN_EXTRUDER_ID, ToolHeadComponent::Extruder, ToolHeadNameCase::SentenceCase)));
grid_sizer->Add(left_sizer);
grid_sizer->AddSpacer(COLUMN_GAP);
grid_sizer->Add(right_sizer);
wxFlexGridSizer *left_grid_sizer = new wxFlexGridSizer(3, COLUMN_GAP, ROW_GAP);
wxFlexGridSizer *right_grid_sizer = new wxFlexGridSizer(3, COLUMN_GAP, ROW_GAP);
// The main extruder can carry a nozzle rack (H2C); non-rack printers keep the 3-column layout.
auto ns = m_obj->GetNozzleSystem();
auto rack = ns ? ns->GetNozzleRack() : nullptr;
bool has_rack = rack && rack->IsSupported();
wxFlexGridSizer *right_grid_sizer = new wxFlexGridSizer(has_rack ? 4 : 3, COLUMN_GAP, ROW_GAP);
left_sizer->Add(left_grid_sizer);
right_sizer->Add(right_grid_sizer);
@@ -567,29 +552,46 @@ void CaliPASaveAutoPanel::sync_cali_result_for_multi_extruder(const std::vector<
auto k_title = new Label(m_multi_extruder_grid_panel, _L("Factor K"), 0, CALIBRATION_SAVE_NUMBER_INPUT_SIZE);
k_title->SetFont(Label::Head_14);
right_grid_sizer->Add(k_title, 1, wxALIGN_CENTER);
if (has_rack) {
auto nozzle_title = new Label(m_multi_extruder_grid_panel, _L("Nozzle ID"), 0, CALIBRATION_SAVE_NUMBER_INPUT_SIZE);
nozzle_title->SetFont(Label::Head_14);
right_grid_sizer->Add(nozzle_title, 1, wxALIGN_CENTER);
}
}
std::vector<std::pair<int, std::string>> preset_names;
// key: (extruder_id, tray_id)
std::vector<std::pair<std::pair<int, int>, std::string>> preset_names;
int i = 1;
std::unordered_set<std::string> set;
for (auto &info : m_obj->selected_cali_preset) {
std::string default_name;
// extruder _id
// Derive extruder_id from the tray so the extruder/tray key stays consistent
// with the calibration result's extruder_id.
int extruder_id = 0;
if (info.tray_id == VIRTUAL_TRAY_MAIN_ID) {
extruder_id = 0;
} else if (info.tray_id == VIRTUAL_TRAY_DEPUTY_ID) {
extruder_id = 1;
} else {
int ams_id = info.tray_id / 4;
extruder_id = m_obj->get_extruder_id_by_ams_id(std::to_string(ams_id));
}
{
int extruder_id = 0;
if (info.tray_id == VIRTUAL_TRAY_MAIN_ID) {
extruder_id = 0;
} else if (info.tray_id == VIRTUAL_TRAY_DEPUTY_ID) {
extruder_id = 1;
} else {
int ams_id = info.tray_id / 4;
extruder_id = m_obj->get_extruder_id_by_ams_id(std::to_string(ams_id));
}
if (extruder_id == 0) {
default_name += L("Right Nozzle");
} else if (extruder_id == 1){
default_name += L("Left Nozzle");
if (extruder_id == MAIN_EXTRUDER_ID) {
default_name += DevPrinterConfigUtil::get_toolhead_display_name(cwsp_pt, MAIN_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::TitleCase);
if (has_rack) {
default_name += "_";
if (info.nozzle_pos_id == 0) {
default_name += "R";
} else if (info.nozzle_pos_id >= 0x10) {
default_name += std::to_string((info.nozzle_pos_id & 0x0f) + 1);
} else {
default_name += "N/A";
}
}
} else if (extruder_id == DEPUTY_EXTRUDER_ID) {
default_name += DevPrinterConfigUtil::get_toolhead_display_name(cwsp_pt, DEPUTY_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::TitleCase);
}
}
@@ -614,7 +616,7 @@ void CaliPASaveAutoPanel::sync_cali_result_for_multi_extruder(const std::vector<
}
}
preset_names.push_back({info.tray_id, default_name});
preset_names.push_back({{extruder_id, info.tray_id}, default_name});
}
bool left_first_add_item = true;
@@ -648,14 +650,19 @@ void CaliPASaveAutoPanel::sync_cali_result_for_multi_extruder(const std::vector<
tray_title->SetBitmap(*get_extruder_color_icon(full_filament_ams_list[item.tray_id].opt_string("filament_colour", 0u), tray_name.ToStdString(), FromDIP(20), FromDIP(20)));
tray_title->SetToolTip("");
auto k_value = new GridTextInput(m_multi_extruder_grid_panel, "", "", CALIBRATION_SAVE_NUMBER_INPUT_SIZE, item.tray_id, GridTextInputType::K);
auto n_value = new GridTextInput(m_multi_extruder_grid_panel, "", "", CALIBRATION_SAVE_NUMBER_INPUT_SIZE, item.tray_id, GridTextInputType::N);
auto k_value = new GridTextInput(m_multi_extruder_grid_panel, "", "", CALIBRATION_SAVE_NUMBER_INPUT_SIZE, item.tray_id, GridTextInputType::K, item.extruder_id);
auto n_value = new GridTextInput(m_multi_extruder_grid_panel, "", "", CALIBRATION_SAVE_NUMBER_INPUT_SIZE, item.tray_id, GridTextInputType::N, item.extruder_id);
k_value->GetTextCtrl()->SetValidator(wxTextValidator(wxFILTER_NUMERIC));
n_value->GetTextCtrl()->SetValidator(wxTextValidator(wxFILTER_NUMERIC));
auto k_value_failed = new Label(m_multi_extruder_grid_panel, _L("Failed"));
auto n_value_failed = new Label(m_multi_extruder_grid_panel, _L("Failed"));
auto comboBox_tray_name = new GridComboBox(m_multi_extruder_grid_panel, CALIBRATION_SAVE_INPUT_SIZE, item.tray_id);
auto nozzle_id_value = new Label(m_multi_extruder_grid_panel, wxEmptyString);
nozzle_id_value->SetMinSize(wxSize(FromDIP(180), FromDIP(24)));
nozzle_id_value->Wrap(-1);
auto nozzle_id_failed = new Label(m_multi_extruder_grid_panel, _L("Failed"));
auto comboBox_tray_name = new GridComboBox(m_multi_extruder_grid_panel, CALIBRATION_SAVE_INPUT_SIZE, item.tray_id, item.extruder_id);
auto tray_name_failed = new Label(m_multi_extruder_grid_panel, " - ");
wxArrayString selections;
static std::vector<PACalibResult> filtered_results;
@@ -671,22 +678,26 @@ void CaliPASaveAutoPanel::sync_cali_result_for_multi_extruder(const std::vector<
}
comboBox_tray_name->Set(selections);
auto set_edit_mode = [this, k_value, n_value, k_value_failed, n_value_failed, comboBox_tray_name, tray_name_failed](std::string str) {
auto set_edit_mode = [this, k_value, n_value, k_value_failed, n_value_failed, nozzle_id_value, nozzle_id_failed, comboBox_tray_name, tray_name_failed](std::string str, bool display_nozzle) {
if (str == "normal") {
comboBox_tray_name->Show();
tray_name_failed->Show(false);
k_value->Show();
n_value->Show();
nozzle_id_value->Show(display_nozzle);
k_value_failed->Show(false);
n_value_failed->Show(false);
nozzle_id_failed->Show(false);
}
if (str == "failed") {
comboBox_tray_name->Show(false);
tray_name_failed->Show();
k_value->Show(false);
n_value->Show(false);
nozzle_id_value->Show(false);
k_value_failed->Show();
n_value_failed->Show();
nozzle_id_failed->Show(display_nozzle);
}
// hide n value
@@ -698,15 +709,39 @@ void CaliPASaveAutoPanel::sync_cali_result_for_multi_extruder(const std::vector<
};
if (!result_failed) {
set_edit_mode("normal");
set_edit_mode("normal", has_rack && item.extruder_id == MAIN_EXTRUDER_ID);
auto k_str = wxString::Format("%.3f", item.k_value);
auto n_str = wxString::Format("%.3f", item.n_coef);
k_value->GetTextCtrl()->SetValue(k_str);
n_value->GetTextCtrl()->SetValue(n_str);
if (has_rack && item.extruder_id == MAIN_EXTRUDER_ID) {
wxString nozzle_id_str;
if (item.nozzle_pos_id == 0) {
nozzle_id_str += "R | ";
} else if (item.nozzle_pos_id >= 0x10) {
nozzle_id_str += wxString::Format("%d | ", (item.nozzle_pos_id & 0x0f) + 1);
} else {
nozzle_id_str += "N/A | ";
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << "Nozzle position id is -1 or invalid.";
}
nozzle_id_str += wxString::Format("%.1f mm ", item.nozzle_diameter);
switch (item.nozzle_volume_type) {
case NozzleVolumeType::nvtStandard: nozzle_id_str += _L("Standard Flow"); break;
case NozzleVolumeType::nvtHighFlow: nozzle_id_str += _L("High Flow"); break;
case NozzleVolumeType::nvtTPUHighFlow: nozzle_id_str += _L("TPU High Flow"); break;
default: break;
}
nozzle_id_value->SetLabel(nozzle_id_str);
}
for (auto &name : preset_names) {
if (item.tray_id == name.first) { comboBox_tray_name->SetValue(from_u8(name.second)); }
int extruder_id = name.first.first;
int tray_id = name.first.second;
if (item.tray_id == tray_id && item.extruder_id == extruder_id) {
comboBox_tray_name->SetValue(from_u8(name.second));
}
}
comboBox_tray_name->Bind(wxEVT_COMBOBOX, [this, comboBox_tray_name, k_value, n_value](auto &e) {
@@ -714,7 +749,7 @@ void CaliPASaveAutoPanel::sync_cali_result_for_multi_extruder(const std::vector<
auto history = filtered_results[selection];
});
} else {
set_edit_mode("failed");
set_edit_mode("failed", has_rack && item.extruder_id == MAIN_EXTRUDER_ID);
}
if ((m_obj->is_main_extruder_on_left() && item.extruder_id == 0)
@@ -762,6 +797,14 @@ void CaliPASaveAutoPanel::sync_cali_result_for_multi_extruder(const std::vector<
} else {
right_grid_sizer->Add(k_value_failed, 1, wxEXPAND);
}
if (has_rack) {
if (nozzle_id_value->IsShown()) {
right_grid_sizer->Add(nozzle_id_value, 1, wxEXPAND);
} else {
right_grid_sizer->Add(nozzle_id_failed, 1, wxEXPAND);
}
}
}
}
+4 -1
View File
@@ -107,7 +107,10 @@ protected:
wxPanel* m_part_failed_panel;
wxPanel* m_grid_panel{ nullptr };
wxPanel* m_multi_extruder_grid_panel{ nullptr };
std::map<int, PACalibResult> m_calib_results;// map<tray_id, PACalibResult>
// keyed by (tray_id, extruder_id): the same tray_id can appear under two
// extruders (rack / dual-address configs), so a tray_id-only map would
// silently overwrite one extruder's K/N.
std::vector<PACalibResult> m_calib_results;
std::vector<PACalibResult> m_history_results;
bool m_is_all_failed{ true };
MachineObject* m_obj{ nullptr };
+58 -1
View File
@@ -23,6 +23,7 @@
#include "fast_float/fast_float.h"
#include "DeviceCore/DevFilaSystem.h"
#include "DeviceCore/DevFilaSwitch.h"
#include "DeviceCore/DevExtensionTool.h"
#include "DeviceCore/DevExtruderSystem.h"
#include "DeviceCore/DevNozzleSystem.h"
@@ -39,6 +40,7 @@
#include "DeviceCore/DevHMS.h"
#include "DeviceCore/DevMapping.h"
#include "DeviceCore/DevMappingNozzle.h"
#include "DeviceCore/DevManager.h"
#include "DeviceCore/DevUtil.h"
@@ -567,11 +569,14 @@ MachineObject::MachineObject(DeviceManager* manager, NetworkAgent* agent, std::s
m_extension_tool = DevExtensionTool::Create(this);
m_nozzle_system = new DevNozzleSystem(this);
m_fila_system = new DevFilaSystem(this);
m_fila_switch = new DevFilaSwitch(this);
m_hms_system = new DevHMS(this);
m_config = new DevConfig(this);
m_ctrl = new DevCtrl(this);
m_print_options = new DevPrintOptions(this);
m_nozzle_mapping_ptr = std::make_shared<DevNozzleMappingCtrl>(this);
}
}
@@ -615,6 +620,9 @@ MachineObject::~MachineObject()
delete m_fila_system;
m_fila_system = nullptr;
delete m_fila_switch;
m_fila_switch = nullptr;
delete m_hms_system;
m_hms_system = nullptr;
@@ -862,6 +870,9 @@ void MachineObject::clear_version_info()
cutting_module_version_info = DevFirmwareVersionInfo();
extinguish_version_info = DevFirmwareVersionInfo();
module_vers.clear();
// Drop cached rack-hotend (WTM) firmware alongside the module list.
// Inert for non-rack printers (rack firmware map is empty).
m_nozzle_system->ClearFirmwareInfoWTM();
}
void MachineObject::store_version_info(const DevFirmwareVersionInfo& info)
@@ -874,6 +885,11 @@ void MachineObject::store_version_info(const DevFirmwareVersionInfo& info)
cutting_module_version_info = info;
} else if (info.isExtinguishSystem()) {
extinguish_version_info = info;
} else if (info.isWTM()) {
// Route rack-hotend / extruder-nozzle firmware into the nozzle system so
// the rack upgrade UI can read per-nozzle versions. isWTM() is false for every non-rack
// printer's modules, so this branch never fires outside H2C.
m_nozzle_system->AddFirmwareInfoWTM(info);
}
module_vers.emplace(info.name, info);
@@ -1976,6 +1992,10 @@ int MachineObject::command_set_pa_calibration(const std::vector<PACalibResult> &
j["print"]["filaments"][i]["n_coef"] = std::to_string(pa_calib_values[i].n_coef);
else
j["print"]["filaments"][i]["n_coef"] = "0.0";
if (pa_calib_values[i].nozzle_pos_id >= 0) {
j["print"]["filaments"][i]["nozzle_pos"] = pa_calib_values[i].nozzle_pos_id;
j["print"]["filaments"][i]["nozzle_sn"] = pa_calib_values[i].nozzle_sn;
}
}
BOOST_LOG_TRIVIAL(info) << "extrusion_cali_set: " << j.dump();
@@ -1994,6 +2014,10 @@ int MachineObject::command_delete_pa_calibration(const PACalibIndexInfo& pa_cali
j["print"]["nozzle_id"] = _generate_nozzle_id(pa_calib.nozzle_volume_type, to_string_nozzle_diameter(pa_calib.nozzle_diameter)).ToStdString();
j["print"]["filament_id"] = pa_calib.filament_id;
j["print"]["cali_idx"] = pa_calib.cali_idx;
if (pa_calib.nozzle_pos_id >= 0) {
j["print"]["nozzle_pos"] = pa_calib.nozzle_pos_id;
j["print"]["nozzle_sn"] = pa_calib.nozzle_sn;
}
j["print"]["nozzle_diameter"] = to_string_nozzle_diameter(pa_calib.nozzle_diameter);
BOOST_LOG_TRIVIAL(info) << "extrusion_cali_del: " << j.dump();
@@ -2014,6 +2038,11 @@ int MachineObject::command_get_pa_calibration_tab(const PACalibExtruderInfo &cal
j["print"]["nozzle_id"] = _generate_nozzle_id(calib_info.nozzle_volume_type, to_string_nozzle_diameter(calib_info.nozzle_diameter)).ToStdString();
j["print"]["nozzle_diameter"] = to_string_nozzle_diameter(calib_info.nozzle_diameter);
if (calib_info.nozzle_pos_id >= 0) {
j["print"]["nozzle_pos"] = calib_info.nozzle_pos_id;
j["print"]["nozzle_sn"] = calib_info.nozzle_sn;
}
BOOST_LOG_TRIVIAL(info) << "extrusion_cali_get: " << j.dump();
request_tab_from_bbs = true;
return this->publish_json(j);
@@ -2040,6 +2069,10 @@ int MachineObject::commnad_select_pa_calibration(const PACalibIndexInfo& pa_cali
j["print"]["slot_id"] = pa_calib_info.slot_id;
j["print"]["cali_idx"] = pa_calib_info.cali_idx;
j["print"]["filament_id"] = pa_calib_info.filament_id;
if (pa_calib_info.nozzle_pos_id >= 0) {
j["print"]["nozzle_pos"] = pa_calib_info.nozzle_pos_id;
j["print"]["nozzle_sn"] = pa_calib_info.nozzle_sn;
}
j["print"]["nozzle_diameter"] = to_string_nozzle_diameter(pa_calib_info.nozzle_diameter);
BOOST_LOG_TRIVIAL(info) << "extrusion_cali_sel: " << j.dump();
@@ -2379,6 +2412,7 @@ void MachineObject::reset()
jobState_ = 0;
m_plate_index = -1;
device_cert_installed = false;
clear_auto_nozzle_mapping();// reset nozzle mapping
// reset print_json
json empty_j;
@@ -2896,6 +2930,7 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
}
m_fan->ParseV2_0(jj);
m_fila_switch->ParseFilaSwitchInfo(jj);
if (jj.contains("support_filament_backup")) {
if (jj["support_filament_backup"].is_boolean()) {
@@ -3000,6 +3035,8 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
if (jj.contains("command")) {
m_nozzle_mapping_ptr->ParseAutoNozzleMapping(jj);
if (jj["command"].get<std::string>() == "ams_change_filament") {
if (jj.contains("errno")) {
if (jj["errno"].is_number()) {
@@ -4082,6 +4119,14 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
pa_calib_result.nozzle_volume_type = convert_to_nozzle_type((*it)["nozzle_id"].get<std::string>());
}
if ((*it).contains("nozzle_pos")) {
pa_calib_result.nozzle_pos_id = (*it)["nozzle_pos"].get<int>();
}
if ((*it).contains("nozzle_sn")) {
pa_calib_result.nozzle_sn = (*it)["nozzle_sn"].get<std::string>();
}
if (jj["nozzle_diameter"].is_number_float()) {
pa_calib_result.nozzle_diameter = jj["nozzle_diameter"].get<float>();
} else if (jj["nozzle_diameter"].is_string()) {
@@ -4179,6 +4224,14 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
pa_calib_result.nozzle_volume_type = NozzleVolumeType::nvtStandard;
}
if (it->contains("nozzle_pos")) {
pa_calib_result.nozzle_pos_id = (*it)["nozzle_pos"].get<int>();
}
if (it->contains("nozzle_sn")) {
pa_calib_result.nozzle_sn = (*it)["nozzle_sn"].get<std::string>();
}
if ((*it)["k_value"].is_number_float())
pa_calib_result.k_value = (*it)["k_value"].get<float>();
else if ((*it)["k_value"].is_string())
@@ -5004,6 +5057,7 @@ void MachineObject::parse_new_info(json print)
is_support_user_preset = get_flag_bits(fun, 11);
is_support_door_open_check = get_flag_bits(fun, 12);
is_support_nozzle_blob_detection = get_flag_bits(fun, 13);
m_nozzle_system->SetSupportNozzleRack(get_flag_bits(fun, 60)); // H2C hotend rack support (device-side gate for the sync dialog)
is_support_upgrade_kit = get_flag_bits(fun, 14);
is_support_internal_timelapse = get_flag_bits(fun, 28);
m_support_mqtt_homing = get_flag_bits(fun, 32);
@@ -5066,7 +5120,10 @@ void MachineObject::parse_new_info(json print)
DevBed::ParseV2_0(device,m_bed);
if (device.contains("nozzle")) { DevNozzleSystemParser::ParseV2_0(device["nozzle"], m_nozzle_system); }
// Pass the whole device json: ParseV2_0 reads ext/rack nozzles from device["nozzle"] and the
// hotend-rack state from device["holder"] (rack data lives outside device["nozzle"]). It is a
// no-op for devices that report neither key, so non-rack machines are unaffected.
DevNozzleSystemParser::ParseV2_0(device, m_nozzle_system);
if (device.contains("extruder")) { ExtderSystemParser::ParseV2_0(device["extruder"], m_extder_system);}
if (device.contains("ext_tool")) { DevExtensionToolParser::ParseV2_0(device["ext_tool"], m_extension_tool); }
+13
View File
@@ -83,10 +83,12 @@ class DevExtensionTool;
class DevExtderSystem;
class DevFan;
class DevFilaSystem;
class DevFilaSwitch;
class DevPrintOptions;
class DevHMS;
class DevLamp;
class DevNozzleSystem;
class DevNozzleMappingCtrl;
class DeviceManager;
class DevStorage;
struct DevPrintTaskRatingInfo;
@@ -115,6 +117,7 @@ private:
DevExtderSystem* m_extder_system;
DevNozzleSystem* m_nozzle_system;
DevFilaSystem* m_fila_system;
DevFilaSwitch* m_fila_switch;
DevFan* m_fan;
DevBed * m_bed;
DevStorage* m_storage;
@@ -131,6 +134,11 @@ private:
/*Config*/
DevConfig* m_config;
/* print-dispatch nozzle mapping (H2C hotend rack). Created unconditionally in the ctor so
get_nozzle_mapping_result() is always valid; stays empty (no result attached) for every
non-rack printer. */
std::shared_ptr<DevNozzleMappingCtrl> m_nozzle_mapping_ptr;
public:
MachineObject(DeviceManager* manager, NetworkAgent* agent, std::string name, std::string id, std::string ip);
~MachineObject();
@@ -329,7 +337,12 @@ public:
DevNozzleSystem* GetNozzleSystem() const { return m_nozzle_system;}
/* print-dispatch nozzle mapping (H2C hotend rack); result stays empty for non-rack printers */
std::shared_ptr<DevNozzleMappingCtrl> get_nozzle_mapping_result() const { return m_nozzle_mapping_ptr; }
void clear_auto_nozzle_mapping();// defined in DevMappingNozzle.cpp
DevFilaSystem* GetFilaSystem() const { return m_fila_system;}
DevFilaSwitch* GetFilaSwitch() const { return m_fila_switch;}
bool HasAms() const;
DevLamp* GetLamp() const { return m_lamp; }
+10
View File
@@ -4,5 +4,15 @@ list(APPEND SLIC3R_GUI_SOURCES
GUI/DeviceTab/uiAmsHumidityPopup.cpp
GUI/DeviceTab/uiDeviceUpdateVersion.h
GUI/DeviceTab/uiDeviceUpdateVersion.cpp
GUI/DeviceTab/wgtDeviceNozzleRackNozzleItem.h
GUI/DeviceTab/wgtDeviceNozzleRackNozzleItem.cpp
GUI/DeviceTab/wgtDeviceNozzleRack.h
GUI/DeviceTab/wgtDeviceNozzleRack.cpp
GUI/DeviceTab/wgtDeviceNozzleRackUpdate.h
GUI/DeviceTab/wgtDeviceNozzleRackUpdate.cpp
GUI/DeviceTab/wgtDeviceNozzleSelect.h
GUI/DeviceTab/wgtDeviceNozzleSelect.cpp
GUI/DeviceTab/wgtMsgBox.h
GUI/DeviceTab/wgtMsgBox.cpp
)
set(SLIC3R_GUI_SOURCES ${SLIC3R_GUI_SOURCES} PARENT_SCOPE)
@@ -0,0 +1,700 @@
//**********************************************************/
/* File: wgtDeviceNozzleRack.cpp
* Description: The Device-tab panel with the toolhead nozzle and the H2C induction hotend rack.
*
* \n class wgtDeviceNozzleRack;
* \n class wgtDeviceNozzleRackToolHead;
* \n class wgtDeviceNozzleRackArea;
* \n class wgtDeviceNozzleRackPos;
*
* The wgtDeviceNozzleRackNozzleItem tile and the EVT_NOZZLE_RACK_NOZZLE_ITEM_SELECTED event live in
* wgtDeviceNozzleRackNozzleItem.{h,cpp}, so they are not redefined here.
//**********************************************************/
#include "wgtDeviceNozzleRack.h"
#include "wgtDeviceNozzleRackUpdate.h"
#include "slic3r/GUI/DeviceCore/DevNozzleSystem.h"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/MainFrame.hpp"
#include "slic3r/GUI/wxExtensions.hpp"
#include "slic3r/GUI/Widgets/Button.hpp"
#include "slic3r/GUI/Widgets/Label.hpp"
#define WX_DIP_SIZE(x, y) wxSize(FromDIP(x), FromDIP(y))
#define L_RAW_A_STR _L("Row A")
#define L_RAW_B_STR _L("Row B")
// Orca: StateColor lacks these grey constants, so mirror the values here.
static const wxColour WGT_GREY200 = wxColour(248, 248, 248);
static const wxColour WGT_GREY300 = wxColour(238, 238, 238);
static wxColour s_hgreen_clr("#00AE42");
namespace Slic3r::GUI
{
// Tints the "yellow" template pixels of a nozzle bitmap with the loaded filament colour.
// Duplicates the file-local helper of the same purpose in wgtDeviceNozzleRackNozzleItem.cpp.
static wxBitmap SetNozzleBmpColor(const wxBitmap& bmp, const std::string& color_str) {
if(color_str.empty()) return bmp;
wxImage img = bmp.ConvertToImage();
wxColour color("#" + color_str);
for (int y = 0; y < img.GetHeight(); ++y) {
for (int x = 0; x < img.GetWidth(); ++x) {
unsigned char r = img.GetRed(x, y);
unsigned char g = img.GetGreen(x, y);
unsigned char b = img.GetBlue(x, y);
/*replace yellow with color*/
if ( r >= 180 && g >= 180 && b <= 150) {
img.SetRGB(x, y, color.Red(), color.Green(), color.Blue());
}
}
}
return wxBitmap(img, -1, bmp.GetScaleFactor());
}
// Orca: StateColor has no gray button style, so define one file-local. This panel is its only
// consumer, so keeping it here avoids touching the shared StateColor widget.
static StateColor s_button_style_gray()
{
return StateColor(std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Pressed),
std::pair<wxColour, int>(*wxWHITE, StateColor::Focused),
std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Hovered),
std::pair<wxColour, int>(*wxWHITE, StateColor::Normal));
}
wgtDeviceNozzleRack::wgtDeviceNozzleRack(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
: wxPanel(parent, id, pos, size, style)
{
CreateGui();
}
void wgtDeviceNozzleRack::CreateGui()
{
m_toolhead_panel = new wgtDeviceNozzleRackToolHead(this);
m_rack_area = new wgtDeviceNozzleRackArea(this);
wxPanel* separator = new wxPanel(this);
separator->SetMaxSize(wxSize(FromDIP(1), -1));
separator->SetMinSize(wxSize(FromDIP(1), -1));
separator->SetBackgroundColour(WGT_GREY300);
wxSizer* main_sizer = new wxBoxSizer(wxHORIZONTAL);
main_sizer->AddStretchSpacer();
main_sizer->Add(m_toolhead_panel, 0, wxEXPAND);
main_sizer->Add(separator, 0, wxEXPAND);
main_sizer->Add(m_rack_area, 0, wxEXPAND);
main_sizer->AddStretchSpacer();
SetSizer(main_sizer);
SetMaxSize(WX_DIP_SIZE(586, -1));
SetMinSize(WX_DIP_SIZE(586, -1));
SetSize(WX_DIP_SIZE(586, -1));
Layout();
wxGetApp().UpdateDarkUIWin(this);
}
void wgtDeviceNozzleRack::UpdateRackInfo(std::shared_ptr<DevNozzleRack> rack)
{
if (!rack->IsSupported()) { return; }
m_nozzle_rack = rack;
if (m_nozzle_rack.expired()) { return; }
DevNozzleSystem* nozzle_system = m_nozzle_rack.lock()->GetNozzleSystem();
if (nozzle_system)
{
m_toolhead_panel->UpdateToolHeadInfo(nozzle_system->GetExtNozzle(MAIN_EXTRUDER_ID));
m_rack_area->UpdateRackInfo(m_nozzle_rack);
}
}
void wgtDeviceNozzleRack::Rescale()
{
m_toolhead_panel->Rescale();
m_rack_area->Rescale();
Layout();
}
class wgtDeviceNozzleRackTitle : public StaticBox
{
public:
wgtDeviceNozzleRackTitle(wxWindow* parent, const wxString& title) : StaticBox(parent)
{
SetBackgroundColour(WGT_GREY200);
SetBorderColor(*wxWHITE);
SetCornerRadius(0);
m_title_label = new Label(this, title);
m_title_label->SetFont(Label::Body_14);
m_title_label->SetBackgroundColour(WGT_GREY200);
wxSizer* title_sizer = new wxBoxSizer(wxHORIZONTAL);
title_sizer->AddStretchSpacer();
title_sizer->Add(m_title_label, 0, wxEXPAND | wxALIGN_CENTER | wxTOP | wxBOTTOM, FromDIP(5));
title_sizer->AddStretchSpacer();
SetSizer(title_sizer);
};
public:
void SetLabel(const wxString& new_label) { m_title_label->SetLabel(new_label); }
private:
Label* m_title_label;
};
void wgtDeviceNozzleRackToolHead::CreateGui()
{
wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);
// Create Header
wgtDeviceNozzleRackTitle* title_box = new wgtDeviceNozzleRackTitle(this, _L("Toolhead"));
mainSizer->Add(title_box, 0, wxEXPAND | wxTOP);
mainSizer->AddStretchSpacer();
// Image
m_extruder_nozzle_empty = new ScalableBitmap(this, "dev_rack_toolhead_empty", 98);
m_extruder_nozzle_normal = new ScalableBitmap(this, "dev_rack_toolhead_normal", 98);
m_toolhead_icon = new wxStaticBitmap(this, wxID_ANY, m_extruder_nozzle_empty->bmp(), wxDefaultPosition, WX_DIP_SIZE(98, 98));
mainSizer->Add(m_toolhead_icon, 0, wxALIGN_CENTRE_HORIZONTAL | wxTOP, FromDIP(20));
// Nozzle info
m_nozzle_diamenter_label = new Label(this);
m_nozzle_diamenter_label->SetFont(Label::Body_13);
m_nozzle_diamenter_label->SetBackgroundColour(*wxWHITE);
mainSizer->Add(m_nozzle_diamenter_label, 0, wxALIGN_CENTRE_HORIZONTAL | wxBOTTOM | wxTOP, FromDIP(5));
m_nozzle_flowtype_label = new Label(this);
m_nozzle_flowtype_label->SetFont(Label::Body_13);
m_nozzle_flowtype_label->SetBackgroundColour(*wxWHITE);
mainSizer->Add(m_nozzle_flowtype_label, 0, wxALIGN_CENTRE_HORIZONTAL);
mainSizer->AddStretchSpacer();
// Set sizer
SetSizer(mainSizer);
SetMaxSize(WX_DIP_SIZE(132, -1));
SetMinSize(WX_DIP_SIZE(132, -1));
SetSize(WX_DIP_SIZE(132, -1));
}
void wgtDeviceNozzleRackToolHead::UpdateToolHeadInfo(const DevNozzle& extruder_nozzle)
{
/* Labels */
if (extruder_nozzle.IsEmpty())
{
m_nozzle_diamenter_label->Show(false);
m_nozzle_flowtype_label->SetLabel(_L("Empty"));
}
else if (extruder_nozzle.IsUnknown())
{
m_nozzle_diamenter_label->Show(false);
m_nozzle_flowtype_label->SetLabel(_L("Unknown"));
}
else if (extruder_nozzle.IsAbnormal())
{
m_nozzle_diamenter_label->Show(false);
m_nozzle_flowtype_label->SetLabel(_L("Error"));
}
else /*extruder_nozzle.IsNormal()*/
{
m_nozzle_diamenter_label->Show(true);
m_nozzle_diamenter_label->SetLabel(extruder_nozzle.GetNozzleDiameterStr());
m_nozzle_flowtype_label->SetLabel(extruder_nozzle.GetNozzleFlowTypeStr());
}
/* Icon*/
bool extruder_exist = !extruder_nozzle.IsEmpty();
if (m_extruder_nozzle_exist != extruder_exist)
{
m_extruder_nozzle_exist = extruder_exist;
m_filament_color = extruder_nozzle.GetFilamentColor();
m_toolhead_icon->SetBitmap(m_extruder_nozzle_exist ? SetNozzleBmpColor(m_extruder_nozzle_normal->bmp(), m_filament_color) : m_extruder_nozzle_empty->bmp());
m_toolhead_icon->Refresh();
}
}
void wgtDeviceNozzleRackToolHead::Rescale()
{
m_extruder_nozzle_normal->msw_rescale();
m_extruder_nozzle_empty->msw_rescale();
m_toolhead_icon->SetBitmap(m_extruder_nozzle_exist ? SetNozzleBmpColor(m_extruder_nozzle_normal->bmp(), m_filament_color) : m_extruder_nozzle_empty->bmp());
Layout();
Refresh();
}
void wgtDeviceNozzleRackArea::CreateGui()
{
wxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
// Create Header
m_title_nozzle_rack = new wgtDeviceNozzleRackTitle(this, _L("Induction Hotend Rack"));
main_sizer->Add(m_title_nozzle_rack, 0, wxEXPAND | wxTOP);
// Create Simple Book
m_simple_book = new wxSimplebook(this, wxID_ANY);
wxSizer* content_sizer = new wxBoxSizer(wxVERTICAL);
m_panel_content = new wxPanel(m_simple_book, wxID_ANY);
m_panel_refresh = new wxPanel(m_simple_book, wxID_ANY);
// Create Hotends ans Rack Position Panel
wxSizer* hotends_rack_sizer = new wxBoxSizer(wxHORIZONTAL);
// Hotends
m_hotends_sizer = new wxBoxSizer(wxVERTICAL);
m_arow_nozzles_box = CreateNozzleBox( { 0, 2, 4});
m_brow_nozzles_box = CreateNozzleBox( { 1, 3, 5});
m_hotends_sizer->Add(m_arow_nozzles_box);
m_hotends_sizer->Add(m_brow_nozzles_box);
hotends_rack_sizer->Add(m_hotends_sizer, 0, wxLEFT, FromDIP(8));
// Rack
m_rack_pos_panel = new wgtDeviceNozzleRackPos(m_panel_content);
hotends_rack_sizer->Add(m_rack_pos_panel, 0, wxEXPAND);
content_sizer->Add(hotends_rack_sizer, 0);
wxSizer* btn_sizer = new wxBoxSizer(wxHORIZONTAL);
m_btn_hotends_infos = new Button(m_panel_content, _L("Hotends Info"));
m_btn_hotends_infos->SetFont(Label::Body_12);
m_btn_hotends_infos->SetBackgroundColor(s_button_style_gray());
m_btn_hotends_infos->SetBackgroundColour(*wxWHITE);
m_btn_hotends_infos->Bind(wxEVT_BUTTON, &wgtDeviceNozzleRackArea::OnBtnHotendsInfos, this);
m_btn_read_all = new Button(m_panel_content, _L("Read All"));
m_btn_read_all->SetFont(Label::Body_12);
m_btn_read_all->SetBackgroundColor(s_button_style_gray());
m_btn_read_all->SetBackgroundColour(*wxWHITE);
m_btn_read_all->Bind(wxEVT_BUTTON, &wgtDeviceNozzleRackArea::OnBtnReadAll, this);
btn_sizer->Add(m_btn_hotends_infos, 0, wxLEFT);
btn_sizer->Add(m_btn_read_all, 0, wxLEFT, FromDIP(5));
content_sizer->Add(btn_sizer, 0, wxLEFT, FromDIP(10));
/* refresh panel */
wxSizer* refresh_sizer = CreateRefreshBook(m_panel_refresh);
m_panel_content->SetSizer(content_sizer);
m_panel_refresh->SetSizer(refresh_sizer);
m_simple_book->AddPage(m_panel_content, "Content");
m_simple_book->AddPage(m_panel_refresh, "Refresh");
main_sizer->Add(m_simple_book, 1, wxEXPAND);
m_simple_book->SetSelection(0);
SetSizer(main_sizer);
Layout();
Fit();
}
wxSizer* wgtDeviceNozzleRackArea::CreateRefreshBook(wxPanel* parent)
{
wxSizer* refresh_sizer = new wxBoxSizer(wxVERTICAL);
std::vector<std::string> list{"ams_rfid_1", "ams_rfid_2", "ams_rfid_3", "ams_rfid_4"};
m_refresh_icon = new AnimaIcon(parent, wxID_ANY, list, "refresh_printer", 100);
m_refresh_icon->SetMinSize(wxSize(FromDIP(25), FromDIP(25)));
wxSizer* progress_sizer = new wxBoxSizer(wxHORIZONTAL);
Label* progress_prefix = new Label(parent, _L("Reading "));
progress_prefix->SetBackgroundColour(*wxWHITE);
m_progress_refresh = new Label(parent, "(1/6)");
m_progress_refresh->SetFont(Label::Body_14);
m_progress_refresh->SetBackgroundColour(*wxWHITE);
m_progress_refresh->SetForegroundColour(*wxGREEN);
Label* progress_suffix = new Label(parent, " ...");
progress_suffix->SetBackgroundColour(*wxWHITE);
progress_sizer->Add(progress_prefix, 0, wxLEFT);
progress_sizer->Add(m_progress_refresh, 0, wxLEFT);
progress_sizer->Add(progress_suffix, 0, wxLEFT);
Label* refresh_tip = new Label(parent, _L("Please wait"));
refresh_tip->SetBackgroundColour(*wxWHITE);
refresh_sizer->Add(0, 0, 1, wxEXPAND, 0);
refresh_sizer->Add(m_refresh_icon, 0, wxALIGN_CENTER_HORIZONTAL, 0);
refresh_sizer->Add(progress_sizer, 0, wxALIGN_CENTER_HORIZONTAL, FromDIP(0));
refresh_sizer->Add(refresh_tip, 0, wxALIGN_CENTER_HORIZONTAL, FromDIP(0));
refresh_sizer->Add(0, 0, 1, wxEXPAND, 0);
return refresh_sizer;
}
StaticBox* wgtDeviceNozzleRackArea::CreateNozzleBox(const std::vector<int> nozzle_idxes)
{
StaticBox* nozzle_box = new StaticBox(m_panel_content);
nozzle_box->SetBackgroundColor(*wxWHITE);
nozzle_box->SetBorderColor(*wxWHITE);
nozzle_box->SetCornerRadius(0);
wxSizer* h_sizer = new wxBoxSizer(wxHORIZONTAL);
for (auto start_idx : nozzle_idxes)
{
wgtDeviceNozzleRackNozzleItem* nozzle_item = new wgtDeviceNozzleRackNozzleItem(nozzle_box, start_idx);
m_nozzle_items[start_idx] = nozzle_item;
h_sizer->Add(nozzle_item, 0, wxALL, FromDIP(8));
}
nozzle_box->SetSizer(h_sizer);
return nozzle_box;
}
void wgtDeviceNozzleRackArea::UpdateNozzleItems(const std::unordered_map<int, wgtDeviceNozzleRackNozzleItem*>& nozzle_items,
std::shared_ptr<DevNozzleRack> nozzle_rack)
{
for (auto iter : nozzle_items)
{
iter.second->Update(nozzle_rack);
}
/*update nozzle possition and background*/
if (nozzle_rack->GetReadingCount() != 0)
{
m_progress_refresh->SetLabel(wxString::Format("(%d/%d)", nozzle_rack->GetReadingIdx(), nozzle_rack->GetReadingCount()));
if(!m_refresh_icon->IsPlaying()) {
m_simple_book->SetSelection(1);
m_refresh_icon->Play();
}
return;
} else{
m_refresh_icon->Stop();
m_simple_book->SetSelection(0);
}
const DevNozzleRack::RackPos new_pos = nozzle_rack->GetPosition();
const DevNozzleRack::RackStatus new_status = nozzle_rack->GetStatus();
if (m_rack_pos != new_pos || m_rack_status != new_status)
{
m_rack_pos = new_pos;
m_rack_status = new_status;
if (m_rack_status == DevNozzleRack::RACK_STATUS_IDLE)
{
m_hotends_sizer->Clear();
if (m_rack_pos == DevNozzleRack::RACK_POS_B_TOP)
{
m_hotends_sizer->Add(m_brow_nozzles_box);
m_hotends_sizer->Add(m_arow_nozzles_box);
}
else if (m_rack_pos == DevNozzleRack::RACK_POS_A_TOP)
{
m_hotends_sizer->Add(m_arow_nozzles_box);
m_hotends_sizer->Add(m_brow_nozzles_box);
}
else
{
m_hotends_sizer->Add(m_arow_nozzles_box);
m_hotends_sizer->Add(m_brow_nozzles_box);
}
}
}
}
void wgtDeviceNozzleRackArea::UpdateRackInfo(std::weak_ptr<DevNozzleRack> rack)
{
m_nozzle_rack = rack;
const auto& nozzle_rack = rack.lock();
if (nozzle_rack)
{
UpdateNozzleItems(m_nozzle_items, nozzle_rack);
m_rack_pos_panel->UpdateRackPos(nozzle_rack);
m_btn_read_all->Enable(nozzle_rack->CtrlCanReadAll());
}
if (m_rack_upgrade_dlg && m_rack_upgrade_dlg->IsShown())
{
m_rack_upgrade_dlg->UpdateRackInfo(nozzle_rack);
}
};
void wgtDeviceNozzleRackArea::OnBtnHotendsInfos(wxCommandEvent& evt)
{
const auto& nozzle_rack = m_nozzle_rack.lock();
if (nozzle_rack)
{
m_rack_upgrade_dlg = new wgtDeviceNozzleRackUpgradeDlg((wxWindow*)wxGetApp().mainframe, nozzle_rack);
m_rack_upgrade_dlg->ShowModal();
delete m_rack_upgrade_dlg;
m_rack_upgrade_dlg = nullptr;
}
evt.Skip();
}
void wgtDeviceNozzleRackArea::OnBtnReadAll(wxCommandEvent& evt)
{
if (const auto nozzle_rack = m_nozzle_rack.lock())
{
nozzle_rack->CtrlRackReadAll(true);
}
evt.Skip();
}
void wgtDeviceNozzleRackArea::Rescale()
{
for (auto item : m_nozzle_items)
{
item.second->Rescale();
}
m_rack_pos_panel->Rescale();
m_btn_hotends_infos->Rescale();
m_btn_read_all->Rescale();
}
static void s_set_bg_style(StaticBox* box,
ScalableButton* btn,
Label* label_row,
Label* label_row_status,
const wxColour& clr)
{
box->SetBorderColor(clr);
box->SetBackgroundColor(clr);
btn->SetBackgroundColour(clr);
label_row->SetBackgroundColour(clr);
label_row_status->SetBackgroundColour(clr);
}
void wgtDeviceNozzleRackPos::CreateGui()
{
// RowA
m_rowup_panel = new StaticBox(this, wxID_ANY);
m_rowup_panel->SetCornerRadius(0);
wxBoxSizer* rowa_sizer = new wxBoxSizer(wxVERTICAL);
rowa_sizer->AddStretchSpacer();
m_btn_rowup = new ScalableButton(m_rowup_panel, wxID_ANY, "dev_rack_row_up", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, false, 25);
m_btn_rowup->Bind(wxEVT_ENTER_WINDOW, [this](auto&) { SetCursor(wxCURSOR_HAND); });
m_btn_rowup->Bind(wxEVT_LEAVE_WINDOW, [this](auto&) { SetCursor(wxCURSOR_ARROW); });
m_btn_rowup->Bind(wxEVT_BUTTON, &wgtDeviceNozzleRackPos::OnMoveRackUp, this);
rowa_sizer->Add(m_btn_rowup, 0, wxALIGN_CENTER | wxEXPAND | wxLEFT | wxRIGHT, FromDIP(10));
m_label_rowup_status = new Label(m_rowup_panel);
m_label_rowup_status->SetFont(Label::Body_12);
m_label_rowup_status->Show(false);
rowa_sizer->Add(m_label_rowup_status, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, FromDIP(10));
m_label_rowup = new Label(m_rowup_panel);
m_label_rowup->SetFont(Label::Body_14);
rowa_sizer->Add(m_label_rowup, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, FromDIP(10));
rowa_sizer->AddStretchSpacer();
m_rowup_panel->SetSizer(rowa_sizer);
// homing
m_btn_homing = new ScalableButton(this, wxID_ANY, "dev_rack_home", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, false, 25);
m_btn_homing->SetBackgroundColour(WGT_GREY200);
m_btn_homing->Bind(wxEVT_ENTER_WINDOW, [this](auto&) { SetCursor(wxCURSOR_HAND); });
m_btn_homing->Bind(wxEVT_LEAVE_WINDOW, [this](auto&) { SetCursor(wxCURSOR_ARROW); });
m_btn_homing->Bind(wxEVT_BUTTON, &wgtDeviceNozzleRackPos::OnBtnHomingRack, this);
// Row B
m_rowbottom_panel = new StaticBox(this, wxID_ANY);
m_rowbottom_panel->SetCornerRadius(0);
wxBoxSizer* rowb_sizer = new wxBoxSizer(wxVERTICAL);
rowb_sizer->AddStretchSpacer();
m_btn_rowbottom_up = new ScalableButton(m_rowbottom_panel, wxID_ANY, "dev_rack_row_up", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, false, 25);
m_btn_rowbottom_up->Bind(wxEVT_BUTTON, &wgtDeviceNozzleRackPos::OnMoveRackDown, this);
m_btn_rowbottom_up->Bind(wxEVT_ENTER_WINDOW, [this](auto&) { SetCursor(wxCURSOR_HAND); });
m_btn_rowbottom_up->Bind(wxEVT_LEAVE_WINDOW, [this](auto&) { SetCursor(wxCURSOR_ARROW); });
rowb_sizer->Add(m_btn_rowbottom_up, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, FromDIP(10));
m_label_rowbottom_status = new Label(m_rowbottom_panel);
m_label_rowbottom_status->SetFont(Label::Body_12);
m_label_rowbottom_status->Show(false);
rowb_sizer->Add(m_label_rowbottom_status, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, FromDIP(10));
m_label_rowbottom = new Label(m_rowbottom_panel);
m_label_rowbottom->SetFont(Label::Body_14);
rowb_sizer->Add(m_label_rowbottom, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, FromDIP(10));
rowb_sizer->AddStretchSpacer();
m_rowbottom_panel->SetSizer(rowb_sizer);
// bg style
SetBackgroundColour(*wxWHITE);
s_set_bg_style(m_rowup_panel, m_btn_rowup, m_label_rowup, m_label_rowup_status, *wxWHITE);
s_set_bg_style(m_rowbottom_panel, m_btn_rowbottom_up, m_label_rowbottom, m_label_rowbottom_status, *wxWHITE);
// main sizer
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
main_sizer->Add(m_rowup_panel, 1, wxALIGN_TOP | wxEXPAND | wxALIGN_CENTER);
main_sizer->Add(m_btn_homing, 0, wxALIGN_CENTER | wxTOP | wxBOTTOM, FromDIP(10));
main_sizer->Add(m_rowbottom_panel, 1, wxALIGN_BOTTOM | wxEXPAND | wxALIGN_CENTER);
SetSizer(main_sizer);
SetMinSize(WX_DIP_SIZE(85, -1));
Layout();
Fit();
}
void wgtDeviceNozzleRackPos::UpdateRackPos(const std::shared_ptr<DevNozzleRack>& rack)
{
m_rack = rack;
if (rack)
{
UpdateRackPos(rack->GetPosition(), rack->GetStatus(), rack->GetReadingCount() > 0);
}
}
static void s_show_label(Label* label, const wxString& text)
{
label->SetLabel(text);
label->Show();
}
static void s_show_label(Label* label, const wxString& text, const wxColour& text_color)
{
label->SetLabel(text);
label->SetForegroundColour(StateColor::darkModeColorFor(text_color));
label->Show();
}
void wgtDeviceNozzleRackPos::UpdateRackPos(DevNozzleRack::RackPos new_pos,
DevNozzleRack::RackStatus new_status, bool is_reading)
{
// While reading, both rows show a "Running..." status and the move buttons are hidden.
if (is_reading)
{
s_show_label(m_label_rowup, L_RAW_A_STR, *wxBLACK);
s_show_label(m_label_rowup_status, _L("Running..."));
s_show_label(m_label_rowbottom, L_RAW_B_STR, *wxBLACK);
s_show_label(m_label_rowbottom_status, _L("Running..."));
m_btn_rowup->Show(false);
m_btn_rowbottom_up->Show(false);
m_rack_pos = DevNozzleRack::RACK_POS_UNKNOWN;
m_rack_status = DevNozzleRack::RACK_STATUS_UNKNOWN;
return;
}
if (new_pos != m_rack_pos || m_rack_status != new_status)
{
m_rack_pos = new_pos;
m_rack_status = new_status;
if (m_rack_status != DevNozzleRack::RACK_STATUS_IDLE)
{
s_show_label(m_label_rowup, L_RAW_A_STR, *wxBLACK);
s_show_label(m_label_rowup_status, _L("Running..."));
s_show_label(m_label_rowbottom, L_RAW_B_STR, *wxBLACK);
s_show_label(m_label_rowbottom_status, _L("Running..."));
m_btn_rowup->Show(false);
m_btn_rowbottom_up->Show(false);
}
else
{
if (new_pos == DevNozzleRack::RACK_POS_A_TOP)
{
s_show_label(m_label_rowup, L_RAW_A_STR, s_hgreen_clr);
s_show_label(m_label_rowup_status, _L("Raised"));
m_rowbottom_panel->SetBorderColor(*wxWHITE);
m_rowbottom_panel->SetBackgroundColor(*wxWHITE);
s_show_label(m_label_rowbottom, L_RAW_B_STR, *wxBLACK);
m_label_rowbottom_status->Show(false);
m_btn_rowup->Show(false);
m_btn_rowbottom_up->Show(true);
}
else if (new_pos == DevNozzleRack::RACK_POS_B_TOP)
{
s_show_label(m_label_rowup, L_RAW_B_STR, s_hgreen_clr);
s_show_label(m_label_rowup_status, _L("Raised"));
s_show_label(m_label_rowbottom, L_RAW_A_STR, *wxBLACK);
m_label_rowbottom_status->Show(false);
m_btn_rowup->Show(false);
m_btn_rowbottom_up->Show(true);
}
else
{
s_show_label(m_label_rowup, L_RAW_A_STR, *wxBLACK);
m_label_rowup_status->Show(false);
s_show_label(m_label_rowbottom, L_RAW_B_STR, *wxBLACK);
m_label_rowbottom_status->Show(false);
m_btn_rowup->Show(true);
m_btn_rowbottom_up->Show(true);
}
}
Layout();
Refresh();
}
};
void wgtDeviceNozzleRackPos::OnMoveRackUp(wxCommandEvent& evt)
{
auto rack = m_rack.lock();
if (rack)
{
if (m_label_rowup->GetLabel() == L_RAW_A_STR)
{
rack->CtrlRackPosMove(DevNozzleRack::RACK_POS_A_TOP);
}
else if (m_label_rowup->GetLabel() == L_RAW_B_STR)
{
rack->CtrlRackPosMove(DevNozzleRack::RACK_POS_B_TOP);
}
}
evt.Skip();
}
void wgtDeviceNozzleRackPos::OnMoveRackDown(wxCommandEvent& evt)
{
auto rack = m_rack.lock();
if (rack)
{
if (m_label_rowbottom->GetLabel() == L_RAW_A_STR)
{
rack->CtrlRackPosMove(DevNozzleRack::RACK_POS_A_TOP);
}
else if (m_label_rowbottom->GetLabel() == L_RAW_B_STR)
{
rack->CtrlRackPosMove(DevNozzleRack::RACK_POS_B_TOP);
}
}
evt.Skip();
}
void wgtDeviceNozzleRackPos::OnBtnHomingRack(wxCommandEvent& evt)
{
if (auto rack = m_rack.lock())
{
rack->CtrlRackPosGoHome();
}
evt.Skip();
}
void wgtDeviceNozzleRackPos::Rescale()
{
m_btn_rowup->msw_rescale();
m_btn_rowbottom_up->msw_rescale();
m_btn_homing->msw_rescale();
}
};// end of namespace Slic3r::GUI
@@ -0,0 +1,196 @@
//**********************************************************/
/* File: wgtDeviceNozzleRack.h
* Description: The Device-tab panel with the toolhead nozzle and the H2C induction hotend rack.
*
* \n class wgtDeviceNozzleRack; // toolhead panel + rack area, side by side
* \n class wgtDeviceNozzleRackToolHead;
* \n class wgtDeviceNozzleRackArea;
* \n class wgtDeviceNozzleRackPos;
*
* The single-nozzle tile class wgtDeviceNozzleRackNozzleItem lives in its own translation unit
* (wgtDeviceNozzleRackNozzleItem.{h,cpp}), so it is included here rather than redeclared.
* The Hotends-Info upgrade dialog (wgtDeviceNozzleRackUpgradeDlg) is intentionally not wired yet
* (see the TODO markers in the .cpp).
//**********************************************************/
#pragma once
#include "slic3r/GUI/DeviceCore/DevNozzleRack.h"
#include "wgtDeviceNozzleRackNozzleItem.h"
#include "slic3r/GUI/Widgets/StaticBox.hpp"
#include "slic3r/GUI/Widgets/AnimaController.hpp"
#include <wx/panel.h>
#include <wx/simplebook.h>
#include <memory>
#include <unordered_map>
#include <vector>
// Previous definitions
class Button;
class Label;
class ScalableBitmap;
class ScalableButton;
namespace Slic3r
{
struct DevNozzle;
class DevNozzleRack;
namespace GUI
{
class wgtDeviceNozzleRackArea;
class wgtDeviceNozzleRackNozzleItem;
class wgtDeviceNozzleRackToolHead;
class wgtDeviceNozzleRackPos;
class wgtDeviceNozzleRackTitle;
class wgtDeviceNozzleRackUpgradeDlg;
}
};
namespace Slic3r::GUI
{
class wgtDeviceNozzleRack : public wxPanel
{
public:
wgtDeviceNozzleRack(wxWindow* parent,
wxWindowID id = wxID_ANY,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxTAB_TRAVERSAL);
~wgtDeviceNozzleRack() = default;
public:
void UpdateRackInfo(std::shared_ptr<DevNozzleRack> rack);
void Rescale();
private:
void CreateGui();
private:
std::weak_ptr<DevNozzleRack> m_nozzle_rack;
// GUI
wgtDeviceNozzleRackToolHead* m_toolhead_panel{ nullptr };
wgtDeviceNozzleRackArea* m_rack_area{ nullptr };
};
class wgtDeviceNozzleRackToolHead : public wxPanel
{
public:
wgtDeviceNozzleRackToolHead(wxWindow* parent) : wxPanel(parent) { CreateGui();}
public:
void UpdateToolHeadInfo(const DevNozzle& extruder_nozzle);
void Rescale();
private:
void CreateGui();
private:
bool m_extruder_nozzle_exist = false;
std::string m_filament_color;
// GUI
ScalableBitmap* m_extruder_nozzle_normal = nullptr;
ScalableBitmap* m_extruder_nozzle_empty = nullptr;
wxStaticBitmap* m_toolhead_icon;
Label* m_nozzle_diamenter_label;
Label* m_nozzle_flowtype_label;
};
class wgtDeviceNozzleRackArea : public wxPanel
{
public:
wgtDeviceNozzleRackArea(wxWindow* parent) : wxPanel(parent) { CreateGui();}
public:
void UpdateRackInfo(std::weak_ptr<DevNozzleRack> rack);
void Rescale();
private:
void CreateGui();
StaticBox* CreateNozzleBox(const std::vector<int> nozzle_idxes);
wxSizer* CreateRefreshBook(wxPanel* parent);
// updates
void UpdateNozzleItems(const std::unordered_map<int, wgtDeviceNozzleRackNozzleItem*>& nozzle_items,
std::shared_ptr<DevNozzleRack> nozzle_rack);
// events
void OnBtnHotendsInfos(wxCommandEvent& evt);
void OnBtnReadAll(wxCommandEvent& evt);
private:
std::weak_ptr<DevNozzleRack> m_nozzle_rack;
DevNozzleRack::RackPos m_rack_pos = DevNozzleRack::RACK_POS_UNKNOWN;
DevNozzleRack::RackStatus m_rack_status = DevNozzleRack::RACK_STATUS_UNKNOWN;
// GUI
wxSimplebook* m_simple_book{ nullptr };
wxPanel* m_panel_content{ nullptr };
wxPanel* m_panel_refresh{ nullptr };
wgtDeviceNozzleRackTitle* m_title_nozzle_rack;
wxBoxSizer* m_hotends_sizer;
StaticBox* m_arow_nozzles_box;
StaticBox* m_brow_nozzles_box;
std::unordered_map<int, wgtDeviceNozzleRackNozzleItem*> m_nozzle_items;
wgtDeviceNozzleRackPos* m_rack_pos_panel;
Button* m_btn_hotends_infos;
Button* m_btn_read_all;
/* refresh book */
Label* m_progress_refresh{ nullptr };
AnimaIcon* m_refresh_icon{ nullptr };
// "Hotends Info" upgrade dialog. Owned for its ShowModal lifetime by
// OnBtnHotendsInfos; UpdateRackInfo forwards live device pushes to it while it is shown.
wgtDeviceNozzleRackUpgradeDlg* m_rack_upgrade_dlg = nullptr;
};
class wgtDeviceNozzleRackPos : public wxPanel
{
public:
explicit wgtDeviceNozzleRackPos(wxWindow* parent) : wxPanel(parent) { CreateGui();}
public:
void UpdateRackPos(const std::shared_ptr<DevNozzleRack>& rack);
void Rescale();
private:
void CreateGui();
void UpdateRackPos(DevNozzleRack::RackPos new_pos,
DevNozzleRack::RackStatus new_status,
bool is_reading);
// events
void OnMoveRackUp(wxCommandEvent& evt);
void OnMoveRackDown(wxCommandEvent& evt);
void OnBtnHomingRack(wxCommandEvent& evt);
private:
std::weak_ptr<DevNozzleRack> m_rack;
DevNozzleRack::RackPos m_rack_pos = DevNozzleRack::RACK_POS_UNKNOWN;
DevNozzleRack::RackStatus m_rack_status = DevNozzleRack::RACK_STATUS_UNKNOWN;
// GUI
StaticBox* m_rowup_panel;
ScalableButton* m_btn_rowup;
Label* m_label_rowup_status{ nullptr };
Label* m_label_rowup{ nullptr };
StaticBox* m_rowbottom_panel;
ScalableButton* m_btn_rowbottom_up;
Label* m_label_rowbottom_status{ nullptr };
Label* m_label_rowbottom{ nullptr };
ScalableButton* m_btn_homing{ nullptr };
};
};// end of namespace Slic3r::GUI
@@ -0,0 +1,347 @@
//**********************************************************/
/* File: wgtDeviceNozzleRackNozzleItem.cpp
* Description: One nozzle cell of the H2C hotend rack view. Holds the
* wgtDeviceNozzleRackNozzleItem widget, the SetNozzleBmpColor helper and the layout
* constants it needs.
//**********************************************************/
#include "wgtDeviceNozzleRackNozzleItem.h"
#include "slic3r/GUI/DeviceCore/DevNozzleSystem.h"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/MsgDialog.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/wxExtensions.hpp"
#include "slic3r/GUI/Widgets/Label.hpp"
#define WX_DIP_SIZE_46 wxSize(FromDIP(46), FromDIP(46))
#define WX_DIP_SIZE(x, y) wxSize(FromDIP(x), FromDIP(y))
#define WGT_RACK_NOZZLE_SIZE WX_DIP_SIZE(88, 100)
static wxColour s_gray_clr("#B0B0B0");
static wxColour s_hgreen_clr("#00AE42");
static wxColour s_red_clr("#D01B1B");
wxDEFINE_EVENT(EVT_NOZZLE_RACK_NOZZLE_ITEM_SELECTED, wxCommandEvent);
namespace Slic3r::GUI
{
static wxBitmap SetNozzleBmpColor(const wxBitmap& bmp, const std::string& color_str) {
if(color_str.empty()) return bmp;
wxImage img = bmp.ConvertToImage();
wxColour color("#" + color_str);
for (int y = 0; y < img.GetHeight(); ++y) {
for (int x = 0; x < img.GetWidth(); ++x) {
unsigned char r = img.GetRed(x, y);
unsigned char g = img.GetGreen(x, y);
unsigned char b = img.GetBlue(x, y);
/*replace yellow with color*/
if ( r >= 180 && g >= 180 && b <= 150) {
img.SetRGB(x, y, color.Red(), color.Green(), color.Blue());
}
}
}
return wxBitmap(img, -1, bmp.GetScaleFactor());
}
wgtDeviceNozzleRackNozzleItem::wgtDeviceNozzleRackNozzleItem(wxWindow* parent, int nozzle_id)
: StaticBox(parent, wxID_ANY), m_nozzle_id(nozzle_id)
{
CreateGui();
}
void wgtDeviceNozzleRackNozzleItem::CreateGui()
{
// Background
SetCornerRadius(FromDIP(5));
SetBackgroundColor(*wxWHITE);
// Top H
wxSizer *top_h_sizer = new wxBoxSizer(wxHORIZONTAL);
m_nozzle_label_id = new Label(this);
m_nozzle_label_id->SetFont(Label::Body_12);
m_nozzle_label_id->SetBackgroundColour(*wxWHITE);
m_nozzle_label_id->SetLabel(wxString::Format("%d", m_nozzle_id + 1));
m_status = NOZZLE_STATUS::NOZZLE_EMPTY;
m_nozzle_empty_image = new ScalableBitmap(this, "dev_rack_nozzle_empty", 46);
m_nozzle_icon = new wxStaticBitmap(this, wxID_ANY, m_nozzle_empty_image->bmp(), wxDefaultPosition, WX_DIP_SIZE_46);
m_nozzle_icon->SetBackgroundColour(*wxWHITE);
m_nozzle_selected_bitmap = new wxStaticBitmap(this, wxID_ANY, wxNullBitmap, wxDefaultPosition, WX_DIP_SIZE(20, 20));
m_nozzle_selected_bitmap->SetBackgroundColour(*wxWHITE);
top_h_sizer->Add(m_nozzle_label_id, 0, wxTOP | wxLEFT, FromDIP(6));
top_h_sizer->AddStretchSpacer(1);
top_h_sizer->Add(m_nozzle_icon, 0, wxTOP, FromDIP(10));
top_h_sizer->AddStretchSpacer(1);
top_h_sizer->Add(m_nozzle_selected_bitmap, 0, wxTOP | wxRIGHT, FromDIP(2));
// Bottom V
wxBoxSizer* bottom_v = new wxBoxSizer(wxVERTICAL);
wxSizer* label_h_sizer = new wxBoxSizer(wxHORIZONTAL);
m_nozzle_label_1 = new Label(this);
m_nozzle_label_1->SetFont(Label::Body_12);
m_nozzle_label_1->SetBackgroundColour(*wxWHITE);
m_nozzle_label_1->SetLabel(_L("Empty"));
label_h_sizer->Add(m_nozzle_label_1, 0, wxALIGN_LEFT);
auto status_icon = create_scaled_bitmap("dev_rack_nozzle_error_icon", this, 14);
m_nozzle_status_icon = new wxStaticBitmap(this, wxID_ANY, status_icon, wxDefaultPosition, WX_DIP_SIZE(14, 14));
m_nozzle_status_icon->Bind(wxEVT_LEFT_DOWN, &wgtDeviceNozzleRackNozzleItem::OnBtnNozzleStatus, this);
m_nozzle_status_icon->Bind(wxEVT_ENTER_WINDOW, [this](auto&) { SetCursor(wxCURSOR_HAND); });
m_nozzle_status_icon->Bind(wxEVT_LEAVE_WINDOW, [this](auto&) { SetCursor(wxCURSOR_ARROW); });
m_nozzle_status_icon->SetBackgroundColour(*wxWHITE);
m_nozzle_status_icon->Show(false);
label_h_sizer->Add(m_nozzle_status_icon, 0, wxALIGN_CENTER | wxLEFT, FromDIP(2));
bottom_v->Add(label_h_sizer, 0, wxALIGN_CENTER_HORIZONTAL | wxTOP, FromDIP(2));
m_nozzle_label_2 = new Label(this);
m_nozzle_label_2->SetFont(Label::Body_12);
m_nozzle_label_2->SetBackgroundColour(*wxWHITE);
bottom_v->Add(m_nozzle_label_2, 0, wxALIGN_CENTER_HORIZONTAL);
// Main sizer
wxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
main_sizer->Add(top_h_sizer, 0, wxEXPAND);
main_sizer->Add(bottom_v, 0, wxALIGN_CENTER_HORIZONTAL);
SetSizer(main_sizer);
SetMinSize(WGT_RACK_NOZZLE_SIZE);
SetMaxSize(WGT_RACK_NOZZLE_SIZE);
SetSize(WGT_RACK_NOZZLE_SIZE);
Layout();
};
void wgtDeviceNozzleRackNozzleItem::SetSelected(bool selected)
{
if (!m_enable_select){
assert(false && "not support select");
return;
}
if (m_is_selected != selected) {
m_is_selected = selected;
if (selected) {
if (!m_nozzle_selected_image) {
m_nozzle_selected_image = new ScalableBitmap(this, "dev_rack_nozzle_selected", 20);
}
m_nozzle_selected_bitmap->SetBitmap(m_nozzle_selected_image->bmp());
SetBorderColor(StateColor::darkModeColorFor(s_hgreen_clr));
} else {
m_nozzle_selected_bitmap->SetBitmap(wxNullBitmap);
SetBorderColor(StateColor::darkModeColorFor(s_gray_clr));
}
Refresh();
}
}
void wgtDeviceNozzleRackNozzleItem::Update(const std::shared_ptr<DevNozzleRack> rack, bool on_rack /*= true*/)
{
m_rack = rack;
if (rack) {
const auto &nozzle_info = on_rack ? rack->GetNozzle(m_nozzle_id) : rack->GetNozzleSystem()->GetExtNozzle(m_nozzle_id);
const wxString &diameter_str = nozzle_info.GetNozzleDiameterStr();
const wxString &flowtype_str = nozzle_info.GetNozzleFlowTypeStr();
const std::string &color = nozzle_info.GetFilamentColor();
/*check empty first*/
if (nozzle_info.IsEmpty()) {
SetNozzleStatus(NOZZLE_STATUS::NOZZLE_EMPTY, _L("Empty"), wxEmptyString, color);
} else if (nozzle_info.IsNormal()) {
SetNozzleStatus(NOZZLE_STATUS::NOZZLE_NORMAL, diameter_str, flowtype_str, color);
} else if (nozzle_info.IsAbnormal()) {
SetNozzleStatus(NOZZLE_STATUS::NOZZLE_ERROR, _L("Error"), wxEmptyString, color);
} else if (nozzle_info.IsUnknown()) {
SetNozzleStatus(NOZZLE_STATUS::NOZZLE_UNKNOWN, _L("Unknown"), wxEmptyString, color);
}
}
}
void wgtDeviceNozzleRackNozzleItem::SetNozzleStatus(NOZZLE_STATUS status, const wxString& str1, const wxString& str2, const std::string& color)
{
if (m_status != status || m_filament_color != color)
{
m_status = status;
m_filament_color = color;
switch (status)
{
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_EMPTY:
{
if (!m_nozzle_empty_image) { m_nozzle_empty_image = new ScalableBitmap(this, "dev_rack_nozzle_empty", 46);}
m_nozzle_icon->SetBitmap(m_nozzle_empty_image->bmp());
break;
}
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_NORMAL:
{
if (!m_nozzle_normal_image) { m_nozzle_normal_image = new ScalableBitmap(this, "dev_rack_nozzle_normal", 46);}
m_nozzle_icon->SetBitmap(SetNozzleBmpColor(m_nozzle_normal_image->bmp(), m_filament_color));
break;
}
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_UNKNOWN:
{
if (!m_nozzle_unknown_image) { m_nozzle_unknown_image = new ScalableBitmap(this, "dev_rack_nozzle_unknown", 46);}
m_nozzle_icon->SetBitmap(m_nozzle_unknown_image->bmp());
break;
}
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_ERROR:
{
if (!m_nozzle_error_image) { m_nozzle_error_image = new ScalableBitmap(this, "dev_rack_nozzle_error", 46);}
m_nozzle_icon->SetBitmap(m_nozzle_error_image->bmp());
break;
}
default:
{
break;
}
}
if (status == wgtDeviceNozzleRackNozzleItem::NOZZLE_ERROR)
{
m_nozzle_label_1->SetForegroundColour(StateColor::darkModeColorFor(s_red_clr));
m_nozzle_status_icon->Show(true);
}
else
{
m_nozzle_label_1->SetForegroundColour(StateColor::darkModeColorFor(*wxBLACK));
m_nozzle_status_icon->Show(false);
}
}
bool update_layout = (m_nozzle_label_1->GetLabel() != str1 || m_nozzle_label_2->GetLabel() != str2);
m_nozzle_label_1->SetLabel(str1);
m_nozzle_label_2->SetLabel(str2);
if (update_layout) {
Layout();
}
}
void wgtDeviceNozzleRackNozzleItem::OnBtnNozzleStatus(wxMouseEvent& evt)
{
if (m_is_disabled) {
return;
}
auto rack = m_rack.lock();
if (rack && m_status == wgtDeviceNozzleRackNozzleItem::NOZZLE_ERROR)
{
// Orca: show the abnormal-hotend warning as an informational dialog only, with no
// "Jump to the upgrade page" button, since Orca's MessageDialog and device Upgrade UI
// have no such entry point. Fires when tapping an error-state rack nozzle's status icon.
MessageDialog dlg(nullptr, _L("The hotend is in an abnormal state and currently unavailable. "
"Please go to 'Device -> Upgrade' to upgrade firmware."), _L("Abnormal Hotend"), wxICON_WARNING | wxOK);
dlg.ShowModal();
}
}
void wgtDeviceNozzleRackNozzleItem::Rescale()
{
if (m_nozzle_normal_image) { m_nozzle_normal_image->msw_rescale(); }
if (m_nozzle_empty_image) { m_nozzle_empty_image->msw_rescale(); }
if (m_nozzle_unknown_image) { m_nozzle_unknown_image->msw_rescale(); }
if (m_nozzle_error_image) { m_nozzle_error_image->msw_rescale(); }
auto status_icon = create_scaled_bitmap("dev_rack_nozzle_error_icon", this, 14);
m_nozzle_status_icon->SetBitmap(status_icon);
m_nozzle_status_icon->Refresh();
if (m_nozzle_selected_image) {
m_nozzle_selected_image->msw_rescale();
if (m_is_selected) {
m_nozzle_selected_bitmap->SetBitmap(m_nozzle_selected_image->bmp());
}
};
switch (m_status)
{
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_EMPTY:
{
m_nozzle_icon->SetBitmap(m_nozzle_empty_image->bmp());
break;
}
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_NORMAL:
{
m_nozzle_icon->SetBitmap(SetNozzleBmpColor(m_nozzle_normal_image->bmp(), m_filament_color));
break;
}
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_UNKNOWN:
{
m_nozzle_icon->SetBitmap(m_nozzle_unknown_image->bmp());
break;
}
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_ERROR:
{
m_nozzle_icon->SetBitmap(m_nozzle_error_image->bmp());
break;
}
default:
{
break;
}
};
};
void wgtDeviceNozzleRackNozzleItem::EnableSelect()
{
if (m_enable_select == true) {
return;
};
m_enable_select = true;
m_nozzle_icon->Bind(wxEVT_LEFT_DOWN, [this](auto& evt) { OnItemSelected(evt); });
m_nozzle_label_id->Bind(wxEVT_LEFT_DOWN, [this](auto& evt) { OnItemSelected(evt); });
m_nozzle_label_1->Bind(wxEVT_LEFT_DOWN, [this](auto& evt) { OnItemSelected(evt); });
m_nozzle_label_2->Bind(wxEVT_LEFT_DOWN, [this](auto& evt) { OnItemSelected(evt); });
Bind(wxEVT_LEFT_DOWN, [this](auto& evt) { OnItemSelected(evt); });
}
void wgtDeviceNozzleRackNozzleItem::OnItemSelected(wxMouseEvent& evt)
{
if (m_enable_select && !m_is_disabled){
SetSelected(true);
wxCommandEvent command_evt(EVT_NOZZLE_RACK_NOZZLE_ITEM_SELECTED, GetId());
command_evt.SetEventObject(this);
ProcessEvent(command_evt);
}
evt.Skip();
}
void wgtDeviceNozzleRackNozzleItem::SetDisable(bool disabled)
{
if (m_is_disabled == disabled) {
return;
}
m_is_disabled = disabled;
auto bg_clr = disabled ? StateColor::darkModeColorFor("#E5E7EB") : StateColor::darkModeColorFor(*wxWHITE);
m_nozzle_icon->SetBackgroundColour(bg_clr);
m_nozzle_label_id->SetBackgroundColour(bg_clr);
m_nozzle_label_1->SetBackgroundColour(bg_clr);
m_nozzle_status_icon->SetBackgroundColour(bg_clr);
m_nozzle_label_2->SetBackgroundColour(bg_clr);
m_nozzle_selected_bitmap->SetBackgroundColour(bg_clr);
SetBackgroundColor(bg_clr);
Refresh();
};
};// end of namespace Slic3r::GUI
@@ -0,0 +1,99 @@
//**********************************************************/
/* File: wgtDeviceNozzleRackNozzleItem.h
* Description: One nozzle cell of the H2C hotend rack view.
*
* This header defines only the single-nozzle-cell widget (and the selection event it emits), which
* is the one dependency MultiNozzleSync's HotEndTable needs. The rest of the Device-tab rack panel
* (wgtDeviceNozzleRack / ...Area / ...ToolHead / ...Pos) is not provided here.
//**********************************************************/
#pragma once
#include "slic3r/GUI/DeviceCore/DevNozzleRack.h"
#include "slic3r/GUI/Widgets/StaticBox.hpp"
#include "slic3r/GUI/Widgets/Label.hpp" // complete type required by the inline SetDisplayIdText below
#include <wx/panel.h>
#include <memory>
// Previous definitions
class ScalableBitmap;
namespace Slic3r
{
struct DevNozzle;
class DevNozzleRack;
};
// Events
wxDECLARE_EVENT(EVT_NOZZLE_RACK_NOZZLE_ITEM_SELECTED, wxCommandEvent);
namespace Slic3r::GUI
{
class wgtDeviceNozzleRackNozzleItem : public StaticBox
{
public:
enum NOZZLE_STATUS
{
NOZZLE_EMPTY,
NOZZLE_NORMAL,
NOZZLE_UNKNOWN,
NOZZLE_ERROR
};
public:
wgtDeviceNozzleRackNozzleItem(wxWindow* parent, int nozzle_id);
public:
void Update(const std::shared_ptr<DevNozzleRack> rack, bool on_rack = true); // on_rack is false means extruder nozzle
int GetNozzleId() const { return m_nozzle_id; }
void SetDisplayIdText(const wxString& text) { m_nozzle_label_id->SetLabel(text);};
void EnableSelect();;
void SetSelected(bool selected);
bool IsSelected() const { return m_is_selected; }
bool IsDisabled() const { return m_is_disabled; }
void SetDisable(bool disabled);
void Rescale();
private:
void CreateGui();
void SetNozzleStatus(NOZZLE_STATUS status, const wxString& str1, const wxString& str2, const std::string& color);
void OnBtnNozzleStatus(wxMouseEvent& evt);
void OnItemSelected(wxMouseEvent& evt);
private:
std::weak_ptr<DevNozzleRack> m_rack;
int m_nozzle_id; // internal id, from 0 to 5
std::string m_filament_color;
NOZZLE_STATUS m_status = NOZZLE_STATUS::NOZZLE_EMPTY;
// select
bool m_is_selected = false;
bool m_enable_select = false;
ScalableBitmap* m_nozzle_selected_image{ nullptr };
wxStaticBitmap* m_nozzle_selected_bitmap{ nullptr };
// enable or disable
bool m_is_disabled = false;
// Images
ScalableBitmap* m_nozzle_normal_image{ nullptr };
ScalableBitmap* m_nozzle_empty_image{ nullptr };
ScalableBitmap* m_nozzle_unknown_image{ nullptr };
ScalableBitmap* m_nozzle_error_image{ nullptr };
// GUI
wxStaticBitmap* m_nozzle_icon{ nullptr };
Label* m_nozzle_label_id { nullptr };
Label* m_nozzle_label_1{ nullptr };
wxStaticBitmap* m_nozzle_status_icon = nullptr;
Label* m_nozzle_label_2{ nullptr };
};
};// end of namespace Slic3r::GUI
@@ -0,0 +1,696 @@
//**********************************************************/
/* File: wgtDeviceNozzleRackUpdate.cpp
* Description: The dialog for reading/upgrading the H2C induction-hotend-rack firmware.
*
* \n class wgtDeviceNozzleRackUpdate
//**********************************************************/
#include "wgtDeviceNozzleRackUpdate.h"
#include "slic3r/GUI/DeviceCore/DevNozzleSystem.h"
#include "slic3r/GUI/MainFrame.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/MsgDialog.hpp"
#include "slic3r/GUI/wxExtensions.hpp"
#include "slic3r/GUI/Widgets/Button.hpp"
#include "slic3r/GUI/Widgets/Label.hpp"
#define WX_DIP_SIZE(x, y) wxSize(FromDIP(x), FromDIP(y))
static wxColour s_red_clr("#D01B1B");
namespace Slic3r::GUI
{
wxDEFINE_EVENT(wxEVT_NOZZLE_JUMP_UPGRADE, wxCommandEvent);
wgtDeviceNozzleRackUpgradeDlg::wgtDeviceNozzleRackUpgradeDlg(wxWindow* parent, const std::shared_ptr<DevNozzleRack> rack)
: DPIDialog(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX)
{
m_rack_upgrade_panel = new wgtDeviceNozzleRackUprade(this);
m_rack_upgrade_panel->UpdateRackInfo(rack);
Bind(wxEVT_NOZZLE_JUMP_UPGRADE, [this](wxCommandEvent&) {
EndModal(wxID_OK);
});
auto main_sizer = new wxBoxSizer(wxVERTICAL);
main_sizer->Add(m_rack_upgrade_panel, 0, wxEXPAND);
SetSizer(main_sizer);
Layout();
Fit();
wxGetApp().UpdateDlgDarkUI(this);
}
void wgtDeviceNozzleRackUpgradeDlg::UpdateRackInfo(const std::shared_ptr<DevNozzleRack> rack)
{
m_rack_upgrade_panel->UpdateRackInfo(rack);
}
void wgtDeviceNozzleRackUpgradeDlg::on_dpi_changed(const wxRect& suggested_rect)
{
m_rack_upgrade_panel->Rescale();
}
wgtDeviceNozzleRackUprade::wgtDeviceNozzleRackUprade(wxWindow* parent,
wxWindowID id,
const wxPoint& pos,
const wxSize& size,
long style)
: wxPanel(parent, id, pos, size, style)
{
CreateGui();
}
void wgtDeviceNozzleRackUprade::CreateGui()
{
SetBackgroundColour(*wxWHITE);
// Main vertical sizer
auto* main_sizer = new wxBoxSizer(wxVERTICAL);
// Header: title + buttons
auto* header_sizer = new wxBoxSizer(wxHORIZONTAL);
// Title label
auto* title_label = new Label(this, _L("Hotends Info"));
title_label->SetFont(Label::Head_14);
header_sizer->Add(title_label, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(20));
// Spacer
header_sizer->AddStretchSpacer();
main_sizer->Add(header_sizer, 0, wxEXPAND | wxTOP | wxRIGHT, FromDIP(10));
// "Nozzles"
m_extruder_nozzle_item = new wgtDeviceNozzleRackHotendUpdate(this, "R");
m_extruder_nozzle_item->UpdateColourStyle(wxColour("#F8F8F8"));
m_extruder_nozzle_item->SetExtruderNozzleId(MAIN_EXTRUDER_ID);
main_sizer->Add(m_extruder_nozzle_item, 0, wxEXPAND | wxALL, FromDIP(12));
for (int id = 0; id < 6; id ++)
{
auto item = new wgtDeviceNozzleRackHotendUpdate(this, wxString::Format("%d", id + 1));
item->SetRackNozzleId(id);
m_nozzle_items[id] = item;
main_sizer->Add(item, 0, wxEXPAND | wxALL, FromDIP(12));
if (id < 5)
{
wxPanel* separator = new wxPanel(this);
separator->SetMaxSize(wxSize(-1, FromDIP(1)));
separator->SetMinSize(wxSize(-1, FromDIP(1)));
separator->SetBackgroundColour(wxColour(238, 238, 238));// Orca: grey300 spelled out as a literal RGB, since Orca has no equivalent grey-300 colour macro
main_sizer->Add(separator, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(12));
}
}
main_sizer->AddSpacer(FromDIP(20));
// Set sizer
this->SetSizer(main_sizer);
this->Layout();
}
void wgtDeviceNozzleRackUprade::UpdateRackInfo(const std::shared_ptr<DevNozzleRack> rack)
{
m_nozzle_rack = rack;
if (!rack) { return;}
// update the nozzles
m_extruder_nozzle_item->UpdateExtruderNozzleInfo(rack);
for (auto iter : m_nozzle_items)
{
iter.second->UpdateRackNozzleInfo(rack);
}
// update layout
Layout();
}
void wgtDeviceNozzleRackUprade::OnBtnReadAll(wxCommandEvent& e)
{
if (auto rack = m_nozzle_rack.lock())
{
rack->CtrlRackReadAll(true);
}
}
void wgtDeviceNozzleRackUprade::Rescale()
{
m_extruder_nozzle_item->Rescale();
for (auto& iter : m_nozzle_items)
{
iter.second->Rescale();
}
}
#define WGT_DEVICE_NOZZLE_RACK_HOTEND_UPDATE_DEFAULT_BG *wxWHITE
wgtDeviceNozzleRackHotendUpdate::wgtDeviceNozzleRackHotendUpdate(wxWindow* parent, const wxString& idx_text)
: StaticBox(parent, wxID_ANY)
{
CreateGui();
m_idx_label->SetLabel(idx_text);
}
void wgtDeviceNozzleRackHotendUpdate::CreateGui()
{
SetBackgroundColour(WGT_DEVICE_NOZZLE_RACK_HOTEND_UPDATE_DEFAULT_BG);
SetBorderColor(WGT_DEVICE_NOZZLE_RACK_HOTEND_UPDATE_DEFAULT_BG);
SetCornerRadius(0);
//load nozzle hs image
for (int i = 1; i <= 4; i++)
{
auto normalImage = new ScalableBitmap(this, "Nozzle_HS_01_0" + std::to_string(i * 2), 46);
auto bigImage = new ScalableBitmap(this, "Big_Nozzle_HS_01_0" + std::to_string(i * 2), 216);
nozzle_hs.push_back({normalImage, bigImage});
}
for (int i = 2; i <= 4; i++)
{
auto normalImage = new ScalableBitmap(this, "Nozzle_HH_01_0" + std::to_string(i * 2), 46);
auto bigImage = new ScalableBitmap(this, "Big_Nozzle_HH_01_0" + std::to_string(i * 2), 216);
nozzle_hh.push_back({normalImage, bigImage});
}
auto* content_sizer = new wxBoxSizer(wxHORIZONTAL);
// Index
m_idx_label = new Label(this);
m_idx_label->SetFont(Label::Head_14);
m_idx_label->SetBackgroundColour(WGT_DEVICE_NOZZLE_RACK_HOTEND_UPDATE_DEFAULT_BG);
content_sizer->Add(m_idx_label, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(25));
// Icon
wxPanel* imagePanel = new wxPanel(this);
imagePanel->SetMaxSize(WX_DIP_SIZE(46, -1));
imagePanel->SetMinSize(WX_DIP_SIZE(46, -1));
imagePanel->SetSize(wxSize(FromDIP(46), FromDIP(-1)));
wxBoxSizer* panelSizer = new wxBoxSizer(wxVERTICAL);
imagePanel->SetSizer(panelSizer);
m_nozzle_empty_image = new ScalableBitmap(imagePanel, "dev_rack_nozzle_empty", 46);
m_icon_bitmap = new wxStaticBitmap(imagePanel, wxID_ANY, m_nozzle_empty_image->bmp());
panelSizer->Add(m_icon_bitmap, 0, wxALIGN_CENTER);
content_sizer->Add(imagePanel, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(25));
m_icon_bitmap->Bind(wxEVT_ENTER_WINDOW, &wgtDeviceNozzleRackHotendUpdate::OnBitmapHoverEnter, this);
m_icon_bitmap->Bind(wxEVT_LEAVE_WINDOW, &wgtDeviceNozzleRackHotendUpdate::OnBitmapHoverLeave, this);
// Diameter/type (vertical)
wxPanel* type_panel = new wxPanel(this);
auto* main_type_sizer = new wxBoxSizer(wxVERTICAL);
auto* type_sizer_row_1 = new wxBoxSizer(wxHORIZONTAL);
auto* type_sizer_row_2 = new wxBoxSizer(wxHORIZONTAL);
m_material_label = new Label(type_panel);
m_material_label->SetFont(Label::Body_12);
m_material_label->SetBackgroundColour(WGT_DEVICE_NOZZLE_RACK_HOTEND_UPDATE_DEFAULT_BG);
m_colour_box = new StaticBox(type_panel);
m_colour_box->SetMaxSize(WX_DIP_SIZE(16, 16));
m_colour_box->SetMinSize(WX_DIP_SIZE(16, 16));
m_colour_box->SetCornerRadius(FromDIP(2));
m_colour_box->SetSize(wxSize(FromDIP(16), FromDIP(16)));
type_sizer_row_1->Add(m_colour_box, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT);
type_sizer_row_1->AddStretchSpacer(1);
type_sizer_row_1->Add(m_material_label, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(2));
m_diameter_label = new Label(type_panel);
m_diameter_label->SetFont(Label::Body_12);
m_diameter_label->SetBackgroundColour(WGT_DEVICE_NOZZLE_RACK_HOTEND_UPDATE_DEFAULT_BG);
m_flowtype_label = new Label(type_panel);
m_flowtype_label->SetFont(Label::Body_12);
m_flowtype_label->SetBackgroundColour(WGT_DEVICE_NOZZLE_RACK_HOTEND_UPDATE_DEFAULT_BG);
m_type_label = new Label(type_panel);
m_type_label->SetFont(Label::Body_12);
m_type_label->SetBackgroundColour(WGT_DEVICE_NOZZLE_RACK_HOTEND_UPDATE_DEFAULT_BG);
type_sizer_row_2->Add(m_diameter_label, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT );
type_sizer_row_2->Add(m_flowtype_label, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(2));
type_sizer_row_2->Add(m_type_label, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(2));
main_type_sizer->Add(type_sizer_row_1, 0, wxALIGN_LEFT);
main_type_sizer->Add(type_sizer_row_2, 1, wxALIGN_LEFT | wxEXPAND | wxTOP, FromDIP(4));
type_panel->SetSizer(main_type_sizer);
type_panel->SetMaxSize(WX_DIP_SIZE(160, 40));
type_panel->SetMinSize(WX_DIP_SIZE(160, 40));
type_panel->SetSize(WX_DIP_SIZE(160, 40));
content_sizer->Add(type_panel, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(12));
// SN and version (vertical)
wxPanel* info_panel = new wxPanel(this);
auto* info_sizer = new wxBoxSizer(wxVERTICAL);
m_sn_label = new Label(info_panel);
m_sn_label->SetFont(Label::Body_12);
m_sn_label->SetBackgroundColour(WGT_DEVICE_NOZZLE_RACK_HOTEND_UPDATE_DEFAULT_BG);
auto* version_h_sizer = new wxBoxSizer(wxHORIZONTAL);
m_version_label = new Label(info_panel);
m_version_label->SetFont(Label::Body_12);
m_version_label->SetBackgroundColour(WGT_DEVICE_NOZZLE_RACK_HOTEND_UPDATE_DEFAULT_BG);
m_version_new_label = new Label(info_panel);
m_version_new_label->SetFont(Label::Body_12);
m_version_new_label->SetBackgroundColour(WGT_DEVICE_NOZZLE_RACK_HOTEND_UPDATE_DEFAULT_BG);
m_version_new_label->SetForegroundColour(wxColour(0, 168, 84)); // Green
version_h_sizer->Add(m_version_label, 0, wxALIGN_CENTER_VERTICAL);
version_h_sizer->Add(m_version_new_label, 0, wxALIGN_CENTER_VERTICAL);
info_sizer->Add(m_sn_label, 0, wxALIGN_LEFT);
info_sizer->Add(version_h_sizer, 0, wxALIGN_LEFT | wxTOP, FromDIP(4));
info_panel->SetSizer(info_sizer);
info_panel->SetMaxSize(WX_DIP_SIZE(183, 40));
info_panel->SetMinSize(WX_DIP_SIZE(183, 40));
info_panel->SetSize(WX_DIP_SIZE(183, 40));
//Used Time
m_used_time = new Label(this);
m_used_time->SetFont(Label::Body_12);
m_used_time->SetBackgroundColour(WGT_DEVICE_NOZZLE_RACK_HOTEND_UPDATE_DEFAULT_BG);
m_refresh_icon = new ScalableBitmap(this, "refresh_printer", 12);
m_error_icon = new ScalableBitmap(this, "error", 14);
m_status_bitmap = new wxStaticBitmap(this, wxID_ANY, m_refresh_icon->bmp());
m_status_bitmap->Bind(wxEVT_LEFT_UP, &wgtDeviceNozzleRackHotendUpdate::OnStatusIconClick, this);
std::vector<std::string> list{"refresh_nozzle_1", "refresh_nozzle_2", "refresh_nozzle_3", "refresh_nozzle_4"};
// Orca: AnimaIcon has no per-instance size argument (it fixes 25px internally), so no size is
// passed here.
m_refreshing_icon = new AnimaIcon(this, wxID_ANY, list, "refresh_nozzle", 100);
m_refreshing_icon->Show(false);
m_status_label = new Label(this);
m_status_label->SetFont(Label::Body_12);
content_sizer->Add(info_panel, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(20));
content_sizer->Add(m_used_time, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(20));
content_sizer->Add(m_status_label, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(20));
content_sizer->Add(m_status_bitmap, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(2));
content_sizer->Add(m_refreshing_icon, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(2));
content_sizer->AddSpacer(FromDIP(25));
auto* main_sizer = new wxBoxSizer(wxHORIZONTAL);
main_sizer->Add(content_sizer, 0, wxEXPAND | wxTOP | wxBOTTOM, FromDIP(10));
SetSizer(main_sizer);
Layout();
}
void wgtDeviceNozzleRackHotendUpdate::OnStatusIconClick(wxMouseEvent& event)
{
if (m_status_label->GetLabel() == _L("Refresh"))
{
m_status_label->SetForegroundColour(wxColour("#A3A3A3"));
m_status_label->SetLabel(_L("Refreshing"));
m_status_bitmap->Show(false);
if(!m_refreshing_icon->IsPlaying())
{
m_refreshing_icon->Play();
m_refreshing_icon->Show();
}
if (auto shared = m_nozzle_rack.lock())
{
shared->CrtlRackReadNozzle(m_rack_nozzle_id);
}
Layout();
}
if (m_status_label->GetLabel() == _L("Error") /*&& m_nozzle_status == NOZZLE_STATUS_ABNORMAL*/)
{
if (auto shared = m_nozzle_rack.lock())
{
MessageDialog dlg(nullptr, _L("Hotend status abnormal, unavailable at present. Please upgrade the firmware"
" and try again."), _L("Update"), wxICON_WARNING);
dlg.AddButton(wxID_CANCEL, _L("Cancel"), false);
dlg.AddButton(wxID_OK,_L("Jump to the upgrade page"), true);
if (dlg.ShowModal() == wxID_OK)
{
wxGetApp().mainframe->m_monitor->jump_to_Upgrade();
wxCommandEvent evt(wxEVT_NOZZLE_JUMP_UPGRADE, GetId());
evt.SetEventObject(this);
wxWindow* target = GetParent();
if (target)
{
target = target->GetParent();
if (target)
{
wxPostEvent(target, evt);
}
}
};
}
}
}
void wgtDeviceNozzleRackHotendUpdate::OnBitmapHoverEnter(wxMouseEvent& event)
{
int scaledW = FromDIP(240);
int scaledH = FromDIP(240);
ScalableBitmap* scaledBmp{nullptr};
if (m_nozzle_status == NOZZLE_STATUS_EMPTY || m_nozzle_status == NOZZLE_STATUS_UNKNOWN || !findNozzleImage)
{
if (!m_scaled_nozzle_empty_image) {m_scaled_nozzle_empty_image = new ScalableBitmap(this, "dev_rack_nozzle_empty", 216);}
scaledBmp = m_scaled_nozzle_empty_image;
}
else
{
scaledBmp = m_scaled_nozzle_image;
}
m_hoverFrame = new wxFrame(nullptr, wxID_ANY, "",
wxDefaultPosition, wxDefaultSize,
wxFRAME_NO_TASKBAR | wxBORDER_NONE | wxTRANSPARENT_WINDOW);
m_hoverFrame->SetBackgroundColour(WGT_DEVICE_NOZZLE_RACK_HOTEND_UPDATE_DEFAULT_BG);
m_hoverFrame->SetSize(scaledW, scaledH);
wxBoxSizer* frameSizer = new wxBoxSizer(wxVERTICAL);
m_hoverFrame->SetSizer(frameSizer);
wxStaticBitmap* hoverBmp = new wxStaticBitmap(m_hoverFrame, wxID_ANY, scaledBmp->bmp());
frameSizer->Add(hoverBmp, 0, wxALIGN_CENTER_HORIZONTAL | wxTOP | wxBOTTOM, FromDIP(12));
wxPoint mousePos = wxGetMousePosition();
m_hoverFrame->SetPosition(wxPoint(mousePos.x + 10, mousePos.y));
m_hoverFrame->Layout();
m_hoverFrame->Show(true);
event.Skip();
}
void wgtDeviceNozzleRackHotendUpdate::OnBitmapHoverLeave(wxMouseEvent& event)
{
if (m_hoverFrame)
{
m_hoverFrame->Destroy();
m_hoverFrame = nullptr;
}
event.Skip();
}
void wgtDeviceNozzleRackHotendUpdate::updateNozzleImage(const DevNozzle& nozzle)
{
if (m_nozzle_status == NOZZLE_STATUS_UNKNOWN || m_nozzle_status == NOZZLE_STATUS_EMPTY)
{
if (!m_nozzle_empty_image) {m_nozzle_empty_image = new ScalableBitmap(this, "dev_rack_nozzle_empty", 46);}
m_icon_bitmap->SetBitmap(m_nozzle_empty_image->bmp());
m_icon_bitmap->Refresh();
return;
}
findNozzleImage = false;
int index = -1;
switch (nozzle.GetNozzleDiameterType()) {
case NOZZLE_DIAMETER_0_2: index = 0; break;
case NOZZLE_DIAMETER_0_4: index = 1; break;
case NOZZLE_DIAMETER_0_6: index = 2; break;
case NOZZLE_DIAMETER_0_8: index = 3; break;
default: index = -1; break;
}
NozzleType nozzleType = nozzle.GetNozzleType();
if (nozzleType == ntHardenedSteel || nozzleType == ntStainlessSteel)
{
if (nozzle.GetNozzleFlowType() == H_FLOW && index >= 1)
{
m_nozzle_image = nozzle_hh[index - 1][0];
m_icon_bitmap->SetBitmap(m_nozzle_image->bmp());
m_scaled_nozzle_image = nozzle_hh[index - 1][1];
findNozzleImage = true;
}
else if (nozzle.GetNozzleFlowType() == S_FLOW && index >= 0)
{
m_nozzle_image = nozzle_hs[index][0];
m_icon_bitmap->SetBitmap(m_nozzle_image->bmp());
m_scaled_nozzle_image = nozzle_hs[index][1];
findNozzleImage = true;
}
}
if (!findNozzleImage)
{
if (!m_nozzle_empty_image) {m_nozzle_empty_image = new ScalableBitmap(this, "dev_rack_nozzle_empty", 46);}
m_icon_bitmap->SetBitmap(m_nozzle_empty_image->bmp());
}
m_icon_bitmap->Refresh();
}
void wgtDeviceNozzleRackHotendUpdate::UpdateColourStyle(const wxColour& clr)
{
SetBackgroundColour(clr);
SetBorderColor(clr);
// BFS: Update all children background color
auto children = GetChildren();
while (!children.IsEmpty())
{
auto win = children.front();
children.pop_front();
win->SetBackgroundColour(clr);
for (auto child : win->GetChildren())
{
children.push_back(child);
}
}
}
void wgtDeviceNozzleRackHotendUpdate::UpdateExtruderNozzleInfo(const std::shared_ptr<DevNozzleRack> rack)
{
m_nozzle_rack = rack;
if (rack)
{
DevNozzleSystem* nozzle_system = rack->GetNozzleSystem();
if (nozzle_system)
{
UpdateInfo(nozzle_system->GetExtNozzle(m_ext_nozzle_id));
}
}
}
void wgtDeviceNozzleRackHotendUpdate::UpdateRackNozzleInfo(const std::shared_ptr<DevNozzleRack> rack)
{
m_nozzle_rack = rack;
if (rack)
{
UpdateInfo(rack->GetNozzle(m_rack_nozzle_id));
}
}
void wgtDeviceNozzleRackHotendUpdate::UpdateInfo(const DevNozzle& nozzle)
{
/*update nozzle possition and background*/
if (auto share = m_nozzle_rack.lock())
{
// if (share->GetReadingCount() > 0 && m_status_label->IsShown())
if (share->GetReadingCount() > 0 && m_status_label->IsShown() && m_status_label->GetLabel() == _L("Refreshing"))
{
m_refreshing_icon->Play();
m_refreshing_icon->Show();
m_nozzle_status = NOZZLE_STATUS_DC;
return;
}
else
{
m_refreshing_icon->Stop();
m_refreshing_icon->Show(false);
}
}
wxString filamentDisplayName{};
for (auto iter = GUI::wxGetApp().preset_bundle->filaments.begin(); iter != GUI::wxGetApp().preset_bundle->filaments.end(); ++iter)
{
const Preset& filament_preset = *iter;
// const auto& config = filament_preset.config;
if (filament_preset.filament_id == nozzle.GetFilamentId())
{
filamentDisplayName = wxString(filament_preset.alias);
}
}
if (nozzle.IsEmpty() && m_nozzle_status != NOZZLE_STATUS_EMPTY)
{
m_nozzle_status = NOZZLE_STATUS_EMPTY;
m_material_label->Show(false);
m_colour_box->Show(false);
m_diameter_label->Show(false);
m_flowtype_label->Show(false);
m_type_label->SetLabel(_L("Empty"));
m_type_label->SetForegroundColour(StateColor::darkModeColorFor(*wxBLACK));
m_sn_label->Show(false);
m_version_label->Show(false);
m_version_new_label->Show(false);
updateNozzleImage(nozzle);
m_used_time->Show(false);
m_status_label->Show(false);
m_status_bitmap->Show(false);
}
else if (nozzle.IsNormal() && m_nozzle_status != NOZZLE_STATUS_NORMAL)
{
m_nozzle_status = NOZZLE_STATUS_NORMAL;
m_material_label->SetLabel(filamentDisplayName);
m_colour_box->SetBackgroundColour(wxColour("#" + nozzle.GetFilamentColor()));
m_colour_box->SetBorderColor(wxColour("#" + nozzle.GetFilamentColor()));
m_material_label->Show(true);
m_colour_box->Show(true);
m_diameter_label->Show(true);
m_flowtype_label->Show(true);
m_diameter_label->SetLabel(nozzle.GetNozzleDiameterStr());
m_flowtype_label->SetLabel(nozzle.GetNozzleFlowTypeStr());
m_type_label->SetLabel(nozzle.GetNozzleTypeStr());
m_type_label->SetForegroundColour(StateColor::darkModeColorFor(*wxBLACK));
m_sn_label->Show(true);
m_version_label->Show(true);
updateNozzleImage(nozzle);
m_used_time->Show(true);
m_status_label->Show(false);
m_status_bitmap->Show(false);
}
else if (nozzle.IsAbnormal() && m_nozzle_status != NOZZLE_STATUS_ABNORMAL)
{
m_nozzle_status = NOZZLE_STATUS_ABNORMAL;
m_material_label->SetLabel(filamentDisplayName);
m_colour_box->SetBackgroundColour(wxColour("#" + nozzle.GetFilamentColor()));
m_colour_box->SetBorderColor(wxColour("#" + nozzle.GetFilamentColor()));
m_material_label->Show(true);
m_colour_box->Show(true);
m_diameter_label->Show(true);
m_flowtype_label->Show(true);
m_diameter_label->SetLabel(nozzle.GetNozzleDiameterStr());
m_flowtype_label->SetLabel(nozzle.GetNozzleFlowTypeStr());
m_type_label->SetLabel(nozzle.GetNozzleTypeStr());
m_type_label->SetForegroundColour(StateColor::darkModeColorFor(*wxBLACK));
updateNozzleImage(nozzle);
m_status_label->Show(true);
m_status_bitmap->Show(true);
m_status_label->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#E14747")));
m_status_label->SetLabel(_L("Error"));
m_status_bitmap->SetBitmap(m_error_icon->bmp());
m_status_bitmap->Refresh();
}
else if (nozzle.IsUnknown() && m_nozzle_status != NOZZLE_STATUS_UNKNOWN)
{
m_nozzle_status = NOZZLE_STATUS_UNKNOWN;
m_colour_box->Show(false);
m_material_label->SetLabel(wxString("--"));
m_material_label->Show(true);
m_diameter_label->Show(false);
m_flowtype_label->Show(false);
m_type_label->SetLabel(_L("Unknown"));
m_type_label->SetForegroundColour(StateColor::darkModeColorFor(*wxBLACK));
m_sn_label->Show(true);
m_version_label->Show(true);
updateNozzleImage(nozzle);
m_used_time->Show(true);
m_status_label->Show(true);
m_status_bitmap->Show(true);
m_status_label->SetForegroundColour(wxColour("#00AE42"));
m_status_label->SetLabel(_L("Refresh"));
m_status_bitmap->SetBitmap(m_refresh_icon->bmp());
m_status_bitmap->Refresh();
}
// Update firmware info
const DevFirmwareVersionInfo& firmware = nozzle.GetFirmwareInfo();
if (nozzle.IsUnknown())
{
m_sn_label->SetLabel(wxString::Format("%s: --", _L("SN")));
m_version_label->SetLabel(wxString::Format("%s: --", _L("Version")));
m_version_new_label->Show(false);
}
if (nozzle.IsNormal() || nozzle.IsAbnormal())
{
if (firmware.isValid())
{
m_sn_label->SetLabel(wxString::Format("%s: %s", _L("SN"), firmware.sn));
if (!firmware.sw_new_ver.empty() && firmware.sw_new_ver != firmware.sw_ver)
{
m_version_label->SetLabel(wxString::Format("%s: %s > ", _L("Version"), firmware.sw_ver));
m_version_new_label->SetLabel(wxString::Format("%s", firmware.sw_new_ver));
m_version_new_label->Show(true);
}
else
{
m_version_label->SetLabel(wxString::Format("%s:%s", _L("Version"), firmware.sw_ver));
m_version_new_label->Show(false);
}
}
else
{
m_sn_label->SetLabel(wxString::Format("%s: --", _L("SN")));
m_version_label->SetLabel(wxString::Format("%s: --", _L("Version")));
m_version_new_label->Show(false);
}
}
if (!nozzle.IsEmpty())
{
if (nozzle.IsUnknown())
{
m_used_time->SetLabel(wxString::Format(_L("Used Time: %s"), "--h"));
}
else
{
// Update used time
int usedSeconds = nozzle.GetNozzlePrintTime();
if (usedSeconds < 60)
{
m_used_time->SetLabel(wxString::Format(_L("Used Time: %s"), "0 h"));
}
else
{
int printTime = (usedSeconds >= 3600 ? usedSeconds / 3600 : usedSeconds / 60);
std::string printTimeStr = (usedSeconds >= 3600 ? std::to_string(printTime) + " h" : std::to_string(printTime) + " min");
m_used_time->SetLabel(wxString::Format(_L("Used Time: %s"), printTimeStr.c_str()));
}
}
}
}
void wgtDeviceNozzleRackHotendUpdate::Rescale()
{
// update images
if (m_nozzle_image) { m_nozzle_image->msw_rescale(); }
if (m_nozzle_empty_image) { m_nozzle_empty_image->msw_rescale(); }
if (m_nozzle_status == NOZZLE_STATUS_EMPTY && m_nozzle_empty_image)
{
m_icon_bitmap->SetBitmap(m_nozzle_empty_image->bmp());
}
else if (m_nozzle_image != nullptr)
{
m_icon_bitmap->SetBitmap(m_nozzle_image->bmp());
}
m_icon_bitmap->Refresh();
}
};// namespace Slic3r::GUI
@@ -0,0 +1,175 @@
//**********************************************************/
/* File: wgtDeviceNozzleRackUpdate.h
* Description: The dialog for reading/upgrading the H2C induction-hotend-rack firmware.
*
* \n class wgtDeviceNozzleRackUpgradeDlg; // modal "Hotends Info" dialog
* \n class wgtDeviceNozzleRackUprade; // the dialog's content panel (per-nozzle rows)
* \n class wgtDeviceNozzleRackHotendUpdate; // one hotend row (toolhead "R" + rack 1..6)
*
* Opened from the Device-tab rack panel's "Hotends Info" button (wgtDeviceNozzleRack.cpp) and
* reused by the Device->Upgrade page "Hotends on Rack" section
* (UpgradePanel::createNozzleRackWidgets). All device data + MQTT commands live in
* DevNozzleRack / DevNozzleSystem, so this is additive GUI gated behind
* DevNozzleRack::IsSupported() — dormant for every non-rack printer.
//**********************************************************/
#pragma once
#include "slic3r/GUI/DeviceCore/DevNozzleRack.h"
#include "slic3r/GUI/GUI_Utils.hpp"
#include "slic3r/GUI/Widgets/StaticBox.hpp"
#include "slic3r/GUI/Widgets/AnimaController.hpp"
#include <wx/panel.h>
#include <memory>
#include <unordered_map>
#include <vector>
// Previous definitions
class Button;
class Label;
class ScalableBitmap;
class ScalableButton;
namespace Slic3r
{
struct DevNozzle;
class DevNozzleRack;
namespace GUI
{
class wgtDeviceNozzleRackUprade;
class wgtDeviceNozzleRackHotendUpdate;
}
};
namespace Slic3r::GUI
{
class wgtDeviceNozzleRackUpgradeDlg : public DPIDialog
{
public:
wgtDeviceNozzleRackUpgradeDlg(wxWindow* parent, const std::shared_ptr<DevNozzleRack> rack);
public:
void UpdateRackInfo(const std::shared_ptr<DevNozzleRack> rack);
public:
void on_dpi_changed(const wxRect& suggested_rect) override;
private:
wgtDeviceNozzleRackUprade* m_rack_upgrade_panel;
};
class wgtDeviceNozzleRackUprade : public wxPanel
{
public:
wgtDeviceNozzleRackUprade(wxWindow* parent,
wxWindowID id = wxID_ANY,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxTAB_TRAVERSAL);
~wgtDeviceNozzleRackUprade() = default;
public:
void UpdateRackInfo(const std::shared_ptr<DevNozzleRack> rack);
void Rescale();
private:
void CreateGui();
void OnBtnReadAll(wxCommandEvent& e);
private:
std::weak_ptr<DevNozzleRack> m_nozzle_rack;
// GUI
wgtDeviceNozzleRackHotendUpdate* m_extruder_nozzle_item;
std::unordered_map<int, wgtDeviceNozzleRackHotendUpdate*> m_nozzle_items;
};
// Using for rack nozzle id or extruder nozzle id
class wgtDeviceNozzleRackHotendUpdate : public StaticBox
{
public:
wgtDeviceNozzleRackHotendUpdate(wxWindow* parent, const wxString& idx_text);
public:
// Color
void UpdateColourStyle(const wxColour& clr);
// extruder nozzle
int GetExtruderNozzleId() const { return m_ext_nozzle_id; }
void SetExtruderNozzleId(int ext_nozzle_id) { m_ext_nozzle_id = ext_nozzle_id; }
void UpdateExtruderNozzleInfo(const std::shared_ptr<DevNozzleRack> rack);
// rack nozzle
void SetRackNozzleId(int rack_nozzle_id) { m_rack_nozzle_id = rack_nozzle_id; }
int GetRackNozzleId() const { return m_rack_nozzle_id; }
void UpdateRackNozzleInfo(const std::shared_ptr<DevNozzleRack> rack);
// gui
void Rescale();
private:
void CreateGui();
void UpdateInfo(const DevNozzle& nozzle);
void OnBitmapHoverEnter(wxMouseEvent& event);
void OnBitmapHoverLeave(wxMouseEvent& event);
void OnStatusIconClick(wxMouseEvent& event);
void updateNozzleImage(const DevNozzle& nozzle);
private:
enum NozzleStatus : int
{
NOZZLE_STATUS_DC = -1,
NOZZLE_STATUS_EMPTY,
NOZZLE_STATUS_NORMAL,
NOZZLE_STATUS_ABNORMAL,
NOZZLE_STATUS_UNKNOWN,
};
private:
int m_ext_nozzle_id = -1;
int m_rack_nozzle_id = -1;
bool m_isRefreshFinish = false;
bool findNozzleImage = false;
NozzleStatus m_nozzle_status = NOZZLE_STATUS_DC;
std::weak_ptr<DevNozzleRack> m_nozzle_rack;
std::vector<std::vector<ScalableBitmap*>> nozzle_hs;
std::vector<std::vector<ScalableBitmap*>> nozzle_hh;
// GUI
ScalableBitmap* m_nozzle_image = nullptr;
ScalableBitmap* m_nozzle_empty_image = nullptr;
ScalableBitmap* m_scaled_nozzle_image = nullptr;
ScalableBitmap* m_scaled_nozzle_empty_image = nullptr;
ScalableBitmap* m_refresh_icon = nullptr;
AnimaIcon* m_refreshing_icon = nullptr;
ScalableBitmap* m_error_icon = nullptr;
Label* m_idx_label;
wxStaticBitmap* m_icon_bitmap{ nullptr };
wxStaticBitmap* m_status_bitmap{ nullptr };
Label* m_material_label{ nullptr };
StaticBox* m_colour_box{ nullptr };
wxFrame* m_hoverFrame{ nullptr };
Label* m_status_label{ nullptr };
Label* m_used_time{ nullptr };
Label* m_diameter_label;
Label* m_flowtype_label;
Label* m_type_label;
ScalableButton* m_error_button{ nullptr };
Label* m_sn_label;
Label* m_version_label;
Label* m_version_new_label;
};
wxDECLARE_EVENT(wxEVT_NOZZLE_JUMP_UPGRADE, wxCommandEvent);
};// end of namespace Slic3r::GUI
@@ -0,0 +1,289 @@
//**********************************************************/
/* File: wgtDeviceNozzleSelect.cpp
* Description: The panel to select nozzle
*
* \n class wgtDeviceNozzleSelect;
//**********************************************************/
#include "wgtDeviceNozzleSelect.h"
#include "wgtDeviceNozzleRackNozzleItem.h" // the nozzle-item widget lives in its own header
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/DeviceTab/wgtMsgBox.h"
#include "slic3r/GUI/Widgets/Label.hpp"
static wxColour s_gray_clr("#B0B0B0");
static wxColour s_hgreen_clr("#00AE42");
static wxColour s_red_clr("#D01B1B");
static std::vector<int> a_nozzle_seq = {16, 18, 20, 17, 19, 21};
wxDEFINE_EVENT(EVT_NOZZLE_SELECT_CHANGED, wxCommandEvent);
wxDEFINE_EVENT(EVT_NOZZLE_SELECT_CLICKED, wxCommandEvent);
namespace Slic3r::GUI {
wgtDeviceNozzleRackSelect::wgtDeviceNozzleRackSelect(wxWindow *parent) : wxPanel(parent, wxID_ANY) { CreateGui(); }
static wxPanel* s_create_title(wxWindow *parent, const wxString& text)
{
wxPanel *panel = new wxPanel(parent, wxID_ANY);
auto title = new Label(panel, text);
title->SetFont(::Label::Body_13);
title->SetBackgroundColour(*wxWHITE);
title->SetForegroundColour(0x909090);
auto split_line = new wxPanel(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
split_line->SetBackgroundColour(0xeeeeee);
split_line->SetMinSize(wxSize(-1, 1));
split_line->SetMaxSize(wxSize(-1, 1));
wxBoxSizer *sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(0, 0, 0, wxEXPAND, 0);
sizer->Add(title, 0, wxALIGN_CENTER, 0);
sizer->Add(split_line, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND, 0);
panel->SetSizer(sizer);
panel->Layout();
return panel;
}
void wgtDeviceNozzleRackSelect::CreateGui()
{
wxSizer *main_sizer = new wxBoxSizer(wxVERTICAL);
wxColour tip_bg_clr("#FFF0E0");
m_title_tips_dynamic = new wgtMsgBox(this);
m_title_tips_dynamic->SetBackgroundColour(tip_bg_clr);
m_title_tips_dynamic->SetBorderColor(wxColour("#FF6F00"));
m_title_tips_dynamic->SetCornerRadius(1);
m_title_tips_dynamic->SetBorderWidth(1);
auto text_label = m_title_tips_dynamic->GetTextLabel();
text_label->SetFont(::Label::Body_12);
text_label->SetForegroundColour("#FF6F00");
text_label->SetBackgroundColour(tip_bg_clr);
text_label->SetLabel(_L("Dynamic nozzles are allocated on the current plate. Picking hotend is not supported."));
text_label->Wrap(FromDIP(300));
text_label->Fit();
m_title_tips_dynamic->Layout();
m_title_tips_dynamic->Refresh();
// nozzles
wxGridSizer *nozzle_sizer = new wxGridSizer(2, 3, FromDIP(10), FromDIP(10));
for (auto idx : a_nozzle_seq) {
wgtDeviceNozzleRackNozzleItem *nozzle_item = new wgtDeviceNozzleRackNozzleItem(this, idx - 16);
nozzle_item->EnableSelect();
nozzle_item->Bind(EVT_NOZZLE_RACK_NOZZLE_ITEM_SELECTED, &wgtDeviceNozzleRackSelect::OnNozzleItemSelected, this);
m_nozzle_items[idx] = nozzle_item;
nozzle_sizer->Add(nozzle_item, 0);
}
// toolhead area
m_toolhead_nozzle_l = new wgtDeviceNozzleRackNozzleItem(this, 1);
m_toolhead_nozzle_l->EnableSelect();
m_toolhead_nozzle_l->SetDisplayIdText("L");
m_toolhead_nozzle_l->Bind(EVT_NOZZLE_RACK_NOZZLE_ITEM_SELECTED, &wgtDeviceNozzleRackSelect::OnNozzleItemSelected, this);
m_toolhead_nozzle_r = new wgtDeviceNozzleRackNozzleItem(this, 0);
m_toolhead_nozzle_r->EnableSelect();
m_toolhead_nozzle_r->SetDisplayIdText("R");
m_toolhead_nozzle_r->Bind(EVT_NOZZLE_RACK_NOZZLE_ITEM_SELECTED, &wgtDeviceNozzleRackSelect::OnNozzleItemSelected, this);
wxSizer* toolhead_sizer = new wxBoxSizer(wxHORIZONTAL);
toolhead_sizer->Add(m_toolhead_nozzle_l, 0, wxRIGHT, FromDIP(5));
toolhead_sizer->Add(m_toolhead_nozzle_r, 0, wxLEFT, FromDIP(5));
main_sizer->Add(m_title_tips_dynamic, 0, wxALIGN_CENTRE_VERTICAL | wxBOTTOM, FromDIP(10));
main_sizer->Add(s_create_title(this, _L("Hotend Rack")), 0, wxEXPAND);
main_sizer->Add(nozzle_sizer, 0, wxTOP | wxBOTTOM | wxALIGN_LEFT, FromDIP(10));
main_sizer->Add(s_create_title(this, _L("ToolHead")), 0, wxEXPAND);
main_sizer->AddSpacer(FromDIP(10));
main_sizer->Add(toolhead_sizer, 0, wxALIGN_LEFT);
SetBackgroundColour(*wxWHITE);
SetSizer(main_sizer);
Layout();
Fit();
}
static void s_update_nozzle_info(wgtDeviceNozzleRackNozzleItem* item,
std::shared_ptr<DevNozzleRack> rack,
const DevNozzle& nozzle_info)
{
item->Update(rack, nozzle_info.IsOnRack());
if (nozzle_info.IsUnknown()) {
if (item->GetToolTipText() != _L("Nozzle information needs to be read")) {
item->SetToolTip(_L("Nozzle information needs to be read"));
}
} else {
item->SetToolTip(wxEmptyString);
}
}
void wgtDeviceNozzleRackSelect::UpdateNozzleInfos(std::shared_ptr<DevNozzleRack> rack)
{
m_nozzle_rack = rack;
if (rack) {
s_update_nozzle_info(m_toolhead_nozzle_l, rack, rack->GetNozzleSystem()->GetNozzleByPosId(DEPUTY_EXTRUDER_ID));
s_update_nozzle_info(m_toolhead_nozzle_r, rack, rack->GetNozzleSystem()->GetNozzleByPosId(MAIN_EXTRUDER_ID));
for (const auto& item : m_nozzle_items) {
s_update_nozzle_info(item.second, rack, rack->GetNozzleSystem()->GetNozzleByPosId(item.first));
}
}
}
static void s_enable_item_if_match(wgtDeviceNozzleRackNozzleItem* item,
const DevNozzle& nozzle_info,
const DevNozzle& selected_nozzle)
{
if (item) {
if (nozzle_info.GetLogicExtruderId() != selected_nozzle.GetLogicExtruderId()) {
item->SetDisable(true);
return;
}
if (!nozzle_info.IsEmpty() && !nozzle_info.IsAbnormal() && !nozzle_info.IsUnknown() &&
nozzle_info.GetNozzleType() == selected_nozzle.GetNozzleType() &&
nozzle_info.GetNozzleDiameter() == selected_nozzle.GetNozzleDiameter() &&
nozzle_info.GetNozzleFlowType() == selected_nozzle.GetNozzleFlowType()) {
item->SetDisable(false);
} else {
item->SetDisable(true);
}
}
}
void wgtDeviceNozzleRackSelect::UpdatSelectedNozzle(std::shared_ptr<DevNozzleRack> rack, int selected_nozzle_pos_id)
{
m_nozzle_rack = rack;
if (rack) {
SetSelectedNozzle(rack->GetNozzleSystem()->GetNozzleByPosId(selected_nozzle_pos_id));
if (m_enable_manual_nozzle_pick) {
s_enable_item_if_match(m_toolhead_nozzle_r, rack->GetNozzleSystem()->GetNozzleByPosId(MAIN_EXTRUDER_ID), m_selected_nozzle);
s_enable_item_if_match(m_toolhead_nozzle_l, rack->GetNozzleSystem()->GetNozzleByPosId(DEPUTY_EXTRUDER_ID), m_selected_nozzle);
for (auto& item : m_nozzle_items) {
s_enable_item_if_match(item.second, rack->GetNozzleSystem()->GetNozzleByPosId(item.first), m_selected_nozzle);
}
}
}
}
void wgtDeviceNozzleRackSelect::UpdatSelectedNozzles(std::shared_ptr<DevNozzleRack> rack,
std::vector<int> selected_nozzle_pos_vec,
bool use_dynamic_switch,
std::optional<PrintFromType> /*print_from_type*/)
{
m_nozzle_rack = rack;
m_enable_manual_nozzle_pick = !use_dynamic_switch;
m_title_tips_dynamic->Show(use_dynamic_switch);
UpdateNozzleInfos(rack);
if (!use_dynamic_switch) {
if (selected_nozzle_pos_vec.size() > 0) {
return UpdatSelectedNozzle(rack, selected_nozzle_pos_vec.at(0));
} else {
return ClearSelection();
}
}
if (rack) {
ClearSelection();
for (const auto& pos_id : selected_nozzle_pos_vec) {
if (pos_id == MAIN_EXTRUDER_ID) {
m_toolhead_nozzle_r->SetSelected(true);
} else if (pos_id == DEPUTY_EXTRUDER_ID) {
m_toolhead_nozzle_l->SetSelected(true);
} else if (auto it = m_nozzle_items.find(pos_id); it != m_nozzle_items.end()) {
it->second->SetSelected(true);
}
}
if (!m_toolhead_nozzle_l->IsSelected()) {
m_toolhead_nozzle_l->SetDisable(true);
}
if (!m_toolhead_nozzle_r->IsSelected()) {
m_toolhead_nozzle_r->SetDisable(true);
}
for (const auto& item : m_nozzle_items) {
if (!item.second->IsSelected()) {
item.second->SetDisable(true);
}
}
}
}
void wgtDeviceNozzleRackSelect::ClearSelection()
{
m_selected_nozzle = DevNozzle();
m_toolhead_nozzle_l->SetSelected(false);
m_toolhead_nozzle_r->SetSelected(false);
for (auto &item : m_nozzle_items) { item.second->SetSelected(false); }
}
void wgtDeviceNozzleRackSelect::SetSelectedNozzle(const DevNozzle &nozzle)
{
int new_selected_pos_id = nozzle.GetNozzlePosId();
if (m_selected_nozzle.GetNozzlePosId() != new_selected_pos_id) {
ClearSelection();
m_selected_nozzle = nozzle;
if (new_selected_pos_id == MAIN_EXTRUDER_ID) {
m_toolhead_nozzle_r->SetSelected(true);
} else if (new_selected_pos_id == DEPUTY_EXTRUDER_ID) {
m_toolhead_nozzle_l->SetSelected(true);
} else if (auto it = m_nozzle_items.find(m_selected_nozzle.GetNozzlePosId()); it != m_nozzle_items.end()) {
it->second->SetSelected(true);
}
}
}
volatile int sGetNozzlePosId(wgtDeviceNozzleRackNozzleItem* item,
wgtDeviceNozzleRackNozzleItem* l_item,
wgtDeviceNozzleRackNozzleItem* r_item)
{
int to_select_pos_id = -1;
if (item == l_item) {
to_select_pos_id = DEPUTY_EXTRUDER_ID;
} else if (item == r_item) {
to_select_pos_id = MAIN_EXTRUDER_ID;
} else {
to_select_pos_id = item->GetNozzleId() + 0x10;
}
return to_select_pos_id;
}
void wgtDeviceNozzleRackSelect::OnNozzleItemSelected(wxCommandEvent &evt)
{
if (!m_enable_manual_nozzle_pick) {
return;
}
auto *item = dynamic_cast<wgtDeviceNozzleRackNozzleItem *>(evt.GetEventObject());
if (item; auto ptr = m_nozzle_rack.lock()) {
int to_select_pos_id = sGetNozzlePosId(item, m_toolhead_nozzle_l, m_toolhead_nozzle_r);
if (to_select_pos_id > -1 && to_select_pos_id != GetSelectedNozzlePosID()) {
SetSelectedNozzle(ptr->GetNozzleSystem()->GetNozzleByPosId(to_select_pos_id));
wxCommandEvent change_evt(EVT_NOZZLE_SELECT_CHANGED, GetId());
change_evt.SetEventObject(this);
ProcessEvent(change_evt);
evt.Skip();
} else {
wxCommandEvent change_evt(EVT_NOZZLE_SELECT_CLICKED, GetId());
change_evt.SetEventObject(this);
ProcessEvent(change_evt);
evt.Skip();
}
}
}
void wgtDeviceNozzleRackSelect::Rescale()
{
m_toolhead_nozzle_l->Rescale();
m_toolhead_nozzle_r->Rescale();
for (auto &item : m_nozzle_items) { item.second->Rescale(); }
}
}; // namespace Slic3r::GUI
@@ -0,0 +1,74 @@
//**********************************************************/
/* File: wgtDeviceNozzleSelect.h
* Description: The panel to select nozzle
*
* \n class wgtDeviceNozzleSelect;
//**********************************************************/
#pragma once
#include "slic3r/GUI/DeviceCore/DevNozzleSystem.h" // DevNozzle (value member) + DevDefs.h (PrintFromType, MAIN/DEPUTY_EXTRUDER_ID)
#include <wx/panel.h>
#include <memory>
#include <optional>
#include <unordered_map>
#include <vector>
// Previous definitions
class Label;
namespace Slic3r
{
struct DevNozzle;
class DevNozzleRack;
namespace GUI
{
class wgtDeviceNozzleRackNozzleItem;
class wgtMsgBox;
}
};
wxDECLARE_EVENT(EVT_NOZZLE_SELECT_CHANGED, wxCommandEvent);
wxDECLARE_EVENT(EVT_NOZZLE_SELECT_CLICKED, wxCommandEvent);
namespace Slic3r::GUI
{
class wgtDeviceNozzleRackSelect : public wxPanel
{
public:
wgtDeviceNozzleRackSelect(wxWindow* parent);
~wgtDeviceNozzleRackSelect() = default;
public:
int GetSelectedNozzlePosID() const { return m_selected_nozzle.GetNozzlePosId();}
void UpdatSelectedNozzles(std::shared_ptr<DevNozzleRack> rack, std::vector<int> selected_nozzle_pos_vec, bool use_dynamic_switch, std::optional<PrintFromType> print_from_type);// for slicing with dynamic switch
void Rescale();
private:
void CreateGui();
void ClearSelection();
void SetSelectedNozzle(const DevNozzle &nozzle);
void UpdatSelectedNozzle(std::shared_ptr<DevNozzleRack> rack, int selected_nozzle_pos_id = -1); // for slicing without dynamic switch
void UpdateNozzleInfos(std::shared_ptr<DevNozzleRack> rack);
void OnNozzleItemSelected(wxCommandEvent& evt);
private:
DevNozzle m_selected_nozzle;
std::weak_ptr<DevNozzleRack> m_nozzle_rack;
// pick
bool m_enable_manual_nozzle_pick = true;
// Label
wgtMsgBox* m_title_tips_dynamic;
// GUI
wgtDeviceNozzleRackNozzleItem * m_toolhead_nozzle_l{nullptr};
wgtDeviceNozzleRackNozzleItem * m_toolhead_nozzle_r{nullptr};
std::unordered_map<int, wgtDeviceNozzleRackNozzleItem *> m_nozzle_items; // from 16 to 21
};
};// end of namespace Slic3r::GUI
+43
View File
@@ -0,0 +1,43 @@
#include "wgtMsgBox.h"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Widgets/Label.hpp"
#include "slic3r/GUI/wxExtensions.hpp"
#include <wx/sizer.h>
namespace Slic3r::GUI
{
wgtMsgBox::wgtMsgBox(wxWindow* parent)
: StaticBox(parent, wxID_ANY)
{
CreateGUI();
}
void wgtMsgBox::CreateGUI()
{
wxBoxSizer* mainSizer = new wxBoxSizer(wxHORIZONTAL);
m_txt_label = new Label(this);
m_txt_label->SetFont(::Label::Body_13);
m_txt_label->SetBackgroundColour(*wxWHITE);
auto padding_sizer = new wxBoxSizer(wxHORIZONTAL);
padding_sizer->Add(m_txt_label, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5));
mainSizer->Add(padding_sizer, 0, wxALIGN_CENTER_VERTICAL | wxALL, 1);
//Button* btnClose = CreateCloseButton();
//wxButton* btnClose = CreateCloseButton();
//mainSizer->Add(btnClose, 0, wxALIGN_CENTER_VERTICAL);
SetSizer(mainSizer);
mainSizer->Fit(this);
}
void wgtMsgBox::OnCloseClicked(wxCommandEvent& evt)
{
this->Hide();
}
} // namespace Slic3r::GUI
+31
View File
@@ -0,0 +1,31 @@
#ifndef WGTMSGBOX_H
#define WGTMSGBOX_H
#include <wx/wx.h>
#include <wx/panel.h>
#include <wx/stattext.h>
#include <wx/button.h>
#include "slic3r/GUI/Widgets/StaticBox.hpp"
class Label;
namespace Slic3r::GUI
{
class wgtMsgBox : public StaticBox
{
public:
wgtMsgBox(wxWindow* parent);
public:
Label* GetTextLabel() const { return m_txt_label; }
private:
Label* m_txt_label;
private:
void CreateGUI();
void OnCloseClicked(wxCommandEvent& evt);
};
}
#endif // WGTMSGBOX_H
+2 -1
View File
@@ -138,7 +138,8 @@ FilamentMapDialog::FilamentMapDialog(wxWindow *parent,
if (mode < fmmManual)
m_page_type = PageType::ptAuto;
else if (mode == fmmManual)
else if (mode == fmmManual || mode == fmmNozzleManual)
// Orca: there is no dedicated nozzle-manual page, so treat an fmmNozzleManual plate as a manual variant and show the Custom page instead of misfiling it as "Same as Global".
m_page_type = PageType::ptManual;
else
m_page_type = PageType::ptDefault;
+29
View File
@@ -1,5 +1,7 @@
#include "FilamentMapPanel.hpp"
#include "GUI_App.hpp"
#include "Widgets/MultiNozzleSync.hpp" // manuallySetNozzleCount producer for extruder_nozzle_stats
#include <algorithm>
#include <wx/dcbuffer.h>
#include <wx/utils.h>
#include "wx/graphics.h"
@@ -72,6 +74,33 @@ FilamentMapManualPanel::FilamentMapManualPanel(wxWindow *p
m_switch_btn->Bind(wxEVT_BUTTON, &FilamentMapManualPanel::OnSwitchFilament, this);
// Multi-nozzle: give the user a reachable way to declare, per extruder, how many
// physical nozzles of each volume type a multi-nozzle extruder carries. This is the
// fallback "manual" producer of the extruder_nozzle_stats config (the full device nozzle-rack
// auto-sync is deferred). Gated on the edited printer preset having an extruder with
// extruder_max_nozzle_count > 1, so the trigger is not even created for any single-nozzle or
// dual-extruder ({1,1}, H2D) printer - zero UI change for every existing profile.
auto *max_nozzle_counts_opt = wxGetApp().preset_bundle->printers.get_edited_preset().config.option<ConfigOptionIntsNullable>("extruder_max_nozzle_count");
// Skip nil entries: a nullable-int nil is INT_MAX (> 1) and would otherwise falsely pass the gate.
if (max_nozzle_counts_opt &&
std::any_of(max_nozzle_counts_opt->values.begin(), max_nozzle_counts_opt->values.end(),
[](int v) { return v > 1 && v != ConfigOptionIntsNullable::nil_value(); })) {
auto *set_count_link = new Label(this, _L("Set the physical nozzle count..."));
set_count_link->SetFont(Label::Body_14);
set_count_link->SetForegroundColour(BorderSelectedColor);
set_count_link->SetCursor(wxCursor(wxCURSOR_HAND));
top_sizer->AddSpacer(FromDIP(8));
top_sizer->Add(set_count_link, 0, wxALIGN_LEFT | wxLEFT, FromDIP(15));
const std::vector<int> max_counts = max_nozzle_counts_opt->values;
set_count_link->Bind(wxEVT_LEFT_DOWN, [max_counts](wxMouseEvent &evt) {
for (int extruder_id = 0; extruder_id < (int) max_counts.size(); ++extruder_id) {
if (max_counts[extruder_id] > 1)
GUI::manuallySetNozzleCount(extruder_id);
}
evt.Skip();
});
}
SetSizer(top_sizer);
Layout();
Fit();
+5 -2
View File
@@ -2911,8 +2911,11 @@ void GCodeViewer::render_legend_color_arr_recommen(float window_padding)
float delta_weight_to_single_ext = stats_by_extruder.stats_by_single_extruder.filament_flush_weight - stats_by_extruder.stats_by_multi_extruder_curr.filament_flush_weight;
float delta_weight_to_best = stats_by_extruder.stats_by_multi_extruder_curr.filament_flush_weight - stats_by_extruder.stats_by_multi_extruder_best.filament_flush_weight;
int delta_change_to_single_ext = stats_by_extruder.stats_by_single_extruder.filament_change_count - stats_by_extruder.stats_by_multi_extruder_curr.filament_change_count;
int delta_change_to_best = stats_by_extruder.stats_by_multi_extruder_curr.filament_change_count - stats_by_extruder.stats_by_multi_extruder_best.filament_change_count;
// The displayed "hand changes" delta uses the per-nozzle flush_filament_change_count.
// For single-nozzle-per-extruder printers it equals the per-extruder filament_change_count,
// so the shown value is unchanged.
int delta_change_to_single_ext = stats_by_extruder.stats_by_single_extruder.flush_filament_change_count - stats_by_extruder.stats_by_multi_extruder_curr.flush_filament_change_count;
int delta_change_to_best = stats_by_extruder.stats_by_multi_extruder_curr.flush_filament_change_count - stats_by_extruder.stats_by_multi_extruder_best.flush_filament_change_count;
bool any_less_to_single_ext = delta_weight_to_single_ext > EPSILON || delta_change_to_single_ext > 0;
bool any_more_to_best = delta_weight_to_best > EPSILON || delta_change_to_best > 0;
+7 -2
View File
@@ -8136,6 +8136,11 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with
shader = wxGetApp().get_shader("gouraud");
ECanvasType canvas_type = this->m_canvas_type;
bool partly_inside_enable = canvas_type == ECanvasType::CanvasAssembleView ? false : true;
// The edited printer's per-extruder printable heights feed the object shader's
// extruder_printable_heights uniform. Empty for single-extruder printers, so the shader flag stays
// 0.0 and rendering is pixel-identical there (see GLVolumeCollection::render).
auto printable_height_option = wxGetApp().preset_bundle->printers.get_edited_preset().config.option<ConfigOptionFloatsNullable>("extruder_printable_height");
std::vector<double>* printable_heights = printable_height_option ? &printable_height_option->values : nullptr;
if (shader != nullptr) {
shader->start_using();
@@ -8183,7 +8188,7 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with
return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0);
}
},
partly_inside_enable);
partly_inside_enable, printable_heights);
}
}
else {
@@ -8218,7 +8223,7 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with
return true;
}
},
partly_inside_enable);
partly_inside_enable, printable_heights);
if (m_canvas_type == CanvasAssembleView && m_gizmos.m_assemble_view_data->model_objects_clipper()->get_position() > 0) {
const GLGizmosManager& gm = get_gizmos_manager();
shader->stop_using();
+10
View File
@@ -518,6 +518,16 @@ void MonitorPanel::jump_to_HMS()
m_tabpanel->SetSelection(PT_HMS);
}
void MonitorPanel::jump_to_Upgrade()
{
if (this->IsShown()) {
auto page = m_tabpanel->GetCurrentPage();
if (page && page != m_upgrade_panel) {
m_tabpanel->SetSelection(PT_UPDATE);
}
}
}
void MonitorPanel::jump_to_LiveView()
{
if (!this->IsShown()) { return; }
+1
View File
@@ -156,6 +156,7 @@ public:
void jump_to_HMS();
void jump_to_Upgrade();
void jump_to_LiveView();
void update_network_version_footer();
};
+42
View File
@@ -19,6 +19,7 @@
#include "I18N.hpp"
//#include "ConfigWizard.hpp"
#include "wxExtensions.hpp"
#include "Widgets/Label.hpp"
#include "slic3r/GUI/MainFrame.hpp"
#include "GUI_App.hpp"
#define MSG_DLG_MAX_SIZE wxSize(-1, FromDIP(464))//notice:ban setting the maximum width value
@@ -750,6 +751,47 @@ NetworkErrorDialog::NetworkErrorDialog(wxWindow* parent)
Centre(wxBOTH);
}
FilamentWarningDialog::FilamentWarningDialog(wxWindow *parent, const wxString &title, std::vector<FilamentWarningInfo> infos)
: MsgDialog(parent, title.IsEmpty() ? wxString::Format(_L("%s warning"), SLIC3R_APP_FULL_NAME) : title, wxEmptyString, wxOK | wxICON_WARNING), m_messages(infos)
{
BuildContent();
finalize();
}
void FilamentWarningDialog::BuildContent()
{
wxBoxSizer *messages_sizer = new wxBoxSizer(wxVERTICAL);
int message_count = 0;
for (int i = 0; i < m_messages.size(); i++)
{
const wxString &message = m_messages[i].info_msg;
const wxString &wiki_url = m_messages[i].wiki_url;
if (message_count > 0) { messages_sizer->AddSpacer(FromDIP(10)); }
if (wiki_url.IsEmpty()) {
// No wiki link - just display as regular text
Label *text = new Label(this, message);
text->SetFont(::Label::Body_12);
text->Wrap(FromDIP(400));
messages_sizer->Add(text, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(5));
} else {
Label *link = new Label(this, message + " " + _L("Please refer to Wiki before use->"));
link->SetForegroundColour(wxColour(8, 153, 46));
link->SetFont(::Label::Body_12);
link->Wrap(FromDIP(400));
link->Bind(wxEVT_ENTER_WINDOW, [this](auto &e) { SetCursor(wxCURSOR_HAND); });
link->Bind(wxEVT_LEAVE_WINDOW, [this](auto &e) { SetCursor(wxCURSOR_ARROW); });
link->Bind(wxEVT_LEFT_DOWN, [wiki_url](auto &event) { wxLaunchDefaultBrowser(wiki_url); });
messages_sizer->Add(link, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(5));
}
message_count++;
}
content_sizer->Add(messages_sizer, 1, wxEXPAND | wxALL, FromDIP(5));
}
} // namespace GUI
} // namespace Slic3r
+30
View File
@@ -67,6 +67,10 @@ struct MsgDialog : DPIDialog
bool get_checkbox_state();
virtual void on_dpi_changed(const wxRect& suggested_rect);
void SetButtonLabel(wxWindowID btn_id, const wxString& label, bool set_focus = false);
// Public wrapper around add_button — lets callers append custom-labelled choice buttons to an
// already-constructed dialog (used by the H2C rack hotend "Jump to the upgrade page" prompt).
// Purely additive; existing dialogs are unaffected.
void AddButton(wxWindowID btn_id, const wxString& label, bool set_focus = false) { add_button(btn_id, set_focus, label); }
protected:
enum {
@@ -434,6 +438,32 @@ public:
bool m_show_again{false};
};
// Multi-item filament-blacklist warning dialog with per-item wiki links. Replaces the plain
// MessageDialog used for warnings so stacked accumulate-all warnings render with their own
// optional Wiki link.
struct FilamentWarningInfo
{
wxString info_msg;
wxString wiki_url;
};
class FilamentWarningDialog : public MsgDialog
{
public:
FilamentWarningDialog(wxWindow *parent, const wxString &title, std::vector<FilamentWarningInfo> infos);
FilamentWarningDialog(FilamentWarningDialog &&) = delete;
FilamentWarningDialog(const FilamentWarningDialog &) = delete;
FilamentWarningDialog &operator=(FilamentWarningDialog &&) = delete;
FilamentWarningDialog &operator=(const FilamentWarningDialog &) = delete;
virtual ~FilamentWarningDialog() = default;
private:
void BuildContent();
private:
std::vector<FilamentWarningInfo> m_messages;
};
}
}
+81 -1
View File
@@ -3,7 +3,9 @@
#include <numeric>
#include <vector>
#include <string>
#include <sstream>
#include <regex>
#include "libslic3r/MultiNozzleUtils.hpp"
#include <future>
#include <glad/gl.h>
#include <boost/algorithm/string.hpp>
@@ -66,6 +68,8 @@ static const int PARTPLATE_PLATENAME_OFFSET_Y = 10;
const float WIPE_TOWER_DEFAULT_X_POS = 165.;
const float WIPE_TOWER_DEFAULT_Y_POS = 250.; // Max y
const float N9_WIPE_TOWER_DEFAULT_Y_POS = 160.;
const float I3_WIPE_TOWER_DEFAULT_X_POS = 0.;
const float I3_WIPE_TOWER_DEFAULT_Y_POS = 250.; // Max y
@@ -4221,6 +4225,12 @@ void PartPlateList::set_default_wipe_tower_pos_for_plate(int plate_idx, bool ini
y = I3_WIPE_TOWER_DEFAULT_Y_POS;
}
std::string printer_type = wxGetApp().preset_bundle->printers.get_edited_preset().get_printer_type(wxGetApp().preset_bundle);
// Note: printer_type == "N9" and printer_structure_opt->value == PrinterStructure::psI3 can both be true
if (printer_type == "N9") {
y = N9_WIPE_TOWER_DEFAULT_Y_POS;
}
PartPlate *part_plate = get_plate(plate_idx);
Vec3d plate_origin = part_plate->get_origin();
BoundingBoxf3 plate_bbox = part_plate->get_bounding_box();
@@ -6279,6 +6289,58 @@ int PartPlateList::load_from_3mf_structure(PlateDataPtrs& plate_data_list, int f
m_plate_list[index]->slice_filaments_info = plate_data_list[i]->slice_filaments_info;
gcode_result->warnings = plate_data_list[i]->warnings;
gcode_result->filament_maps = plate_data_list[i]->filament_maps;
// Reconstruct the device-side nozzle grouping from the loaded 3mf so
// the monitor/preview can map filaments to physical nozzles.
// load_nozzle_infos_with_compatibility handles older single-nozzle 3mf (no <nozzle> tags) by
// falling back to the per-filament group_id, then to the extruder volume types / nozzle diameters.
{
// nozzle_volume_types is space-separated ints; nozzle_diameters is comma/space-separated floats.
auto parse_int_tokens = [](const std::string& str) -> std::vector<int> {
std::vector<int> out;
std::istringstream iss(str);
std::string tok;
while (iss >> tok) { try { out.push_back(std::stoi(tok)); } catch (...) {} }
return out;
};
auto parse_double_tokens = [](const std::string& str) -> std::vector<double> {
std::vector<double> out;
std::string s = str;
std::replace(s.begin(), s.end(), ',', ' ');
std::istringstream iss(s);
std::string tok;
while (iss >> tok) { try { out.push_back(std::stod(tok)); } catch (...) {} }
return out;
};
std::vector<int> nozzle_volume_type_values = parse_int_tokens(plate_data_list[i]->nozzle_volume_types);
std::vector<double> nozzle_diameter_values = parse_double_tokens(plate_data_list[i]->nozzle_diameters);
std::vector<NozzleVolumeType> extruder_volume_types(nozzle_volume_type_values.size(), NozzleVolumeType::nvtStandard);
for (size_t idx = 0; idx < nozzle_volume_type_values.size(); ++idx)
if (nozzle_volume_type_values[idx] >= 0 && nozzle_volume_type_values[idx] <= nvtMaxNozzleVolumeType)
extruder_volume_types[idx] = static_cast<NozzleVolumeType>(nozzle_volume_type_values[idx]);
auto nozzle_infos = MultiNozzleUtils::load_nozzle_infos_with_compatibility(
plate_data_list[i]->nozzles_info,
plate_data_list[i]->slice_filaments_info,
plate_data_list[i]->filament_maps,
extruder_volume_types,
nozzle_diameter_values);
std::vector<int> fil_seq(plate_data_list[i]->filament_change_sequence.begin(), plate_data_list[i]->filament_change_sequence.end());
std::vector<int> noz_seq(plate_data_list[i]->nozzle_change_sequence.begin(), plate_data_list[i]->nozzle_change_sequence.end());
bool enable_filament_dynamic_map = false;
if (plate_data_list[i]->config.has("enable_filament_dynamic_map"))
enable_filament_dynamic_map = plate_data_list[i]->config.option<ConfigOptionBool>("enable_filament_dynamic_map")->value;
auto group_result = MultiNozzleUtils::StaticNozzleGroupResult::create(
plate_data_list[i]->slice_filaments_info, nozzle_infos, fil_seq, noz_seq, enable_filament_dynamic_map);
if (group_result)
gcode_result->nozzle_group_result = std::make_shared<MultiNozzleUtils::StaticNozzleGroupResult>(group_result.value());
else
gcode_result->nozzle_group_result = nullptr;
}
if (m_plater && !plate_data_list[i]->thumbnail_file.empty()) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": plate %1%, load thumbnail from %2%.")%(i+1) %plate_data_list[i]->thumbnail_file;
if (boost::filesystem::exists(plate_data_list[i]->thumbnail_file)) {
@@ -6438,9 +6500,10 @@ void PartPlateList::init_bed_type_info()
auto bed_texture_maps = wxGetApp().plater()->get_bed_texture_maps();
std::string bottom_texture_end_name = bed_texture_maps.find("bottom_texture_end_name") != bed_texture_maps.end() ? bed_texture_maps["bottom_texture_end_name"] : "";
std::string bottom_texture_rect_str = bed_texture_maps.find("bottom_texture_rect") != bed_texture_maps.end() ? bed_texture_maps["bottom_texture_rect"] : "";
std::string bottom_texture_rect_longer_str = bed_texture_maps.find("bottom_texture_rect_longer") != bed_texture_maps.end() ? bed_texture_maps["bottom_texture_rect_longer"] : "";
std::string middle_texture_rect_str = bed_texture_maps.find("middle_texture_rect") != bed_texture_maps.end() ? bed_texture_maps["middle_texture_rect"] : "";
std::string use_double_extruder_default_texture = bed_texture_maps.find("use_double_extruder_default_texture") != bed_texture_maps.end() ? bed_texture_maps["use_double_extruder_default_texture"] : "";
std::array<float, 4> bottom_texture_rect = {0, 0, 0, 0}, middle_texture_rect = {0, 0, 0, 0};
std::array<float, 4> bottom_texture_rect = {0, 0, 0, 0}, bottom_texture_rect_longer = {0, 0, 0, 0}, middle_texture_rect = {0, 0, 0, 0};
if (bottom_texture_rect_str.size() > 0) {
std::vector<std::string> items;
boost::algorithm::erase_all(bottom_texture_rect_str, " ");
@@ -6451,6 +6514,16 @@ void PartPlateList::init_bed_type_info()
}
}
}
if (bottom_texture_rect_longer_str.size() > 0) {
std::vector<std::string> items;
boost::algorithm::erase_all(bottom_texture_rect_longer_str, " ");
boost::split(items, bottom_texture_rect_longer_str, boost::is_any_of(","));
if (items.size() == 4) {
for (int i = 0; i < items.size(); i++) {
bottom_texture_rect_longer[i] = std::atof(items[i].c_str());
}
}
}
if (middle_texture_rect_str.size() > 0) {
std::vector<std::string> items;
boost::algorithm::erase_all(middle_texture_rect_str, " ");
@@ -6471,6 +6544,7 @@ void PartPlateList::init_bed_type_info()
}
pte_part2 = BedTextureInfo::TexturePart(45, -14.5, 70, 8, "bbl_bed_pte_left_bottom.svg");
auto &bottom_rect = bottom_texture_rect;
auto &bottom_rect_longer = bottom_texture_rect_longer;
if (bottom_texture_end_name.size() > 0 && bottom_rect[2] > 0.f) {
std::string pte_part2_name = "bbl_bed_pte_bottom_" + bottom_texture_end_name + ".svg";
pte_part2 = BedTextureInfo::TexturePart(bottom_rect[0], bottom_rect[1], bottom_rect[2], bottom_rect[3], pte_part2_name);
@@ -6494,6 +6568,9 @@ void PartPlateList::init_bed_type_info()
if (bottom_texture_end_name.size() > 0 && bottom_rect[2] > 0.f) {
std::string st_part2_name = "bbl_bed_st_bottom_" + bottom_texture_end_name + ".svg";
st_part2 = BedTextureInfo::TexturePart(bottom_rect[0], bottom_rect[1], bottom_rect[2], bottom_rect[3], st_part2_name);
} else if (bottom_rect_longer[2] > 0.f) {
// SuperTack bottom strip uses the wider "longer" rect.
st_part2.update_pos(bottom_rect_longer[0], bottom_rect_longer[1], bottom_rect_longer[2], bottom_rect_longer[3]);
}
ep_part1 = BedTextureInfo::TexturePart(57, 300, 236.12f, 10.f, "bbl_bed_ep_middle.svg");
@@ -6504,6 +6581,9 @@ void PartPlateList::init_bed_type_info()
if (bottom_texture_end_name.size() > 0 && bottom_rect[2] > 0.f) {
std::string ep_part2_name = "bbl_bed_ep_bottom_" + bottom_texture_end_name + ".svg";
ep_part2 = BedTextureInfo::TexturePart(bottom_rect[0], bottom_rect[1], bottom_rect[2], bottom_rect[3], ep_part2_name);
} else if (bottom_rect_longer[2] > 0.f) {
// Engineering-plate bottom strip uses the wider "longer" rect.
ep_part2.update_pos(bottom_rect_longer[0], bottom_rect_longer[1], bottom_rect_longer[2], bottom_rect_longer[3]);
}
pc_part1 = BedTextureInfo::TexturePart(57, 300, 236.12f, 10.f, "bbl_bed_pc_middle.svg");
+6
View File
@@ -668,6 +668,12 @@ public:
this->filename = part.filename;
this->texture = part.texture;
}
void update_pos(float xx, float yy, float ww, float hh) {
x = xx;
y = yy;
w = ww;
h = hh;
}
void update_file(std::string file) {
filename = file;
}
+359 -18
View File
@@ -122,6 +122,8 @@
#include "NotificationManager.hpp"
#include "PresetComboBoxes.hpp"
#include "MsgDialog.hpp"
#include "Widgets/MultiNozzleSync.hpp" // NozzleOption, tryPopUpMultiNozzleDialog, setExtruderNozzleCount
#include "DeviceCore/DevNozzleSystem.h" // DevNozzle, GetExtNozzles / GetRackNozzles
#include "ProjectDirtyStateManager.hpp"
#include "Gizmos/GLGizmoSimplify.hpp" // create suggestion notification
#include "Gizmos/GLGizmoSVG.hpp" // Drop SVG file
@@ -584,7 +586,11 @@ struct Sidebar::priv
void jump_to_object(ObjectDataViewModelNode* item);
void can_search();
bool sync_extruder_list(bool &only_external_material);
bool sync_extruder_list(bool &only_external_material, bool is_manual = false);
// Resolve the nozzle option for a multi-nozzle machine. Returns nullopt (and is a no-op) unless
// extruder_count >= 2 && support_multi_nozzle. When is_manual, always pops the MultiNozzleSyncDialog;
// otherwise reuses the app_config-cached option when the machine's nozzle config is unchanged.
std::optional<NozzleOption> get_nozzle_options(MachineObject* obj, int extruder_count, bool support_multi_nozzle, bool is_manual);
bool switch_diameter(bool single);
void update_sync_status(const MachineObject* obj);
@@ -1323,7 +1329,288 @@ static bool is_skip_high_flow_printer(const std::string& printer)
return invalidate_list.count(printer);
};
bool Sidebar::priv::sync_extruder_list(bool &only_external_material)
// ---- Multi-nozzle sync helpers ----------------------------
// Serialize/deserialize the machine nozzle config + chosen NozzleOption for the app_config
// "sync_extruder" reuse cache, and Sidebar::priv::get_nozzle_options (the nozzle picker).
static std::string serialize_nozzle_config(const std::map<int, std::vector<DevNozzle>>& nozzle_cfg_map)
{
std::ostringstream oss;
std::vector<DevNozzle> deputy_nozzles;
auto deputy_it = nozzle_cfg_map.find(1);
if (deputy_it != nozzle_cfg_map.end()) {
deputy_nozzles = deputy_it->second;
}
std::vector<DevNozzle> main_nozzles;
auto main_it = nozzle_cfg_map.find(0);
if (main_it != nozzle_cfg_map.end()) {
main_nozzles = main_it->second;
}
for (size_t i = 0; i < deputy_nozzles.size(); ++i) {
if (i > 0) oss << ";";
oss << std::fixed << std::setprecision(1) << deputy_nozzles[i].GetNozzleDiameter() << ","
<< static_cast<int>(deputy_nozzles[i].GetNozzleFlowType());
}
oss << "|";
for (size_t i = 0; i < main_nozzles.size(); ++i) {
if (i > 0) oss << ";";
oss << std::fixed << std::setprecision(1) << main_nozzles[i].GetNozzleDiameter() << ","
<< static_cast<int>(main_nozzles[i].GetNozzleFlowType());
}
return oss.str();
}
static std::map<int, std::vector<DevNozzle>> deserialize_nozzle_config(const std::string &config_str)
{
std::map<int, std::vector<DevNozzle>> nozzle_cfg_map;
if (config_str.empty()) return nozzle_cfg_map;
std::vector<std::string> extruder_parts;
boost::split(extruder_parts, config_str, boost::is_any_of("|"));
auto get_nozzles_from_string = [](const std::string& part_str) -> std::vector<DevNozzle> {
std::vector<DevNozzle> nozzles;
std::vector<std::string> parts;
boost::split(parts, part_str, boost::is_any_of(";"));
for (const auto &part : parts) {
std::vector<std::string> values;
boost::split(values, part, boost::is_any_of(","));
if (values.size() == 2) {
DevNozzle nozzle;
nozzle.m_diameter = std::stof(values[0]);
nozzle.m_nozzle_flow = static_cast<NozzleFlowType>(std::stoi(values[1]));
nozzles.push_back(nozzle);
}
}
return nozzles;
};
if (extruder_parts.size() != 2) {
auto nozzles = get_nozzles_from_string(config_str);
nozzle_cfg_map[MAIN_EXTRUDER_ID] = nozzles;
nozzle_cfg_map[DEPUTY_EXTRUDER_ID] = { DevNozzle() };
return nozzle_cfg_map;
}
if (!extruder_parts[0].empty()) {
auto nozzles = get_nozzles_from_string(extruder_parts[0]);
nozzle_cfg_map[DEPUTY_EXTRUDER_ID] = nozzles;
}
if (!extruder_parts[1].empty()) {
auto nozzles = get_nozzles_from_string(extruder_parts[1]);
nozzle_cfg_map[MAIN_EXTRUDER_ID] = nozzles;
}
return nozzle_cfg_map;
}
static bool is_same_nozzle_config(const std::map<int, std::vector<DevNozzle>> &config1, const std::map<int, std::vector<DevNozzle>> &config2)
{
if (config1.size() != config2.size()) return false;
for (const auto& [eid, nozzles1] : config1) {
auto it = config2.find(eid);
if (it == config2.end()) return false;
const auto &nozzles2 = it->second;
if (nozzles1.size() != nozzles2.size()) return false;
auto sorted_nozzles1 = nozzles1;
auto sorted_nozzles2 = nozzles2;
auto compare_nozzle = [](const DevNozzle &a, const DevNozzle &b) {
float dia_a = a.GetNozzleDiameter();
float dia_b = b.GetNozzleDiameter();
if (std::abs(dia_a - dia_b) > EPSILON) {
return dia_a < dia_b;
}
return static_cast<int>(a.GetNozzleFlowType()) < static_cast<int>(b.GetNozzleFlowType());
};
std::sort(sorted_nozzles1.begin(), sorted_nozzles1.end(), compare_nozzle);
std::sort(sorted_nozzles2.begin(), sorted_nozzles2.end(), compare_nozzle);
for (size_t i = 0; i < sorted_nozzles1.size(); ++i) {
if (std::abs(sorted_nozzles1[i].GetNozzleDiameter() - sorted_nozzles2[i].GetNozzleDiameter()) > EPSILON ||
sorted_nozzles1[i].GetNozzleFlowType() != sorted_nozzles2[i].GetNozzleFlowType()) {
return false;
}
}
}
return true;
}
static std::string serialize_nozzle_option(const NozzleOption& option) {
std::ostringstream oss;
oss << option.diameter << "|";
bool first = true;
for (const auto& pair : option.extruder_nozzle_stats) {
if (!first) oss << ";";
first = false;
oss << pair.first << ":";
bool first_stat = true;
for (const auto& stat_pair : pair.second) {
if (!first_stat) oss << ",";
first_stat = false;
oss << static_cast<int>(stat_pair.first) << "#" << stat_pair.second;
}
}
return oss.str();
}
static std::optional<NozzleOption> deserialize_nozzle_option(const std::string& option_str) {
if (option_str.empty()) return std::nullopt;
std::vector<std::string> parts;
boost::split(parts, option_str, boost::is_any_of("|"));
if (parts.size() != 2) return std::nullopt;
NozzleOption option;
option.diameter = parts[0];
std::vector<std::string> extruder_parts;
boost::split(extruder_parts, parts[1], boost::is_any_of(";"));
for (const auto& extruder_part : extruder_parts) {
if (extruder_part.empty()) continue;
std::vector<std::string> extruder_data;
boost::split(extruder_data, extruder_part, boost::is_any_of(":"));
if (extruder_data.size() != 2) continue;
int extruder_id = std::stoi(extruder_data[0]);
std::unordered_map<NozzleVolumeType, int> stats;
std::vector<std::string> stat_parts;
boost::split(stat_parts, extruder_data[1], boost::is_any_of(","));
for (const auto& stat_part : stat_parts) {
std::vector<std::string> kv;
boost::split(kv, stat_part, boost::is_any_of("#"));
if (kv.size() == 2) {
NozzleVolumeType type = static_cast<NozzleVolumeType>(std::stoi(kv[0]));
int count = std::stoi(kv[1]);
stats[type] = count;
}
}
option.extruder_nozzle_stats[extruder_id] = stats;
}
return option;
}
std::optional<NozzleOption> Sidebar::priv::get_nozzle_options(MachineObject* obj, int extruder_count, bool support_multi_nozzle, bool is_manual)
{
if (extruder_count < 2 || !support_multi_nozzle) {
return std::nullopt;
}
if (!obj || !obj->GetNozzleSystem()) return std::nullopt;
auto nozzle_system = obj->GetNozzleSystem();
PresetBundle *preset_bundle = wxGetApp().preset_bundle;
if (!preset_bundle) return std::nullopt;
std::string curr_dev_id = obj->get_dev_id();
std::map<int, std::vector<DevNozzle>> curr_nozzle_cfg;
for (const auto& ext_nozzle : nozzle_system->GetExtNozzles()) {
int extruder_id = ext_nozzle.first;
curr_nozzle_cfg[extruder_id].emplace_back(ext_nozzle.second);
}
for (const auto& rack_nozzle : nozzle_system->GetRackNozzles()) {
curr_nozzle_cfg[MAIN_EXTRUDER_ID].emplace_back(rack_nozzle.second);
}
AppConfig *app_config = wxGetApp().app_config;
std::string saved_dev_id = app_config->get("sync_extruder", "dev_id");
std::string saved_nozzle_config_str = app_config->get("sync_extruder", "nozzle_config");
std::string saved_nozzle_option_str = app_config->get("sync_extruder", "nozzle_option");
std::optional<NozzleOption> nozzle_option;
if (is_manual) {
nozzle_option = tryPopUpMultiNozzleDialog(obj);
} else {
bool can_reuse_saved_option = false;
if (!saved_dev_id.empty() && !saved_nozzle_config_str.empty() && !saved_nozzle_option_str.empty() && saved_dev_id == curr_dev_id) {
auto saved_nozzle_config = deserialize_nozzle_config(saved_nozzle_config_str);
if (is_same_nozzle_config(saved_nozzle_config, curr_nozzle_cfg)) {
nozzle_option = deserialize_nozzle_option(saved_nozzle_option_str);
can_reuse_saved_option = nozzle_option.has_value();
}
if (can_reuse_saved_option) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " Reusing saved nozzle option for dev_id: " << curr_dev_id;
auto &project_config = preset_bundle->project_config;
ConfigOptionEnumsGeneric *nozzle_volume_type_opt = project_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type");
// Write to preset bundle config
for (int extruder_id = 0; extruder_id < extruder_count; ++extruder_id) {
NozzleVolumeType volume_type;
int nozzle_count;
bool clear_all = true;
if (!nozzle_option->extruder_nozzle_stats.count(extruder_id)) {
nozzle_count = 0;
// Reset the concrete volume types (Standard/High Flow); Hybrid stays a hidden match-any
// sentinel and is left alone. TPU High Flow is a concrete variant shipped on 0.4/0.6
// nozzles, so reset it too, but only there (mirrors the High-Flow-skip-for-0.2 guard).
std::vector<NozzleVolumeType> reset_types{nvtStandard, nvtHighFlow};
if (extruder_supports_tpu_high_flow(preset_bundle, extruder_id))
reset_types.push_back(nvtTPUHighFlow);
for (NozzleVolumeType vt : reset_types) {
volume_type = vt;
setExtruderNozzleCount(preset_bundle, extruder_id, volume_type, nozzle_count, clear_all);
clear_all = false;
}
} else {
for (auto &stat : nozzle_option->extruder_nozzle_stats[extruder_id]) {
volume_type = stat.first;
nozzle_count = stat.second;
setExtruderNozzleCount(preset_bundle, extruder_id, volume_type, nozzle_count, clear_all);
clear_all = false;
}
}
}
// Orca: nozzle stats are persisted directly into the preset's `extruder_nozzle_stats` key;
// there is no machine/user data-source flag on the stats.
}
}
if (!can_reuse_saved_option) {
nozzle_option = tryPopUpMultiNozzleDialog(obj);
}
}
if (nozzle_option) {
std::string current_nozzle_config = serialize_nozzle_config(curr_nozzle_cfg);
std::string current_nozzle_option = serialize_nozzle_option(*nozzle_option);
if (app_config->has_section("sync_extruder")) {
app_config->set("sync_extruder", "dev_id", curr_dev_id);
app_config->set("sync_extruder", "nozzle_config", current_nozzle_config);
app_config->set("sync_extruder", "nozzle_option", current_nozzle_option);
} else {
std::map<std::string, std::string> data;
data["dev_id"] = curr_dev_id;
data["nozzle_config"] = current_nozzle_config;
data["nozzle_option"] = current_nozzle_option;
app_config->set_section("sync_extruder", data);
}
}
return nozzle_option;
}
bool Sidebar::priv::sync_extruder_list(bool &only_external_material, bool is_manual)
{
MachineObject *obj = wxGetApp().getDeviceManager()->get_selected_machine();
auto printer_name = plater->get_selected_printer_name_in_combox();
@@ -1377,13 +1664,51 @@ bool Sidebar::priv::sync_extruder_list(bool &only_external_material)
}
assert(obj->GetExtderSystem()->GetTotalExtderCount() == extruder_nums);
// Multi-nozzle: resolve the nozzle option (pops MultiNozzleSyncDialog when is_manual). No-op for every
// existing printer — support_multi_nozzle is false unless some extruder_max_nozzle_count entry is a real
// value > 1 (nil-guarded, matching the manual/ToolOrdering gates), which no shipping single-nozzle or
// dual-extruder profile sets. When the machine is multi-nozzle but no option resolves (e.g. user cancelled),
// abort the sync.
const ConfigOptionIntsNullable *extruder_max_nozzle_count = cur_preset.config.option<ConfigOptionIntsNullable>("extruder_max_nozzle_count");
bool support_multi_nozzle = extruder_max_nozzle_count != nullptr &&
std::any_of(extruder_max_nozzle_count->values.begin(), extruder_max_nozzle_count->values.end(),
[](int val) { return val > 1 && val != ConfigOptionIntsNullable::nil_value(); });
auto nozzle_option = get_nozzle_options(obj, extruder_nums, support_multi_nozzle, is_manual);
if (!nozzle_option && support_multi_nozzle)
return false;
std::vector<float> nozzle_diameters;
nozzle_diameters.resize(extruder_nums);
std::vector<NozzleVolumeType> target_types(extruder_nums, NozzleVolumeType::nvtStandard);
for (size_t index = 0; index < extruder_nums; ++index) {
int extruder_id = extruder_map[index];
nozzle_diameters[extruder_id] = obj->GetExtderSystem()->GetNozzleDiameter(index);
nozzle_diameters[extruder_id] = nozzle_option ? atof(nozzle_option->diameter.c_str()) : obj->GetExtderSystem()->GetNozzleDiameter(index);
NozzleVolumeType target_type = NozzleVolumeType::nvtStandard;
auto printer_tab = dynamic_cast<TabPrinter *>(wxGetApp().get_tab(Preset::TYPE_PRINTER));
std::optional<NozzleVolumeType> select_type;
if (nozzle_option && nozzle_option->extruder_nozzle_stats.count(index)) {
const auto &stats = nozzle_option->extruder_nozzle_stats[index];
if (stats.size() > 1) {
// Orca: for a mixed extruder, keep Hybrid out of the (still-deferred) nozzle-centric slicing
// pipeline, so collapse to the dominant concrete volume type (tie -> Standard).
// TPU High Flow is shipped on 0.4/0.6 nozzles, so only fold it in there (mirrors the
// High-Flow-skip-for-0.2 guard). Uses the device-reported diameter, same predicate as the preset path.
int std_cnt = 0, hf_cnt = 0, tpu_hf_cnt = 0;
for (const auto &kv : stats) {
if (kv.first == nvtStandard) std_cnt = kv.second;
else if (kv.first == nvtHighFlow) hf_cnt = kv.second;
else if (kv.first == nvtTPUHighFlow) tpu_hf_cnt = kv.second;
}
if (!nozzle_diameter_supports_tpu_high_flow(nozzle_diameters[extruder_id]))
tpu_hf_cnt = 0;
select_type = nvtStandard;
if (hf_cnt > std_cnt && hf_cnt >= tpu_hf_cnt)
select_type = nvtHighFlow;
else if (tpu_hf_cnt > std_cnt && tpu_hf_cnt > hf_cnt)
select_type = nvtTPUHighFlow;
} else {
select_type = stats.begin()->first;
}
}
if (obj->is_nozzle_flow_type_supported()) {
if (obj->GetExtderSystem()->GetNozzleFlowType(index) == NozzleFlowType::NONE_FLOWTYPE) {
MessageDialog dlg(this->plater, _L("There are unset nozzle types. Please set the nozzle types of all extruders before synchronizing."),
@@ -1393,9 +1718,13 @@ bool Sidebar::priv::sync_extruder_list(bool &only_external_material)
}
// hack code, only use standard flow for 0.2
if (std::fabs(nozzle_diameters[extruder_id] - 0.2) > EPSILON)
target_type = NozzleVolumeType(obj->GetExtderSystem()->GetNozzleFlowType(extruder_id) - 1);
// Map device flow->volume via the table, not `flowtype - 1`.
// The arithmetic only aligns for S_FLOW/H_FLOW; U_FLOW(3)-1 would yield nvtHybrid(2), not nvtTPUHighFlow(3).
target_type = DevNozzle::ToNozzleVolumeType(obj->GetExtderSystem()->GetNozzleFlowType(extruder_id));
}
printer_tab->set_extruder_volume_type(index, target_type);
if (select_type)
target_type = *select_type;
target_types[index] = target_type;
}
int deputy_4 = 0, main_4 = 0, deputy_1 = 0, main_1 = 0;
@@ -1439,6 +1768,12 @@ bool Sidebar::priv::sync_extruder_list(bool &only_external_material)
is_switching_diameter = false;
}
// set nozzle volume type after switching prset, so this value can override the old value stored in conf
auto printer_tab = dynamic_cast<TabPrinter *>(wxGetApp().get_tab(Preset::TYPE_PRINTER));
for (size_t idx = 0; idx < target_types.size(); ++idx) {
printer_tab->set_extruder_volume_type(idx, target_types[idx]);
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << " finish sync_extruder_list";
return true;
}
@@ -3745,15 +4080,13 @@ void Sidebar::sync_ams_list(bool is_from_big_sync_btn)
{ // badge ams filament
clear_combos_filament_badge();
if (sync_result.direct_sync) {
auto& ams_list = wxGetApp().preset_bundle->filament_ams_list;
size_t tray_idx = 0;
for (auto& entry : ams_list) {
if (tray_idx >= p->combos_filament.size()) break;
auto filament_id = entry.second.opt_string("filament_id", 0u);
if (!filament_id.empty()) {
badge_combox_filament(p->combos_filament[tray_idx]);
}
tray_idx++;
// Orca: PresetBundle::sync_ams_list rebuilds combos_filament
// 1:1 from the AMS trays that produce a combo (loaded trays + placeholders; non-placeholder
// empty trays are skipped), so every resulting combo is AMS-sourced and gets a badge. The
// previous per-tray index walked the full filament_ams_list (including the skipped empties),
// so an empty slot before a loaded one dropped the badge for the trailing filaments.
for (auto &c : p->combos_filament) {
badge_combox_filament(c);
}
}
}
@@ -3948,7 +4281,8 @@ bool Sidebar::is_multifilament()
void Sidebar::deal_btn_sync() {
m_begin_sync_printer_status = true;
bool only_external_material;
auto ok = p->sync_extruder_list(only_external_material);
// Manual "sync machine" button: is_manual=true so an H2C pops the MultiNozzleSyncDialog to pick a nozzle option.
auto ok = p->sync_extruder_list(only_external_material, true);
if (ok) {
pop_sync_nozzle_and_ams_dialog();
}
@@ -5641,6 +5975,9 @@ std::map<std::string, std::string> Plater::get_bed_texture_maps()
if (pm->bottom_texture_rect.size() > 0) {
maps["bottom_texture_rect"] = pm->bottom_texture_rect;
}
if (pm->bottom_texture_rect_longer.size() > 0) {
maps["bottom_texture_rect_longer"] = pm->bottom_texture_rect_longer;
}
if (pm->middle_texture_rect.size() > 0) {
maps["middle_texture_rect"] = pm->middle_texture_rect;
}
@@ -11112,8 +11449,9 @@ bool Plater::priv::check_ams_status_impl(bool is_slice_all)
auto nozzle_volumes_values = preset_bundle->project_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type")->values;
assert(obj->GetExtderSystem()->GetTotalExtderCount() == 2 && nozzle_volumes_values.size() == 2);
if (obj->GetExtderSystem()->GetTotalExtderCount() == 2 && nozzle_volumes_values.size() == 2) {
NozzleVolumeType right_nozzle_type = NozzleVolumeType(obj->GetExtderSystem()->GetNozzleFlowType(0) - 1);
NozzleVolumeType left_nozzle_type = NozzleVolumeType(obj->GetExtderSystem()->GetNozzleFlowType(1) - 1);
// Map device flow->volume via the table, not `flowtype - 1` (which mis-maps U_FLOW to nvtHybrid).
NozzleVolumeType right_nozzle_type = DevNozzle::ToNozzleVolumeType(obj->GetExtderSystem()->GetNozzleFlowType(0));
NozzleVolumeType left_nozzle_type = DevNozzle::ToNozzleVolumeType(obj->GetExtderSystem()->GetNozzleFlowType(1));
NozzleVolumeType preset_left_type = NozzleVolumeType(nozzle_volumes_values[0]);
NozzleVolumeType preset_right_type = NozzleVolumeType(nozzle_volumes_values[1]);
is_same_as_printer = (left_nozzle_type == preset_left_type && right_nozzle_type == preset_right_type);
@@ -17317,6 +17655,9 @@ void Plater::suppress_background_process(const bool stop_background_process)
this->p->suppressed_backround_processing_update = true;
}
// Expose the slicing process to the device GUI.
BackgroundSlicingProcess& Plater::background_process() { return p->background_process; }
void Plater::center_selection() { p->center_selection(); }
void Plater::drop_selection() { p->drop_selection(); }
void Plater::mirror(Axis axis) { p->mirror(axis); }
+4
View File
@@ -50,6 +50,7 @@ class SLAPrint;
//BBS: add partplatelist and SlicingStatusEvent
class PartPlateList;
class SlicingStatusEvent;
class BackgroundSlicingProcess;
enum SLAPrintObjectStep : unsigned int;
enum class ConversionType : int;
class DevAms;
@@ -519,6 +520,9 @@ public:
void schedule_background_process(bool schedule = true);
bool is_background_process_update_scheduled() const;
void suppress_background_process(const bool stop_background_process) ;
// Expose the slicing process so the device GUI can read the current
// GCodeProcessorResult (e.g. the nozzle grouping for print-dispatch mapping).
BackgroundSlicingProcess& background_process();
/* -1: send current gcode if not specified
* -2: send all gcode to target machine */
int send_gcode(int plate_idx = -1, Export3mfProgressFn proFn = nullptr);
+3
View File
@@ -36,6 +36,8 @@ std::string PrePrintChecker::get_print_status_info(PrintDialogStatus status)
case PrintStatusNotSupportedPrintAll: return "PrintStatusNotSupportedPrintAll";
case PrintStatusBlankPlate: return "PrintStatusBlankPlate";
case PrintStatusUnsupportedPrinter: return "PrintStatusUnsupportedPrinter";
case PrintStatusRackNozzleMappingWaiting: return "PrintStatusRackNozzleMappingWaiting";
case PrintStatusRackNozzleMappingError: return "PrintStatusRackNozzleMappingError";
case PrintStatusColorQuantityExceed: return "PrintStatusColorQuantityExceed";
// Handle filament errors
case PrintStatusAmsOnSettingup: return "PrintStatusAmsOnSettingup";
@@ -47,6 +49,7 @@ std::string PrePrintChecker::get_print_status_info(PrintDialogStatus status)
case PrintStatusTimelapseNoSdcard: return "PrintStatusTimelapseNoSdcard";
case PrintStatusTimelapseWarning: return "PrintStatusTimelapseWarning";
case PrintStatusMixAmsAndVtSlotWarning: return "PrintStatusMixAmsAndVtSlotWarning";
case PrintStatusRackNozzleMappingWarning: return "PrintStatusRackNozzleMappingWarning";
case PrintStatusWarningKvalueNotUsed: return "PrintStatusWarningKvalueNotUsed";
case PrintStatusHasFilamentInBlackListWarning: return "PrintStatusHasFilamentInBlackListWarning";
case PrintStatusFilamentWarningHighChamberTemp: return "PrintStatusFilamentWarningHighChamberTemp";
+3
View File
@@ -65,6 +65,8 @@ enum PrintDialogStatus : unsigned int {
PrintStatusNotSupportedPrintAll,
PrintStatusBlankPlate,
PrintStatusUnsupportedPrinter,
PrintStatusRackNozzleMappingWaiting,
PrintStatusRackNozzleMappingError,
PrintStatusPrinterErrorEnd,
// Errors for filament, Block Print
@@ -89,6 +91,7 @@ enum PrintDialogStatus : unsigned int {
PrintStatusTimelapseWarning,
PrintStatusMixAmsAndVtSlotWarning,
PrintStatusToolHeadCoolingFanWarning,
PrintStatusRackNozzleMappingWarning,
PrintStatusPrinterWarningEnd,
// Warnings for filament
+10 -1
View File
@@ -1307,7 +1307,16 @@ void PlaterPresetComboBox::update()
bool selected_in_ams = false;
if (m_type == Preset::TYPE_FILAMENT) {
set_replace_text("Bambu", "BambuStudioBlack");
selected_in_ams = add_ams_filaments(into_u8(selected_user_preset.empty() ? selected_system_preset : selected_user_preset), true);
// Orca: selected_system/user_preset hold the FULL preset name because Orca keys the maps above by
// full name to avoid alias collisions (BBS keys by alias). add_ams_filaments() compares against
// get_preset_name() which returns the alias, so resolve the selection back to its alias here.
// Without this, e.g. "Bambu PLA Basic @BBL H2C" never equals the AMS tray alias "Bambu PLA Basic",
// so the FROM_AMS flag is never set and update_sync_status() wipes the AMS sync check mark on the
// filament cards for connected Bambu printers.
wxString selected_full = selected_user_preset.empty() ? selected_system_preset : selected_user_preset;
auto alias_it = preset_aliases.find(selected_full);
wxString selected_alias = alias_it != preset_aliases.end() ? from_u8(alias_it->second) : selected_full;
selected_in_ams = add_ams_filaments(into_u8(selected_alias), true);
}
std::vector<std::string> filament_orders = {"Bambu PLA Basic", "Bambu PLA Matte", "Bambu PETG HF", "Bambu ABS", "Bambu PLA Silk", "Bambu PLA-CF",
+1
View File
@@ -1426,6 +1426,7 @@ wxString PrinterPartsDialog::GetString(NozzleFlowType nozzle_flow_type) const {
switch (nozzle_flow_type) {
case Slic3r::S_FLOW: return _L("Standard");
case Slic3r::H_FLOW: return _L("High flow");
case Slic3r::U_FLOW: return _L("TPU High flow");
default: break;
}
+436 -26
View File
@@ -20,12 +20,16 @@
#include "DeviceCore/DevConfig.h"
#include "DeviceCore/DevNozzleSystem.h"
#include "DeviceCore/DevNozzleRack.h"
#include "DeviceCore/DevExtensionTool.h"
#include "DeviceCore/DevExtruderSystem.h"
#include "DeviceCore/DevFilaBlackList.h"
#include "DeviceCore/DevFilaSystem.h"
#include "DeviceCore/DevFilaSwitch.h"
#include "DeviceCore/DevManager.h"
#include "DeviceCore/DevMapping.h"
#include "DeviceCore/DevUtilBackend.h"
#include "DeviceCore/DevMappingNozzle.h"
#include "DeviceCore/DevStorage.h"
#include <wx/progdlg.h>
@@ -62,12 +66,22 @@ std::string get_nozzle_volume_type_cloud_string(NozzleVolumeType nozzle_volume_t
else if (nozzle_volume_type == NozzleVolumeType::nvtHighFlow) {
return "high_flow";
}
else if (nozzle_volume_type == NozzleVolumeType::nvtTPUHighFlow) {
return "tpu_high_flow";
}
else if (nozzle_volume_type == NozzleVolumeType::nvtHybrid) {
// to be supported
return "hybrid_flow";
}
else {
assert(false);
return "";
}
}
// Throttle so the rack print-dispatch nozzle-mapping request isn't re-sent on every status poll.
static int s_nozzle_mapping_last_request_time = 0;
std::vector<wxString> SelectMachineDialog::MACHINE_BED_TYPE_STRING;
std::vector<string> SelectMachineDialog::MachineBedTypeString;
void SelectMachineDialog::init_machine_bed_types()
@@ -558,7 +572,11 @@ SelectMachineDialog::SelectMachineDialog(Plater *plater)
m_checkbox_list["flow_cali"] = option_flow_dynamics_cali;
m_checkbox_list["nozzle_offset_cali"] = option_nozzle_offset_cali_cali;
for (auto print_opt : m_checkbox_list_order) {
print_opt->Bind(EVT_SWITCH_PRINT_OPTION, [this](auto &e) { save_option_vals(); });
print_opt->Bind(EVT_SWITCH_PRINT_OPTION, [this, print_opt](auto &e) {
save_option_vals();
// Flow calibration feeds the printer-side rack nozzle mapping; re-request it on change.
if (print_opt == m_checkbox_list["flow_cali"]) on_flow_cali_option_changed();
});
}
option_auto_bed_level->Hide();
@@ -932,13 +950,30 @@ void SelectMachineDialog::finish_mode()
void SelectMachineDialog::sync_ams_mapping_result(std::vector<FilamentInfo> &result)
{
// A rack / filament-switcher printer grows its filament cards to show the mapped-nozzle row.
// Reflow the grid so the taller cards aren't clipped; no-op for printers that never show it.
auto relayout_nozzle_cards = [this]() {
DeviceManager* dev = wxGetApp().getDeviceManager();
MachineObject* obj_ = dev ? dev->get_selected_machine() : nullptr;
if (!obj_) return;
DevNozzleSystem* ns = obj_->GetNozzleSystem();
if (!obj_->GetFilaSwitch()->IsInstalled() && !(ns && ns->GetNozzleRack()->IsSupported())) return;
if (m_sizer_ams_mapping_left) { m_sizer_ams_mapping_left->Layout(); m_filament_panel_left_sizer->Layout(); m_filament_left_panel->Layout(); }
if (m_sizer_ams_mapping_right) { m_sizer_ams_mapping_right->Layout(); m_filament_panel_right_sizer->Layout(); m_filament_right_panel->Layout(); }
if (m_sizer_ams_mapping) { m_sizer_ams_mapping->Layout(); m_filament_panel_sizer->Layout(); m_filament_panel->Layout(); }
if (m_scroll_area) { m_scroll_area->Layout(); }
Layout();
};
if (result.empty()) {
BOOST_LOG_TRIVIAL(info) << "ams_mapping result is empty";
for (auto it = m_materialList.begin(); it != m_materialList.end(); it++) {
wxString ams_id = "Ext";//
wxColour ams_col = wxColour(0xCE, 0xCE, 0xCE);
it->second->item->set_ams_info(ams_col, ams_id);
it->second->item->set_nozzle_info(get_mapped_nozzle_str(it->first));
}
relayout_nozzle_cards();
return;
}
@@ -975,11 +1010,13 @@ void SelectMachineDialog::sync_ams_mapping_result(std::vector<FilamentInfo> &res
cols.push_back(DevAmsTray::decode_color(col));
}
m->set_ams_info(ams_col, ams_id,f->ctype, cols);
m->set_nozzle_info(get_mapped_nozzle_str(id));
break;
}
iter++;
}
}
relayout_nozzle_cards();
auto tab_index = (MainFrame::TabPosition) dynamic_cast<Notebook *>(wxGetApp().tab_panel())->GetSelection();
if (tab_index == MainFrame::TabPosition::tp3DEditor || tab_index == MainFrame::TabPosition::tpPreview) {
updata_thumbnail_data_after_connected_printer();
@@ -1378,6 +1415,7 @@ bool SelectMachineDialog::is_nozzle_type_match(DevExtderSystem data, wxString& e
if (nozzle_volume_type_opt) {
NozzleVolumeType nozzle_volume_type = (NozzleVolumeType) (nozzle_volume_type_opt->get_at(used_extruders[i]));
if (nozzle_volume_type == NozzleVolumeType::nvtStandard) { used_extruders_flow[used_extruders[i]] = "Standard";}
else if (nozzle_volume_type == NozzleVolumeType::nvtTPUHighFlow) { used_extruders_flow[used_extruders[i]] = "TPU High Flow";}
else {used_extruders_flow[used_extruders[i]] = "High Flow";}
}
}
@@ -1397,6 +1435,10 @@ bool SelectMachineDialog::is_nozzle_type_match(DevExtderSystem data, wxString& e
{
flow_type_of_machine.push_back(L("Standard"));
}
else if (it.GetNozzleFlowType() == NozzleFlowType::U_FLOW)
{
flow_type_of_machine.push_back(L("TPU High Flow"));
}
}
//Only when all preset nozzle types and machine nozzle types are exactly the same, return true.
@@ -1443,6 +1485,279 @@ int SelectMachineDialog::convert_filament_map_nozzle_id_to_task_nozzle_id(int no
}
}
// Physical nozzle(s) a mapped filament prints on. key: nozzle pos id, value: nozzle.
// (fila_id is the filament index, i.e. FilamentInfo::id.) Non-rack printers resolve from the
// filament->extruder map; a rack printer (H2C) resolves from the print-dispatch mapping the printer
// returned. Empty when no mapping is available yet — the per-nozzle blacklist check then skips it.
std::map<int, DevNozzle> SelectMachineDialog::get_mapped_nozzles(int fila_id) const
{
std::map<int, DevNozzle> nozzle_map;
DeviceManager* dev = wxGetApp().getDeviceManager();
if (!dev) { return nozzle_map; }
MachineObject* obj_ = dev->get_selected_machine();
if (!obj_) { return nozzle_map; }
int total_ext_count = 0;
if (m_print_type == FROM_NORMAL) {
const auto& full_config = wxGetApp().preset_bundle->full_config();
const auto opt = full_config.option<ConfigOptionFloats>("nozzle_diameter");
total_ext_count = opt ? opt->values.size() : 0;
} else {
const auto opt = m_required_data_config.option<ConfigOptionFloats>("nozzle_diameter");
total_ext_count = opt ? opt->values.size() : 0;
}
if (total_ext_count != obj_->GetExtderSystem()->GetTotalExtderCount()) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": total_ext_count not match";
return nozzle_map;
}
DevNozzleSystem* nozzle_system = obj_->GetNozzleSystem();
if (!nozzle_system) { return nozzle_map; }
if (!nozzle_system->GetNozzleRack()->IsSupported()) {
if (total_ext_count == 1) {
nozzle_map[MAIN_EXTRUDER_ID] = nozzle_system->GetNozzleByPosId(MAIN_EXTRUDER_ID);
} else if (total_ext_count == 2) {
if (fila_id >= 0 && (size_t)fila_id < m_filaments_map.size()) {
if (m_filaments_map[fila_id] == 1) {
nozzle_map[DEPUTY_EXTRUDER_ID] = nozzle_system->GetNozzleByPosId(DEPUTY_EXTRUDER_ID);
} else if (m_filaments_map[fila_id] == 2) {
nozzle_map[MAIN_EXTRUDER_ID] = nozzle_system->GetNozzleByPosId(MAIN_EXTRUDER_ID);
}
}
}
} else if (m_print_type == FROM_NORMAL) {
// Rack printer (H2C): resolve the physical rack nozzle(s) the print-dispatch mapping assigned
// to this filament. Empty until the printer returns the auto-mapping result.
for (int nozzle_pos : obj_->get_nozzle_mapping_result()->GetMappedNozzlePosVecByFilaId(fila_id))
nozzle_map[nozzle_pos] = nozzle_system->GetNozzleByPosId(nozzle_pos);
}
return nozzle_map;
}
wxString SelectMachineDialog::get_mapped_nozzle_str(int fila_id) const
{
if (m_print_type != FROM_NORMAL)
return wxEmptyString; // no slicing data when printing from sdcard
DeviceManager* dev = wxGetApp().getDeviceManager();
if (!dev) { return wxEmptyString; }
MachineObject* obj_ = dev->get_selected_machine();
if (!obj_) { return wxEmptyString; }
DevNozzleSystem* nozzle_system = obj_->GetNozzleSystem();
const bool rack_supported = nozzle_system && nozzle_system->GetNozzleRack()->IsSupported();
const bool fila_switch_installed = obj_->GetFilaSwitch()->IsInstalled();
// A dynamic nozzle map routes filaments through the filament switcher; without one it can't map.
if (use_dynamic_nozzle_map() && !fila_switch_installed)
return "?";
if (fila_switch_installed || rack_supported) {
if (rack_supported) {
return obj_->get_nozzle_mapping_result()->GetMappedNozzlePosStrByFilaId(fila_id);
} else {
const auto& nozzle_map = get_mapped_nozzles(fila_id);
if (nozzle_map.count(MAIN_EXTRUDER_ID) != 0 && nozzle_map.count(DEPUTY_EXTRUDER_ID) != 0)
return "L R";
else if (nozzle_map.count(MAIN_EXTRUDER_ID) != 0)
return "R";
else if (nozzle_map.count(DEPUTY_EXTRUDER_ID) != 0)
return "L";
}
return "?";
}
return wxEmptyString;
}
bool SelectMachineDialog::use_dynamic_nozzle_map() const
{
if (m_print_type == FROM_NORMAL) {
auto nozzle_group_res = DevUtilBackend::GetNozzleGroupResult(m_plater);
if (nozzle_group_res && nozzle_group_res->is_support_dynamic_nozzle_map())
return true;
} else if (m_print_type == FROM_SDCARD_VIEW) {
if (m_required_data_plate_data_list.size() > (size_t) m_print_plate_idx) {
auto dynamic_nozzle_map = m_required_data_plate_data_list[m_print_plate_idx]->config.option<ConfigOptionBool>("enable_filament_dynamic_map");
if (dynamic_nozzle_map)
return dynamic_nozzle_map->value;
}
auto dynamic_nozzle_map = m_required_data_config.option<ConfigOptionBool>("enable_filament_dynamic_map");
if (dynamic_nozzle_map)
return dynamic_nozzle_map->value;
}
return false;
}
void SelectMachineDialog::clear_nozzle_mapping()
{
m_nozzle_mapping_result.clear();
// Orca: no BBS get_current_machine(); use the selected device (same accessor get_mapped_nozzles uses).
DeviceManager* dev = wxGetApp().getDeviceManager();
if (MachineObject* obj_ = dev ? dev->get_selected_machine() : nullptr)
obj_->get_nozzle_mapping_result()->Clear();
}
void SelectMachineDialog::on_flow_cali_option_changed()
{
// Flow calibration feeds the printer-side rack nozzle-mapping computation, so a change must
// invalidate the cached mapping and let the next status poll re-request it (V0 path).
DeviceManager* dev = wxGetApp().getDeviceManager();
MachineObject* obj_ = dev ? dev->get_selected_machine() : nullptr;
if (!obj_ || !(obj_->GetNozzleSystem() && obj_->GetNozzleSystem()->GetNozzleRack()->IsSupported())) return;
if (use_dynamic_nozzle_map()) return;
// Orca: BBS also pops a confirmation dialog and re-fires immediately (and handles a PA-value
// switch Orca lacks); here we just clear + unthrottle so the next poll re-requests promptly.
clear_nozzle_mapping();
s_nozzle_mapping_last_request_time = 0;
}
// Rack print-dispatch nozzle mapping, dynamic-map (V1) path. Fires get_auto_nozzle_mapping when no
// result is cached, blocks Send while the printer computes, then surfaces error/warning. Returns
// false (pre-print check fails / keep waiting) only while blocking; true when the chain may proceed.
bool SelectMachineDialog::CheckErrorSyncNozzleMappingResultV1(MachineObject* obj_)
{
if (m_print_type != FROM_NORMAL)
return true; // there is no slicing data when printing from sdcard
if (!obj_)
return true;
if (!(obj_->GetNozzleSystem() && obj_->GetNozzleSystem()->GetNozzleRack()->IsSupported()))
return true; // no need to check if the printer has no nozzle rack
if (!use_dynamic_nozzle_map())
return true;
const auto& obj_nozzle_mapping_ptr = obj_->get_nozzle_mapping_result();
if (!obj_nozzle_mapping_ptr->HasResult()) {
if (time(nullptr) - s_nozzle_mapping_last_request_time > 10) { // avoid too many requests
int rtn = obj_nozzle_mapping_ptr->CtrlGetAutoNozzleMappingV1(m_plater);
if (rtn == 0) {
s_nozzle_mapping_last_request_time = time(nullptr);
} else {
const auto& err_msg = wxString::Format(_L("Failed to send nozzle auto-mapping request to printer { code: %d }. "
"Please try to refresh the printer information. "
"If it still does not recover, you can try to rebind the printer and check the network connection."), rtn);
show_status(PrintDialogStatus::PrintStatusRackNozzleMappingWaiting, {err_msg});
return false;
}
}
show_status(PrintDialogStatus::PrintStatusRackNozzleMappingWaiting, {_L("The printer is calculating nozzle mapping.") + " " + _L("Please wait a moment...")});
return false;
}
if (obj_nozzle_mapping_ptr->GetResultStr() == "failed") {
const auto& err_msg = wxString::Format(_L("Failed to receive nozzle auto-mapping table from printer { msg: %s }. Please refresh the printer information."), obj_nozzle_mapping_ptr->GetMqttReason());
show_status(PrintDialogStatus::PrintStatusRackNozzleMappingError, {err_msg});
return false;
}
if (obj_nozzle_mapping_ptr->GetResultStr() == "fail") {
const wxString& err_msg = wxString::Format(_L("The printer failed to build the nozzle auto-mapping table { code: %d }. Please refresh nozzle information."), obj_nozzle_mapping_ptr->GetErrno());
show_status(PrintDialogStatus::PrintStatusRackNozzleMappingError, {err_msg});
return false;
}
const auto& mapping_map = obj_->get_nozzle_mapping_result()->GetNozzleMappingMap();
if (!mapping_map.empty()) {
if (m_nozzle_mapping_result != mapping_map) {
m_nozzle_mapping_result = mapping_map;
sync_ams_mapping_result(m_ams_mapping_result);
}
float flush_waste_base = obj_->get_nozzle_mapping_result()->GetFlushWeightBase();
float flush_waste_current = obj_->get_nozzle_mapping_result()->GetFlushWeightCurrent();
if ((flush_waste_base != -1) && (flush_waste_current != -1) && flush_waste_current > flush_waste_base) {
const wxString& warning_msg = wxString::Format(_L("The current nozzle mapping may produce an extra %0.2f g of waste."), flush_waste_current - flush_waste_base);
show_status(PrintDialogStatus::PrintStatusRackNozzleMappingWarning, {warning_msg});
}
return true;
}
const wxString& err_msg = wxString::Format(_L("Failed to receive nozzle auto-mapping table from printer { msg: %s }. Please refresh the printer information."), "empty table");
show_status(PrintDialogStatus::PrintStatusRackNozzleMappingError, {err_msg});
return true;
}
// Rack print-dispatch nozzle mapping, static-rack (V0) path — see CheckErrorSyncNozzleMappingResultV1.
bool SelectMachineDialog::CheckErrorSyncNozzleMappingResultV0(MachineObject* obj_)
{
if (!obj_)
return true;
if (!(obj_->GetNozzleSystem() && obj_->GetNozzleSystem()->GetNozzleRack()->IsSupported()))
return true; // no need to check if the printer has no nozzle rack
if (m_print_type != FROM_NORMAL)
return true; // there is no slicing data when printing from sdcard
if (use_dynamic_nozzle_map())
return true; // handled by the V1 path
auto nozzle_group_res = DevUtilBackend::GetNozzleGroupResult(m_plater);
if (nozzle_group_res && nozzle_group_res->get_used_nozzles_in_extruder(LOGIC_R_EXTRUDER_ID).empty())
return true; // no right-extruder nozzles used in slicing -> nothing to map
const auto& obj_nozzle_mapping_ptr = obj_->get_nozzle_mapping_result();
if (!obj_nozzle_mapping_ptr->HasResult()) {
if (time(nullptr) - s_nozzle_mapping_last_request_time > 10) { // avoid too many requests
// Orca: BBS passes m_pa_value_switch->GetValue() ? 0 : 1; Orca has no PA-value switch UI,
// so use the switch-off default (1).
int rtn = obj_nozzle_mapping_ptr->CtrlGetAutoNozzleMappingV0(m_plater, m_ams_mapping_result, m_checkbox_list["flow_cali"]->getValueInt(), 1);
if (rtn == 0) {
s_nozzle_mapping_last_request_time = time(nullptr);
} else {
const auto& err_msg = wxString::Format(_L("Failed to send nozzle auto-mapping request to printer { code: %d }. "
"Please try to refresh the printer information. "
"If it still does not recover, you can try to rebind the printer and check the network connection."), rtn);
show_status(PrintDialogStatus::PrintStatusRackNozzleMappingWaiting, {err_msg});
return false;
}
}
show_status(PrintDialogStatus::PrintStatusRackNozzleMappingWaiting, {_L("The printer is calculating nozzle mapping.") + " " + _L("Please wait a moment...")});
return false;
}
if (obj_nozzle_mapping_ptr->GetResultStr() == "failed") {
const auto& err_msg = wxString::Format(_L("Failed to receive nozzle auto-mapping table from printer { msg: %s }. Please refresh the printer information."), obj_nozzle_mapping_ptr->GetMqttReason());
show_status(PrintDialogStatus::PrintStatusRackNozzleMappingError, {err_msg});
return false;
}
if (obj_nozzle_mapping_ptr->GetResultStr() == "fail") {
const wxString& err_msg = wxString::Format(_L("The printer failed to build the nozzle auto-mapping table { code: %d }. Please refresh nozzle information."), obj_nozzle_mapping_ptr->GetErrno());
show_status(PrintDialogStatus::PrintStatusRackNozzleMappingError, {err_msg});
return false;
}
const auto& mapping_map = obj_->get_nozzle_mapping_result()->GetNozzleMappingMap();
if (!mapping_map.empty()) {
if (m_nozzle_mapping_result != mapping_map) {
m_nozzle_mapping_result = mapping_map;
sync_ams_mapping_result(m_ams_mapping_result);
}
float flush_waste_base = obj_->get_nozzle_mapping_result()->GetFlushWeightBase();
float flush_waste_current = obj_->get_nozzle_mapping_result()->GetFlushWeightCurrent();
if ((flush_waste_base != -1) && (flush_waste_current != -1) && flush_waste_current > flush_waste_base) {
const wxString& warning_msg = wxString::Format(_L("The current nozzle mapping may produce an extra %0.2f g of waste."), flush_waste_current - flush_waste_base);
show_status(PrintDialogStatus::PrintStatusRackNozzleMappingWarning, {warning_msg});
}
return true;
}
const wxString& err_msg = wxString::Format(_L("Failed to receive nozzle auto-mapping table from printer { msg: %s }. Please refresh the printer information."), "empty table");
show_status(PrintDialogStatus::PrintStatusRackNozzleMappingError, {err_msg});
return true;
}
void SelectMachineDialog::prepare(int print_plate_idx)
{
m_print_plate_idx = print_plate_idx;
@@ -1684,6 +1999,17 @@ void SelectMachineDialog::show_status(PrintDialogStatus status, std::vector<wxSt
} else if (status == PrintStatusWarningExtFilamentNotMatch) {
Enable_Refresh_Button(true);
Enable_Send_Button(true);
} else if (status == PrintDialogStatus::PrintStatusRackNozzleMappingWaiting) {
// Printer is computing the rack nozzle mapping: block Send until it replies.
Enable_Refresh_Button(true);
Enable_Send_Button(false);
} else if (status == PrintDialogStatus::PrintStatusRackNozzleMappingError) {
Enable_Refresh_Button(true);
Enable_Send_Button(false);
} else if (status == PrintDialogStatus::PrintStatusRackNozzleMappingWarning) {
// Extra-waste warning: allow Send.
Enable_Refresh_Button(true);
Enable_Send_Button(true);
}
/*enter perpare mode*/
@@ -1983,15 +2309,48 @@ void SelectMachineDialog::on_ok_btn(wxCommandEvent &event)
}
}
bool in_blacklist = false;
std::string action;
wxString info;
wxString wiki_url;
DevFilaBlacklist::check_filaments_in_blacklist_url(obj_->printer_type, filament_brand, filament_type, m_ams_mapping_result[i].filament_id, ams_id, slot_id, "", in_blacklist,
action, info, wiki_url);
if (in_blacklist && action == "warning") {
confirm_text.push_back(ConfirmBeforeSendInfo(info, wiki_url));
has_slice_warnings = true;
DevFilaBlacklist::CheckFilamentInfo check_info;
check_info.dev_id = obj_->get_dev_id();
check_info.model_id = obj_->printer_type;
check_info.fila_id = m_ams_mapping_result[i].filament_id;
check_info.fila_type = filament_type;
check_info.fila_vendor = filament_brand;
// Populate fila_name so the high-flow warning strings (blacklist rules 14/15/18/19,
// "%s has a risk of nozzle clogging ...") render with the filament name instead of a
// leading blank. Resolves the same preset the engine's internal AMS-name recovery uses, so
// blacklist name-matching is unchanged.
if (auto option = wxGetApp().preset_bundle->get_filament_by_filament_id(check_info.fila_id))
check_info.fila_name = option->filament_name;
check_info.ams_id = ams_id;
check_info.slot_id = slot_id;
check_info.has_filament_switch = obj_->GetFilaSwitch()->IsInstalled();
std::vector<DevNozzle> mapped_nozzles;
for (const auto &[pos_id, nozzle] : get_mapped_nozzles(m_ams_mapping_result[i].id)) {
if (!nozzle.IsEmpty()) { mapped_nozzles.push_back(nozzle); }
}
// Evaluate the blacklist once per resolved physical nozzle so the high-flow
// nozzle_flows/nozzle_diameters rules match against the real nozzle. If no nozzle context is
// resolvable (a non-rack printer, or a rack filament not yet mapped by the print-dispatch
// mapping), evaluate once with the nozzle fields unset so the non-nozzle rules still fire as
// before (empty nozzle_flow never matches a nozzle_flows rule).
const size_t iterations = mapped_nozzles.empty() ? 1 : mapped_nozzles.size();
for (size_t n = 0; n < iterations; ++n) {
if (n < mapped_nozzles.size()) {
const DevNozzle &nozzle = mapped_nozzles[n];
check_info.extruder_id = nozzle.GetExtruderId();
check_info.nozzle_flow = DevNozzle::GetNozzleFlowTypeString(nozzle.GetNozzleFlowType());
check_info.nozzle_diameter = nozzle.GetNozzleDiameter();
}
const auto &result = DevFilaBlacklist::check_filaments_in_blacklist(check_info);
if (const auto &warning_items = result.get_items_by_action("warning"); !warning_items.empty()) {
for (const auto &item : warning_items) {
confirm_text.push_back(ConfirmBeforeSendInfo(item.info_msg, item.wiki_url));
has_slice_warnings = true;
}
}
}
}
@@ -2514,6 +2873,12 @@ void SelectMachineDialog::on_send_print()
m_print_job->task_ams_mapping = ams_mapping_array;
m_print_job->task_ams_mapping2 = ams_mapping_array2;
m_print_job->task_ams_mapping_info = ams_mapping_info;
// Print-dispatch nozzle mapping (H2C hotend rack): attach ONLY when a mapping result exists.
// For every non-rack printer (X1/P1/A1/H2S/H2D) the mapping json is empty, so task_nozzle_mapping
// stays absent and the print payload is unchanged.
if (!obj_->get_nozzle_mapping_result()->GetNozzleMappingJson().empty()) {
m_print_job->task_nozzle_mapping = obj_->get_nozzle_mapping_result()->GetNozzleMappingJson().dump();
}
/* build nozzles info for multi extruders printers */
if (build_nozzles_info(m_print_job->task_nozzles_info)) {
@@ -2689,6 +3054,7 @@ void SelectMachineDialog::on_set_finish_mapping(wxCommandEvent &evt)
if (item->id == m_current_filament_id) {
auto ams_colour = wxColour(wxAtoi(selection_data_arr[0]), wxAtoi(selection_data_arr[1]), wxAtoi(selection_data_arr[2]), wxAtoi(selection_data_arr[3]));
m->set_ams_info(ams_colour, selection_data_arr[4], ctype, material_cols);
m->set_nozzle_info(get_mapped_nozzle_str(item->id));
}
iter++;
}
@@ -2993,9 +3359,11 @@ void SelectMachineDialog::on_selection_changed(wxCommandEvent &event)
/* reset timeout and reading printer info */
m_status_bar->reset();
m_timeout_count = 0;
s_nozzle_mapping_last_request_time = 0;
m_ams_mapping_res = false;
m_ams_mapping_valid = false;
m_ams_mapping_result.clear();
clear_nozzle_mapping();
m_pre_print_checker.clear();
m_link_edit_nozzle->Show(false);
@@ -3102,7 +3470,9 @@ void SelectMachineDialog::update_filament_change_count()
auto best = stats.stats_by_multi_extruder_best;
auto curr = stats.stats_by_multi_extruder_curr;
int hand_changes_count = curr.filament_change_count - best.filament_change_count;
// Per-nozzle flush_filament_change_count. Equals the per-extruder filament_change_count for
// single-nozzle-per-extruder printers, so this suggestion's shown value is unchanged.
int hand_changes_count = curr.flush_filament_change_count - best.flush_filament_change_count;
int saving_weight = curr.filament_flush_weight - best.filament_flush_weight;
if (obj->GetExtderSystem()->GetTotalExtderCount() > 1) { m_link_edit_nozzle->Show(true); }
@@ -3425,6 +3795,10 @@ void SelectMachineDialog::update_show_status(MachineObject* obj_)
}
}
// Rack print-dispatch nozzle mapping (dynamic V1): request/await the printer's mapping and block
// Send while it computes. No-op for non-rack printers.
if (!CheckErrorSyncNozzleMappingResultV1(obj_)) return;
if (!DevPrinterConfigUtil::support_ams_ext_mix_print(obj_->printer_type)) {
bool useAms = _HasAms(m_ams_mapping_result);
bool useExt = _HasExt(m_ams_mapping_result);
@@ -3444,6 +3818,10 @@ void SelectMachineDialog::update_show_status(MachineObject* obj_)
return;
}
// Rack print-dispatch nozzle mapping (static rack V0): consumes the validated AMS mapping, so it
// runs after the AMS-validity check and before the per-nozzle blacklist loop (which needs the result).
if (!CheckErrorSyncNozzleMappingResultV0(obj_)) return;
// filaments check for black list
for (auto i = 0; i < m_ams_mapping_result.size(); i++) {
const auto &ams_id = m_ams_mapping_result[i].get_ams_id();
@@ -3458,22 +3836,53 @@ void SelectMachineDialog::update_show_status(MachineObject* obj_)
if (fs.id == m_ams_mapping_result[i].id) { filament_brand = m_filaments[i].brand; }
}
bool in_blacklist = false;
std::string action;
wxString info;
wxString wiki_url;
DevFilaBlacklist::check_filaments_in_blacklist_url(obj_->printer_type, filament_brand, filament_type, m_ams_mapping_result[i].filament_id, ams_id, slot_id, "", in_blacklist,
action, info, wiki_url);
if (in_blacklist) {
DevFilaBlacklist::CheckFilamentInfo check_info;
check_info.dev_id = obj_->get_dev_id();
check_info.model_id = obj_->printer_type;
check_info.fila_id = m_ams_mapping_result[i].filament_id;
check_info.fila_type = filament_type;
check_info.fila_vendor = filament_brand;
// Populate fila_name so the high-flow warning strings (blacklist rules 14/15/18/19,
// "%s has a risk of nozzle clogging ...") render with the filament name instead of a
// leading blank. Resolves the same preset the engine's internal AMS-name recovery uses, so
// blacklist name-matching is unchanged.
if (auto option = wxGetApp().preset_bundle->get_filament_by_filament_id(check_info.fila_id))
check_info.fila_name = option->filament_name;
check_info.ams_id = ams_id;
check_info.slot_id = slot_id;
check_info.has_filament_switch = obj_->GetFilaSwitch()->IsInstalled();
std::vector<wxString> error_msg { info };
if (action == "prohibition") {
show_status(PrintDialogStatus::PrintStatusHasFilamentInBlackListError, error_msg, wiki_url);
std::vector<DevNozzle> mapped_nozzles;
for (const auto &[pos_id, nozzle] : get_mapped_nozzles(m_ams_mapping_result[i].id)) {
if (!nozzle.IsEmpty()) { mapped_nozzles.push_back(nozzle); }
}
// Per-physical-nozzle blacklist check — thread the nozzle the filament prints on so the
// high-flow prohibition/warning rules evaluate against the real nozzle flow/diameter.
// With no resolvable nozzle context (a non-rack printer, or a rack filament not yet mapped
// by the print-dispatch mapping), evaluate once with the nozzle fields unset so the
// non-nozzle prohibitions/warnings still fire as before.
const size_t iterations = mapped_nozzles.empty() ? 1 : mapped_nozzles.size();
for (size_t n = 0; n < iterations; ++n) {
if (n < mapped_nozzles.size()) {
const DevNozzle &nozzle = mapped_nozzles[n];
check_info.extruder_id = nozzle.GetExtruderId();
check_info.nozzle_flow = DevNozzle::GetNozzleFlowTypeString(nozzle.GetNozzleFlowType());
check_info.nozzle_diameter = nozzle.GetNozzleDiameter();
}
const auto &result = DevFilaBlacklist::check_filaments_in_blacklist(check_info);
if (const auto &prohibition_items = result.get_items_by_action("prohibition"); !prohibition_items.empty()) {
for (const auto &item : prohibition_items) {
show_status(PrintDialogStatus::PrintStatusHasFilamentInBlackListError, {item.info_msg}, item.wiki_url);
}
return;
}
else if (action == "warning") {
show_status(PrintDialogStatus::PrintStatusHasFilamentInBlackListWarning, error_msg, wiki_url);/** warning check **/
// return;
if (const auto &warning_items = result.get_items_by_action("warning"); !warning_items.empty()) {
for (const auto &item : warning_items) {
show_status(PrintDialogStatus::PrintStatusHasFilamentInBlackListWarning, {item.info_msg}, item.wiki_url);/** warning check **/
}
}
}
}
@@ -3664,6 +4073,7 @@ void SelectMachineDialog::reset_ams_material()
wxString ams_id = "-";
wxColour ams_col = wxColour(0xEE, 0xEE, 0xEE);
m->set_ams_info(ams_col, ams_id);
m->set_nozzle_info(get_mapped_nozzle_str(id));
iter++;
}
}
@@ -3968,7 +4378,7 @@ void SelectMachineDialog::reset_and_sync_ams_list()
m_mapping_popup.set_current_filament_id(extruder);
m_mapping_popup.set_tag_texture(materials[extruder]);
m_mapping_popup.set_send_win(this);//fix bug:fisrt click is not valid
m_mapping_popup.update(obj_, m_ams_mapping_result);
m_mapping_popup.update(obj_, m_ams_mapping_result, use_dynamic_nozzle_map(), m_print_type);
m_mapping_popup.Popup();
}
}
@@ -4471,7 +4881,7 @@ void SelectMachineDialog::set_default_from_sdcard()
m_mapping_popup.set_current_filament_id(fo.id);
m_mapping_popup.set_tag_texture(fo.type);
m_mapping_popup.set_send_win(this);
m_mapping_popup.update(obj_, m_ams_mapping_result);
m_mapping_popup.update(obj_, m_ams_mapping_result, use_dynamic_nozzle_map(), m_print_type);
m_mapping_popup.Popup();
}
}
+21 -4
View File
@@ -32,6 +32,7 @@
#include "GUI_Utils.hpp"
#include "wxExtensions.hpp"
#include "DeviceManager.hpp"
#include "DeviceCore/DevNozzleSystem.h" // DevNozzle (get_mapped_nozzles return type)
#include "Plater.hpp"
#include "BBLStatusBar.hpp"
#include "BBLStatusBarPrint.hpp"
@@ -61,10 +62,8 @@ namespace Slic3r { namespace GUI {
std::string get_nozzle_volume_type_cloud_string(NozzleVolumeType nozzle_volume_type);
void print_ams_mapping_result(std::vector<FilamentInfo> &result);
enum PrintFromType {
FROM_NORMAL,
FROM_SDCARD_VIEW,
};
// enum PrintFromType moved to DeviceCore/DevDefs.h so shared device-mapping GUI
// headers can name it without an include cycle. SelectMachine.hpp still sees it via DeviceManager.hpp.
enum PrintPageMode {
PrintPageModePrepare = 0,
@@ -324,6 +323,7 @@ private:
std::vector<MachineObject*> m_list;
std::vector<FilamentInfo> m_filaments;
std::vector<FilamentInfo> m_ams_mapping_result;
std::unordered_map<int, int> m_nozzle_mapping_result; // cached rack print-dispatch mapping: filament/group id -> physical nozzle pos
std::vector<int> m_filaments_map;
std::shared_ptr<BBLStatusBarPrint> m_status_bar;
std::unique_ptr<Worker> m_worker;
@@ -511,6 +511,23 @@ public:
bool is_nozzle_type_match(DevExtderSystem data, wxString& error_message) const;
int convert_filament_map_nozzle_id_to_task_nozzle_id(int nozzle_id) const;
// Physical nozzle(s) a mapped filament (by filament index / FilamentInfo::id) prints on.
// key: nozzle pos id, value: nozzle. Used by the per-nozzle filament blacklist check loop.
std::map<int, DevNozzle> get_mapped_nozzles(int fila_id) const;
// Short label of the physical nozzle(s) a filament prints on, shown on its send-dialog card:
// rack slots ("R1", "R2 R3") for a nozzle-rack printer, or "L"/"R"/"L R" for a filament-switcher
// printer. Empty for printers with neither (the card then shows no nozzle row).
wxString get_mapped_nozzle_str(int fila_id) const;
// Rack print-dispatch nozzle mapping (H2C): request the printer's auto-mapping and consume the
// result, gating the Send button while the printer computes. Both are no-ops for non-rack printers.
bool CheckErrorSyncNozzleMappingResultV1(MachineObject* obj_);
bool CheckErrorSyncNozzleMappingResultV0(MachineObject* obj_);
void clear_nozzle_mapping();
bool use_dynamic_nozzle_map() const;
void on_flow_cali_option_changed();
PrintFromType get_print_type() {return m_print_type;};
wxString format_steel_name(NozzleType type);
PrintDialogStatus get_status() { return m_print_status; }
+87 -4
View File
@@ -34,6 +34,8 @@
#include "DeviceCore/DevManager.h"
#include "DeviceCore/DevPrintTaskInfo.h"
#include "DeviceTab/wgtDeviceNozzleRack.h"
#include "PrintOptionsDialog.hpp"
@@ -1611,13 +1613,26 @@ wxBoxSizer *StatusBasePanel::create_machine_control_page(wxWindow *parent)
wxBoxSizer *bSizer_control = new wxBoxSizer(wxVERTICAL);
auto temp_axis_ctrl_sizer = create_temp_axis_group(parent);
auto m_ams_ctrl_sizer = create_ams_group(parent);
auto m_filament_load_sizer = create_filament_group(parent);
/* ams control box or live nozzle-rack panel (rack printers switch between the two) */
wxSizer *ams_rack_sizer = new wxBoxSizer(wxHORIZONTAL);
ams_rack_sizer->Add(create_ams_group(parent), 0, wxEXPAND | wxLEFT);
m_panel_nozzle_rack = new wgtDeviceNozzleRack(parent);
m_panel_nozzle_rack->Show(false);
ams_rack_sizer->Add(m_panel_nozzle_rack, 0, wxEXPAND | wxLEFT);
m_ams_rack_switch = new SwitchBoard(parent, _L("Filament"), _L("Hotends"), wxSize(FromDIP(126), FromDIP(26)));
m_ams_rack_switch->updateState("left");
m_ams_rack_switch->Hide();
m_ams_rack_switch->Bind(wxCUSTOMEVT_SWITCH_POS, &StatusBasePanel::on_ams_rack_switch, this);
bSizer_control->Add(0, 0, 0, wxTOP, FromDIP(8));
bSizer_control->Add(temp_axis_ctrl_sizer, 0, wxALIGN_CENTER|wxLEFT|wxRIGHT, FromDIP(8));
bSizer_control->Add(m_ams_rack_switch, 0, wxALIGN_CENTRE|wxTOP, FromDIP(6));
bSizer_control->Add(0, 0, 0, wxTOP, FromDIP(6));
bSizer_control->Add(m_ams_ctrl_sizer, 0, wxALIGN_CENTER|wxLEFT|wxRIGHT, FromDIP(8));
bSizer_control->Add(ams_rack_sizer, 0, wxALIGN_CENTER|wxLEFT|wxRIGHT, FromDIP(8));
bSizer_control->Add(0, 0, 0, wxTOP, FromDIP(6));
bSizer_control->Add(m_filament_load_sizer, 0, wxALIGN_CENTER|wxLEFT|wxRIGHT, FromDIP(8));
bSizer_control->Add(0, 0, 0, wxTOP, FromDIP(4));
@@ -2163,7 +2178,19 @@ void StatusBasePanel::expand_filament_loading(wxMouseEvent& e)
else if (obj->is_series_o())
{
const auto& ext_system = obj->GetExtderSystem();
if (ext_system->GetTotalExtderCount() == 2)
// Prefer the config-driven filament-load image: H2C ships per-extruder bitmaps, and a printer
// fitted with an induction hotend rack picks the rack-specific set (filament_load_image_nozzle_rack).
// Printers whose profile carries no such key (e.g. H2D) get an empty name here and fall through to
// the generic O-series bitmaps below, so their monitor page stays byte-identical. Rack-driven, so
// there is no behavior change for any non-rack printer.
int cur_ext_id = (ext_system && ext_system->GetTotalExtderCount() > 1) ? ext_system->GetCurrentExtderId() : 0;
bool has_nozzle_rack = obj->GetNozzleSystem()->GetNozzleRack()->IsSupported();
std::string img_name = DevPrinterConfigUtil::get_filament_load_img(obj->printer_type, cur_ext_id, has_nozzle_rack);
if (!img_name.empty())
{
m_filament_load_img->SetBitmap(create_scaled_bitmap(img_name, this, load_img_size));
}
else if (ext_system->GetTotalExtderCount() == 2)
{
int cur_extder_id = ext_system->GetCurrentExtderId();
if (cur_extder_id == MAIN_EXTRUDER_ID)
@@ -2202,6 +2229,10 @@ void StatusBasePanel::show_ams_group(bool show)
wxGetApp().mainframe->m_monitor->Layout();
}
// On rack printers, don't clobber the rack view when the user has the switch on "Hotends".
// Inert for every non-rack printer: the switch stays hidden, so this guard never triggers.
if (show && m_ams_rack_switch->IsShown() && (m_ams_rack_switch->switch_left != true)) { return; }
if (m_ams_control_box->IsShown() != show) {
m_ams_control_box->Show(show);
m_ams_control->Layout();
@@ -2237,6 +2268,31 @@ void StatusBasePanel::show_filament_load_group(bool show)
}
}
void StatusBasePanel::jump_to_Rack()
{
if (obj && obj->GetNozzleSystem()->GetNozzleRack()->IsSupported()) {
m_ams_rack_switch->updateState("right");
m_ams_control_box->Show(false);
m_panel_nozzle_rack->Show(true);
Layout();
}
}
void StatusBasePanel::on_ams_rack_switch(wxCommandEvent &e)
{
if (!m_ams_control_box->IsShown() && e.GetInt() == 1) {
m_ams_control_box->Show(e.GetInt() == 1);
m_panel_nozzle_rack->Show(e.GetInt() == 0);
Layout();
} else if (!m_panel_nozzle_rack->IsShown() && e.GetInt() == 0) {
m_ams_control_box->Show(e.GetInt() == 1);
m_panel_nozzle_rack->Show(e.GetInt() == 0);
Layout();
}
e.Skip();
}
void StatusPanel::update_camera_state(MachineObject* obj)
{
if (!obj) return;
@@ -2778,6 +2834,8 @@ void StatusPanel::update(MachineObject *obj)
update_ams(obj);
update_cali(obj);
update_rack(obj);
if (obj) {
//nozzle ui
//m_button_left_of_extruder->SetSelected();
@@ -3412,7 +3470,14 @@ void StatusPanel::update_ams(MachineObject *obj)
int tray_id_int = atoi(tray_id.c_str());
// new protocol
if (ams_id_int < 128) {
if ((obj->tray_reading_bits & (1 << (ams_id_int * 4 + tray_id_int))) != 0) {
if (ams_it->second->IsAmsLiteMixed()) {
// Mixed AMS-Lite (A2L / N9) RFID-reading bits occupy positions 24..27.
if ((obj->tray_reading_bits & (1 << (AMS_LITE_MIXED_TRAY_INDEX_OFFSET + tray_id_int))) != 0) {
m_ams_control->PlayRridLoading(ams_id, tray_id);
} else {
m_ams_control->StopRridLoading(ams_id, tray_id);
}
} else if ((obj->tray_reading_bits & (1 << (ams_id_int * 4 + tray_id_int))) != 0) {
m_ams_control->PlayRridLoading(ams_id, tray_id);
} else {
m_ams_control->StopRridLoading(ams_id, tray_id);
@@ -5085,6 +5150,9 @@ void StatusPanel::set_default()
m_ams_control->Hide();
m_ams_control_box->Hide();
m_ams_control->Reset();
m_ams_rack_switch->updateState("left");
m_ams_rack_switch->Hide();
m_panel_nozzle_rack->Hide();
m_scale_panel->Hide();
m_filament_load_box->Hide();
m_filament_step->Hide();
@@ -5262,6 +5330,7 @@ void StatusPanel::msw_rescale()
m_extruder_switching_status->msw_rescale();
m_ams_control->msw_rescale();
m_panel_nozzle_rack->Rescale();
// m_filament_step->Rescale();
@@ -5283,6 +5352,20 @@ void StatusPanel::msw_rescale()
Refresh();
}
void StatusPanel::update_rack(MachineObject *obj)
{
// Rack switch + live rack panel are shown only for printers whose nozzle system reports a rack
// (H2C). Every other Bambu printer keeps the switch hidden and the panel collapsed -> the AMS
// monitor page is unchanged for X1/P1/A1/H2D/H2S.
if (obj && obj->GetNozzleSystem()->GetNozzleRack()->IsSupported()) {
m_ams_rack_switch->Show();
m_panel_nozzle_rack->UpdateRackInfo(obj->GetNozzleSystem()->GetNozzleRack());
} else {
m_ams_rack_switch->Show(false);
m_panel_nozzle_rack->Show(false);
}
}
void StatusPanel::update_filament_loading_panel(MachineObject* obj)
{
if (!obj) {
+12
View File
@@ -50,6 +50,7 @@ namespace GUI {
// Previous definitions
class MessageDialog;
class wgtDeviceNozzleRack;
enum CameraRecordingStatus {
RECORDING_NONE,
@@ -518,11 +519,16 @@ protected:
bool m_show_ams_group{false};
bool m_show_filament_group{ false };
/* AMS control box <-> live nozzle-rack panel toggle (rack printers only) */
SwitchBoard* m_ams_rack_switch{ nullptr };
AMSControl* m_ams_control;
StaticBox* m_ams_control_box;
wxStaticBitmap *m_ams_extruder_img;
wxStaticBitmap* m_bitmap_extruder_img;
wgtDeviceNozzleRack* m_panel_nozzle_rack{ nullptr };
wxPanel * m_panel_separator_right;
wxPanel * m_panel_separotor_bottom;
wxGridBagSizer *m_tasklist_info_sizer{nullptr};
@@ -611,6 +617,11 @@ public:
void show_ams_group(bool show = true);
void show_filament_load_group(bool show = true);
MediaPlayCtrl* get_media_play_ctrl() {return m_media_play_ctrl;};
void jump_to_Rack();
private:
void on_ams_rack_switch(wxCommandEvent& event);
};
@@ -767,6 +778,7 @@ protected:
void update_temp_ctrl(MachineObject *obj);
void update_misc_ctrl(MachineObject *obj);
void update_ams(MachineObject* obj);
void update_rack(MachineObject* obj);
void update_filament_loading_panel(MachineObject* obj);
void update_extruder_status(MachineObject* obj);
+4
View File
@@ -1547,6 +1547,8 @@ bool SyncAmsInfoDialog::is_nozzle_type_match(DevExtderSystem data, wxString &err
NozzleVolumeType nozzle_volume_type = (NozzleVolumeType) (nozzle_volume_type_opt->get_at(used_extruders[i]));
if (nozzle_volume_type == NozzleVolumeType::nvtStandard) {
used_extruders_flow[used_extruders[i]] = "Standard";
} else if (nozzle_volume_type == NozzleVolumeType::nvtTPUHighFlow) {
used_extruders_flow[used_extruders[i]] = "TPU High Flow";
} else {
used_extruders_flow[used_extruders[i]] = "High Flow";
}
@@ -1562,6 +1564,8 @@ bool SyncAmsInfoDialog::is_nozzle_type_match(DevExtderSystem data, wxString &err
flow_type_of_machine.push_back("High Flow");
} else if (it->GetNozzleFlowType() == NozzleFlowType::S_FLOW) {
flow_type_of_machine.push_back("Standard");
} else if (it->GetNozzleFlowType() == NozzleFlowType::U_FLOW) {
flow_type_of_machine.push_back("TPU High Flow");
}
}
+21
View File
@@ -1847,6 +1847,27 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
if (opt_key == "enable_prime_tower") {
auto timelapse_type = m_config->option<ConfigOptionEnum<TimelapseType>>("timelapse_type");
bool timelapse_enabled = timelapse_type->value == TimelapseType::tlSmooth;
if (!boost::any_cast<bool>(value)) {
// Disabling the prime tower on a multi-nozzle printer degrades quality because nozzle changes rely
// on it. Gate on any extruder having extruder_max_nozzle_count > 1 so single-nozzle and dual-extruder
// (H2D, {1,1}) printers keep their exact existing behavior.
// Orca: gate on any_of(count > 1) rather than the sum of counts >= 2. The sum form would also fire for
// H2D ({1,1} sums to 2); any_of(>1) preserves H2D's current no-dialog behavior.
auto *max_nozzle_counts_opt = wxGetApp().preset_bundle->printers.get_edited_preset().config.option<ConfigOptionIntsNullable>("extruder_max_nozzle_count");
const bool has_multiple_nozzle = max_nozzle_counts_opt &&
std::any_of(max_nozzle_counts_opt->values.begin(), max_nozzle_counts_opt->values.end(),
[](int v) { return v > 1 && v != ConfigOptionIntsNullable::nil_value(); });
if (has_multiple_nozzle) {
MessageDialog dlg(wxGetApp().plater(),
_L("Prime tower is required for nozzle changing. There may be flaws on the model without prime tower. Are you sure you want to disable prime tower?"),
_L("Warning"), wxICON_WARNING | wxYES | wxNO);
if (dlg.ShowModal() == wxID_NO) {
DynamicPrintConfig new_conf = *m_config;
new_conf.set_key_value("enable_prime_tower", new ConfigOptionBool(true));
m_config_manipulation.apply(m_config, &new_conf);
}
}
}
if (!boost::any_cast<bool>(value) && timelapse_enabled) {
bool set_enable_prime_tower = false;
MessageDialog dlg(wxGetApp().plater(), _L("A prime tower is required for smooth timelapse mode. There may be flaws on the model without a prime tower. Are you sure you want to disable the prime tower\?"),
+80
View File
@@ -3,6 +3,7 @@
#include <slic3r/GUI/Widgets/Label.hpp>
#include <slic3r/GUI/I18N.hpp>
#include "slic3r/GUI/DeviceTab/uiDeviceUpdateVersion.h"
#include "slic3r/GUI/DeviceTab/wgtDeviceNozzleRackUpdate.h"
#include "GUI.hpp"
#include "GUI_App.hpp"
@@ -217,6 +218,9 @@ MachineInfoPanel::MachineInfoPanel(wxWindow* parent, wxWindowID id, const wxPoin
createAirPumpWidgets(m_main_left_sizer);
createExtinguishWidgets(m_main_left_sizer);
// nozzle rack widgets (H2C induction hotend rack; hidden unless GetNozzleRack()->IsSupported())
createNozzleRackWidgets(m_main_left_sizer);
m_main_sizer->Add(m_main_left_sizer, 1, wxEXPAND, 0);
wxBoxSizer *m_main_right_sizer = new wxBoxSizer(wxVERTICAL);
@@ -431,6 +435,7 @@ void MachineInfoPanel::init_bitmaps()
m_img_laser = ScalableBitmap(this, "laser", 160);
m_img_cutting = ScalableBitmap(this, "cut", 160);
m_img_extinguish = ScalableBitmap(this, "extinguish", 160);
m_img_nozzle_rack = ScalableBitmap(this, "nozzle_rack", 160);
upgrade_green_icon = ScalableBitmap(this, "monitor_upgrade_online", 5);
upgrade_gray_icon = ScalableBitmap(this, "monitor_upgrade_offline", 5);
@@ -539,6 +544,7 @@ void MachineInfoPanel::update(MachineObject* obj)
update_cut(obj);
update_laszer(obj);
update_extinguish(obj);
update_nozzle_rack(obj);
//update progress
int upgrade_percent = obj->get_upgrade_percent();
@@ -1257,6 +1263,80 @@ void MachineInfoPanel::show_extinguish(bool show)
}
}
void MachineInfoPanel::createNozzleRackWidgets(wxBoxSizer *main_left_sizer)
{
// horizontal line above
m_nozzle_rack_line_above = new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL);
m_nozzle_rack_line_above->SetBackgroundColour(wxColour(206, 206, 206));
main_left_sizer->Add(m_nozzle_rack_line_above, 0, wxEXPAND | wxLEFT, FromDIP(40));
m_nozzle_rack_sizer = new wxBoxSizer(wxHORIZONTAL);
// left placeholder icon (keep consistent spacing with others)
m_nozzle_rack_img = new wxStaticBitmap(this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize(FromDIP(200), FromDIP(200)));
m_nozzle_rack_img->SetBitmap(m_img_nozzle_rack.bmp());
m_nozzle_rack_sizer->Add(m_nozzle_rack_img, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5));
// right content: label + update button
auto *content_sizer = new wxBoxSizer(wxHORIZONTAL);
m_nozzle_rack_text = new wxStaticText(this, wxID_ANY, _L("Hotends on Rack"), wxDefaultPosition, wxDefaultSize, 0);
m_nozzle_rack_text->Wrap(-1);
m_nozzle_rack_text->SetFont(Label::Head_14);
content_sizer->Add(m_nozzle_rack_text, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT | wxLEFT, FromDIP(50));
m_nozzle_rack_update_btn = new Button(this, _L("Info"));
StateColor btn_bg(std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Disabled), std::pair<wxColour, int>(wxColour(200, 200, 200), StateColor::Pressed),
std::pair<wxColour, int>(wxColour(240, 240, 240), StateColor::Hovered), std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Enabled),
std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Normal));
StateColor btn_bd(std::pair<wxColour, int>(wxColour(200, 200, 200), StateColor::Disabled), std::pair<wxColour, int>(wxColour(150, 150, 150), StateColor::Enabled));
StateColor btn_text(std::pair<wxColour, int>(wxColour(150, 150, 150), StateColor::Disabled), std::pair<wxColour, int>(wxColour(0, 0, 0), StateColor::Enabled));
m_nozzle_rack_update_btn->SetBackgroundColor(btn_bg);
m_nozzle_rack_update_btn->SetBorderColor(btn_bd);
m_nozzle_rack_update_btn->SetTextColor(btn_text);
m_nozzle_rack_update_btn->SetFont(Label::Body_10.Bold());
m_nozzle_rack_update_btn->SetMinSize(wxSize(FromDIP(-1), FromDIP(24)));
m_nozzle_rack_update_btn->SetCornerRadius(FromDIP(12));
m_nozzle_rack_update_btn->Bind(wxEVT_BUTTON, &MachineInfoPanel::on_nozzle_rack_update, this);
content_sizer->Add(m_nozzle_rack_update_btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(350));
m_nozzle_rack_sizer->Add(content_sizer, 1, wxEXPAND, 0);
main_left_sizer->Add(m_nozzle_rack_sizer, 0, wxEXPAND, 0);
}
void MachineInfoPanel::update_nozzle_rack(MachineObject* obj)
{
if (obj && obj->GetNozzleSystem()) {
auto rack = obj->GetNozzleSystem()->GetNozzleRack();
if (rack && rack->IsSupported()) {
show_nozzle_rack(true);
}
else {
show_nozzle_rack(false);
}
}
}
void MachineInfoPanel::show_nozzle_rack(bool show)
{
if (m_nozzle_rack_img->IsShown() != show) {
m_nozzle_rack_line_above->Show(show);
m_nozzle_rack_update_btn->Show(show);
m_nozzle_rack_img->Show(show);
m_nozzle_rack_text->Show(show);
}
}
void MachineInfoPanel::on_nozzle_rack_update(wxCommandEvent &event)
{
if (!m_obj || !m_obj->GetNozzleSystem()) return;
auto rack = m_obj->GetNozzleSystem()->GetNozzleRack();
if (!rack) return;
wgtDeviceNozzleRackUpgradeDlg dlg(this, rack);
dlg.ShowModal();
}
void MachineInfoPanel::on_sys_color_changed()
{
+13
View File
@@ -134,6 +134,13 @@ protected:
wxStaticLine* m_extinguish_line_above = nullptr;;
uiDeviceUpdateVersion* m_extinguish_version = nullptr;
/* nozzle rack (H2C induction hotend rack) — opens wgtDeviceNozzleRackUpgradeDlg */
wxBoxSizer* m_nozzle_rack_sizer = nullptr;
wxStaticBitmap* m_nozzle_rack_img = nullptr;
wxStaticLine* m_nozzle_rack_line_above = nullptr;
wxStaticText* m_nozzle_rack_text = nullptr;
Button* m_nozzle_rack_update_btn = nullptr;
/* upgrade widgets */
wxBoxSizer* m_upgrading_sizer;
wxStaticText * m_staticText_upgrading_info;
@@ -155,6 +162,7 @@ protected:
ScalableBitmap m_img_cutting;
ScalableBitmap m_img_laser;
ScalableBitmap m_img_extinguish;
ScalableBitmap m_img_nozzle_rack;
ScalableBitmap upgrade_gray_icon;
ScalableBitmap upgrade_green_icon;
ScalableBitmap upgrade_yellow_icon;
@@ -212,16 +220,21 @@ private:
void createCuttingWidgets(wxBoxSizer* main_left_sizer);
void createLaserWidgets(wxBoxSizer* main_left_sizer);
void createExtinguishWidgets(wxBoxSizer* main_left_sizer);
void createNozzleRackWidgets(wxBoxSizer* main_left_sizer);
void update_air_pump(MachineObject* obj);
void update_cut(MachineObject* obj);
void update_laszer(MachineObject* obj);
void update_extinguish(MachineObject* obj);
void update_nozzle_rack(MachineObject* obj);
void show_air_pump(bool show = true);
void show_cut(bool show = true);
void show_laszer(bool show = true);
void show_extinguish(bool show = true);
void show_nozzle_rack(bool show = true);
void on_nozzle_rack_update(wxCommandEvent& event);
};
//enum UpgradeMode {
File diff suppressed because it is too large Load Diff
+239
View File
@@ -0,0 +1,239 @@
#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);
// Refresh the sidebar nozzle-count display for one extruder. Deferred wiring point: Orca's sidebar ExtruderGroup has
// no nozzle-count widget yet, so the multi-nozzle sidebar UI is not wired up.
void updateNozzleCountDisplay(PresetBundle *preset_bundle, int extruder_id, NozzleVolumeType volume_type);
// 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_
+4
View File
@@ -94,6 +94,10 @@ wxString get_nozzle_volume_type_name(NozzleVolumeType type)
return _L("Standard");
} else if (NozzleVolumeType::nvtHighFlow == type) {
return _L("High Flow");
} else if (NozzleVolumeType::nvtHybrid == type) {
return _L("Hybrid");
} else if (NozzleVolumeType::nvtTPUHighFlow == type) {
return _L("TPU High Flow");
}
return wxString();
}