mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-27 10:51:22 +00:00
Merge branch 'main' into dev/ams-heat
# Conflicts: # src/libslic3r/Preset.cpp # src/libslic3r/PrintConfig.hpp # src/slic3r/GUI/DeviceCore/CMakeLists.txt # src/slic3r/GUI/DeviceCore/DevDefs.h # src/slic3r/GUI/DeviceCore/DevFilaSystem.cpp # src/slic3r/GUI/DeviceCore/DevFilaSystem.h # src/slic3r/GUI/DeviceCore/DevUtilBackend.cpp # src/slic3r/GUI/DeviceCore/DevUtilBackend.h # src/slic3r/GUI/DeviceManager.cpp # src/slic3r/GUI/DeviceManager.hpp # src/slic3r/GUI/SelectMachine.cpp # src/slic3r/GUI/SelectMachine.hpp
This commit is contained in:
@@ -413,6 +413,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/Project.hpp
|
||||
GUI/PublishDialog.cpp
|
||||
GUI/PublishDialog.hpp
|
||||
GUI/PurgeModeDialog.cpp
|
||||
GUI/PurgeModeDialog.hpp
|
||||
GUI/RammingChart.cpp
|
||||
GUI/RammingChart.hpp
|
||||
GUI/RecenterDialog.cpp
|
||||
@@ -496,6 +498,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
|
||||
|
||||
@@ -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>()));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -298,7 +301,7 @@ void AMSMaterialsSetting::create_panel_kn(wxWindow* parent)
|
||||
if (language.find("zh") == 0)
|
||||
region = "zh";
|
||||
wxString link_url = wxString::Format("https://wiki.bambulab.com/%s/software/bambu-studio/calibration_pa", region);
|
||||
m_wiki_ctrl = new HyperLink(parent, "Wiki Guide", link_url);
|
||||
m_wiki_ctrl = new HyperLink(parent, _L("Wiki Guide"), link_url);
|
||||
cali_title_sizer->Add(m_ratio_text, 0, wxALIGN_CENTER_VERTICAL);
|
||||
cali_title_sizer->Add(m_wiki_ctrl, 0, wxALIGN_CENTER_VERTICAL);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -22,8 +22,12 @@
|
||||
#include "Plater.hpp"
|
||||
#include "BitmapCache.hpp"
|
||||
#include "BindDialog.hpp"
|
||||
#include "slic3r/GUI/DeviceCore/DevFilaSwitch.h"
|
||||
|
||||
#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 +68,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 +78,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 +92,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 +118,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 +230,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 +247,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 +258,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 +278,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 +369,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 +431,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 +449,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 +461,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 +829,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 +854,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 +904,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();
|
||||
@@ -852,6 +975,8 @@ void AmsMapingPopup::on_left_down(wxMouseEvent &evt)
|
||||
auto left = item->GetSize();
|
||||
|
||||
if (pos.x > p_rect.x && pos.y > p_rect.y && pos.x < (p_rect.x + item->GetSize().x) && pos.y < (p_rect.y + item->GetSize().y)) {
|
||||
// Orca: the external spool is un-pickable while a Filament Track Switch is installed (Apple hit-tests here).
|
||||
if (m_fila_switch_installed && (item->m_ams_id == VIRTUAL_TRAY_MAIN_ID || item->m_ams_id == VIRTUAL_TRAY_DEPUTY_ID)) { return; }
|
||||
if (item->m_tray_data.type == TrayType::NORMAL) {
|
||||
if (!m_ext_mapping_filatype_check && (item->m_ams_id == VIRTUAL_TRAY_MAIN_ID || item->m_ams_id == VIRTUAL_TRAY_DEPUTY_ID)) {
|
||||
// Do nothing
|
||||
@@ -1038,7 +1163,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();
|
||||
@@ -1047,6 +1172,11 @@ void AmsMapingPopup::update(MachineObject* obj, const std::vector<FilamentInfo>&
|
||||
if (!obj) {return;}
|
||||
m_ams_remain_detect_flag = obj->GetFilaSystem()->IsDetectRemainEnabled();
|
||||
|
||||
// Orca: with a Filament Track Switch installed, filament routes through the switch and the external
|
||||
// spool cannot be used, so its mapping slots are rendered greyed-out and un-pickable. Inert (false)
|
||||
// on any printer without a switch — GetFilaSwitch()->IsInstalled() defaults to false.
|
||||
m_fila_switch_installed = obj->GetFilaSwitch()->IsInstalled();
|
||||
|
||||
for (auto& ams_container : m_amsmapping_container_list) {
|
||||
ams_container->Destroy();
|
||||
}
|
||||
@@ -1252,11 +1382,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;
|
||||
@@ -1367,6 +1554,20 @@ void AmsMapingPopup::add_ext_ams_mapping(TrayData tray_data, MappingItem* item)
|
||||
#ifdef __APPLE__
|
||||
m_mapping_item_list.push_back(item);
|
||||
#endif
|
||||
|
||||
// Orca: a Filament Track Switch cannot route the external spool, so grey the ext slot out and make it
|
||||
// un-pickable, with a tooltip explaining why (the click below is disabled; on_left_down also skips it).
|
||||
if (m_fila_switch_installed) {
|
||||
const wxString disp_name = (tray_data.type == THIRD) ? wxString("?")
|
||||
: (tray_data.type == EMPTY) ? wxString("-")
|
||||
: wxString(tray_data.name);
|
||||
item->set_data(m_tag_material, wxColour(0xEE, 0xEE, 0xEE), disp_name, false, tray_data, true);
|
||||
item->SetToolTip(_L("External spools is not supported since Filament Track Switch has been installed. If you want to use external spool, please uninstall it."));
|
||||
item->Bind(wxEVT_LEFT_DOWN, [](wxMouseEvent&) { /* un-pickable while the switch is installed */ });
|
||||
item->set_tray_index(_L("Ext"));
|
||||
return;
|
||||
}
|
||||
|
||||
// set button
|
||||
if (tray_data.type == NORMAL) {
|
||||
if (is_match_material(tray_data.filament_type)) {
|
||||
@@ -2305,12 +2506,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
@@ -229,6 +244,9 @@ public:
|
||||
bool m_has_unmatch_filament {false};
|
||||
int m_current_filament_id;
|
||||
ShowType m_show_type{ShowType::RIGHT};
|
||||
// Orca: set from update() — true when a Filament Track Switch is installed, which makes the
|
||||
// external-spool slots un-pickable (false on any printer without a switch, so behavior is unchanged).
|
||||
bool m_fila_switch_installed{false};
|
||||
std::string m_tag_material;
|
||||
wxBoxSizer *m_sizer_main{nullptr};
|
||||
wxBoxSizer *m_sizer_ams{nullptr};
|
||||
@@ -259,12 +277,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 +320,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};
|
||||
|
||||
@@ -231,7 +231,20 @@ void BackgroundSlicingProcess::process_fff()
|
||||
if (m_current_plate->get_real_filament_map_mode(preset_bundle.project_config) < FilamentMapMode::fmmManual) {
|
||||
std::vector<int> f_maps = m_fff_print->get_filament_maps();
|
||||
m_current_plate->set_filament_maps(f_maps);
|
||||
// The engine's concrete per-filament volume assignment is merged into the print
|
||||
// config by the ToolOrdering write-back; reading it back keeps the plate config the
|
||||
// next apply overlays equal to the written-back state (no diff, no re-invalidation)
|
||||
// and persists the auto result (always concrete Std/HF, never the Hybrid seed).
|
||||
std::vector<int> f_volume_maps = m_fff_print->get_filament_volume_maps();
|
||||
m_current_plate->set_filament_volume_maps(f_volume_maps);
|
||||
}
|
||||
if (m_current_plate->get_real_filament_map_mode(preset_bundle.project_config) != FilamentMapMode::fmmNozzleManual) {
|
||||
// The engine-resolved nozzle map is read back for every non-nozzle-manual mode so the
|
||||
// plate config the next apply overlays matches the engine's written-back state
|
||||
// (otherwise the full-config diff would invalidate the g-code on every apply).
|
||||
std::vector<int> f_nozzle_maps = m_fff_print->get_filament_nozzle_maps();
|
||||
m_current_plate->set_filament_nozzle_maps(f_nozzle_maps);
|
||||
}
|
||||
wxCommandEvent evt(m_event_slicing_completed_id);
|
||||
// Post the Slicing Finished message for the G-code viewer to update.
|
||||
// Passing the timestamp
|
||||
@@ -241,6 +254,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());
|
||||
|
||||
|
||||
@@ -590,7 +590,7 @@ PingCodeBindDialog::~PingCodeBindDialog() {
|
||||
sizer_error_code->Add(m_st_txt_error_code, 0, wxALL, 0);
|
||||
|
||||
|
||||
auto st_title_error_desc = new wxStaticText(m_sw_bind_failed_info, wxID_ANY, wxT("Error desc"));
|
||||
auto st_title_error_desc = new wxStaticText(m_sw_bind_failed_info, wxID_ANY, _L("Error desc"));
|
||||
auto st_title_error_desc_doc = new wxStaticText(m_sw_bind_failed_info, wxID_ANY, ": ");
|
||||
m_st_txt_error_desc = new Label(m_sw_bind_failed_info, wxEmptyString);
|
||||
st_title_error_desc->SetForegroundColour(0x909090);
|
||||
@@ -607,7 +607,7 @@ PingCodeBindDialog::~PingCodeBindDialog() {
|
||||
sizer_error_desc->Add(st_title_error_desc_doc, 0, wxALL, 0);
|
||||
sizer_error_desc->Add(m_st_txt_error_desc, 0, wxALL, 0);
|
||||
|
||||
auto st_title_extra_info = new wxStaticText(m_sw_bind_failed_info, wxID_ANY, wxT("Extra info"));
|
||||
auto st_title_extra_info = new wxStaticText(m_sw_bind_failed_info, wxID_ANY, _L("Extra info"));
|
||||
auto st_title_extra_info_doc = new wxStaticText(m_sw_bind_failed_info, wxID_ANY, ": ");
|
||||
m_st_txt_extra_info = new Label(m_sw_bind_failed_info, wxEmptyString);
|
||||
st_title_extra_info->SetForegroundColour(0x909090);
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -28,8 +28,8 @@ list(APPEND SLIC3R_GUI_SOURCES
|
||||
GUI/DeviceCore/DevFilaSystem.h
|
||||
GUI/DeviceCore/DevFilaSystem.cpp
|
||||
GUI/DeviceCore/DevFilaSystemCtrl.cpp
|
||||
GUI/DeviceCore/DevUtilBackend.h
|
||||
GUI/DeviceCore/DevUtilBackend.cpp
|
||||
GUI/DeviceCore/DevFilaSwitch.h
|
||||
GUI/DeviceCore/DevFilaSwitch.cpp
|
||||
GUI/DeviceCore/DevFirmware.h
|
||||
GUI/DeviceCore/DevFirmware.cpp
|
||||
GUI/DeviceCore/DevPrintOptions.h
|
||||
@@ -49,10 +49,17 @@ list(APPEND SLIC3R_GUI_SOURCES
|
||||
GUI/DeviceCore/DevManager.cpp
|
||||
GUI/DeviceCore/DevMapping.h
|
||||
GUI/DeviceCore/DevMapping.cpp
|
||||
GUI/DeviceCore/DevMappingNozzle.h
|
||||
GUI/DeviceCore/DevMappingNozzle.cpp
|
||||
GUI/DeviceCore/DevNozzleSystem.h
|
||||
GUI/DeviceCore/DevNozzleSystem.cpp
|
||||
GUI/DeviceCore/DevNozzleRack.h
|
||||
GUI/DeviceCore/DevNozzleRack.cpp
|
||||
GUI/DeviceCore/DevNozzleRackCtrl.cpp
|
||||
GUI/DeviceCore/DevUtil.h
|
||||
GUI/DeviceCore/DevUtil.cpp
|
||||
GUI/DeviceCore/DevUtilBackend.h
|
||||
GUI/DeviceCore/DevUtilBackend.cpp
|
||||
|
||||
)
|
||||
set(SLIC3R_GUI_SOURCES ${SLIC3R_GUI_SOURCES} PARENT_SCOPE)
|
||||
@@ -87,6 +87,28 @@ std::string DevPrinterConfigUtil::get_printer_ext_img(const std::string& type_st
|
||||
return (vec.size() > pos) ? vec[pos] : std::string();
|
||||
};
|
||||
|
||||
std::string DevPrinterConfigUtil::get_filament_load_img(const std::string &type_str, int ext_id, bool has_nozzle_rack)
|
||||
{
|
||||
if (has_nozzle_rack)
|
||||
{
|
||||
const auto &rack_vec = get_value_from_config<std::vector<std::string>>(type_str, "filament_load_image_nozzle_rack");
|
||||
if (!rack_vec.empty())
|
||||
{
|
||||
if (ext_id >= 0 && static_cast<size_t>(ext_id) < rack_vec.size())
|
||||
{
|
||||
return rack_vec[ext_id];
|
||||
}
|
||||
return rack_vec[0];
|
||||
}
|
||||
}
|
||||
const auto &vec = get_value_from_config<std::vector<std::string>>(type_str, "filament_load_image");
|
||||
if (ext_id >= 0 && static_cast<size_t>(ext_id) < vec.size())
|
||||
{
|
||||
return vec[ext_id];
|
||||
}
|
||||
return vec.empty() ? std::string() : vec[0];
|
||||
}
|
||||
|
||||
std::string DevPrinterConfigUtil::get_fan_text(const std::string& type_str, const std::string& key)
|
||||
{
|
||||
std::vector<std::string> filaments;
|
||||
|
||||
@@ -74,6 +74,7 @@ public:
|
||||
static std::string get_printer_use_ams_type(std::string type_str) { return get_value_from_config<std::string>(type_str, "use_ams_type"); }
|
||||
static std::string get_printer_ams_img(const std::string& type_str) { return get_value_from_config<std::string>(type_str, "printer_use_ams_image"); }
|
||||
static std::string get_printer_ext_img(const std::string& type_str, int pos);//printer_ext_image
|
||||
static std::string get_filament_load_img(const std::string &type_str, int ext_id, bool has_nozzle_rack = false);
|
||||
|
||||
/*fan*/
|
||||
static std::string get_fan_text(const std::string& type_str, const std::string& key);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
enum PrinterArch
|
||||
{
|
||||
@@ -47,6 +48,32 @@ enum DevAmsType : int
|
||||
AMS_LITE = 2, // AMS-Lite
|
||||
N3F = 3, // N3F, AMS 2PRO
|
||||
N3S = 4, // N3S, AMS HT
|
||||
AMS_LITE_MIXED = 5, // AMS-Lite for N9
|
||||
};
|
||||
|
||||
// Device-numbered filament-change step codes. Newer firmware reports the exact change-step
|
||||
// sequence through the AMS (ams.cfs), keyed by these codes, instead of the client hardcoding
|
||||
// steps per model. Distinct from the legacy GUI-side Slic3r::GUI::FilamentStep enum.
|
||||
// STEP_CHECK_POSITION and STEP_CONFIRM_EXTRUDED share code 0x08 by device design.
|
||||
enum DevFilamentStep
|
||||
{
|
||||
STEP_IDLE = 0x00,
|
||||
STEP_PAUSE = 0x01,
|
||||
STEP_HEAT_NOZZLE = 0x02,
|
||||
STEP_CUT_FILAMENT = 0x03,
|
||||
STEP_PULL_CURR_FILAMENT = 0x04,
|
||||
STEP_PUSH_NEW_FILAMENT = 0x05,
|
||||
STEP_GRAB_NEW_FILAMENT = 0x06,
|
||||
STEP_PURGE_OLD_FILAMENT = 0x07,
|
||||
STEP_CHECK_POSITION = 0x08,
|
||||
STEP_SWITCH_EXTRUDER = 0x09,
|
||||
STEP_SWITCH_HOTEND = 0x0A,
|
||||
STEP_AMS_FILA_COOLING = 0x0B,
|
||||
STEP_PUSH_SWITCHER_FILA = 0x0C,
|
||||
STEP_PULL_SWITCHER_FILA = 0x0D,
|
||||
STEP_SWITCHER_SWITCH = 0x0E,
|
||||
STEP_CONFIRM_EXTRUDED = 0x08,
|
||||
STEP_COUNT,
|
||||
};
|
||||
|
||||
// Slots and Tray
|
||||
@@ -56,21 +83,44 @@ enum DevAmsType : int
|
||||
#define VIRTUAL_AMS_MAIN_ID_STR "255"
|
||||
#define VIRTUAL_AMS_DEPUTY_ID_STR "254"
|
||||
|
||||
// Tray index offset for the AMS-Lite-mixed unit (A2L / N9). Its 4 trays occupy
|
||||
// global tray indices 24..27.
|
||||
#define AMS_LITE_MIXED_TRAY_INDEX_OFFSET 24
|
||||
|
||||
#define INVALID_AMS_TEMPERATURE std::numeric_limits<float>::min()
|
||||
|
||||
// (ams_id, slot_id) pair.
|
||||
using DevAmsSlotId = std::pair<int, int>;
|
||||
|
||||
/* Extruder*/
|
||||
#define MAIN_EXTRUDER_ID 0
|
||||
#define DEPUTY_EXTRUDER_ID 1
|
||||
#define UNIQUE_EXTRUDER_ID MAIN_EXTRUDER_ID
|
||||
#define INVALID_EXTRUDER_ID -1
|
||||
|
||||
/* Logical extruder ids (multi-nozzle). */
|
||||
#define LOGIC_UNIQUE_EXTRUDER_ID 0
|
||||
#define LOGIC_L_EXTRUDER_ID 0
|
||||
#define LOGIC_R_EXTRUDER_ID 1
|
||||
|
||||
|
||||
/* Nozzle*/
|
||||
enum NozzleFlowType
|
||||
{
|
||||
NONE_FLOWTYPE,
|
||||
S_FLOW,
|
||||
H_FLOW
|
||||
H_FLOW,
|
||||
U_FLOW, // TPU 1.75 High Flow (device-reported; maps to nvtTPUHighFlow)
|
||||
};
|
||||
|
||||
// Discrete nozzle diameters reported by the device.
|
||||
enum NozzleDiameterType : int
|
||||
{
|
||||
NONE_DIAMETER_TYPE,
|
||||
NOZZLE_DIAMETER_0_2,
|
||||
NOZZLE_DIAMETER_0_4,
|
||||
NOZZLE_DIAMETER_0_6,
|
||||
NOZZLE_DIAMETER_0_8
|
||||
};
|
||||
|
||||
/*Print speed*/
|
||||
@@ -105,4 +155,39 @@ public:
|
||||
static bool IsVirtualSlot(const std::string& ams_id) { return (ams_id == VIRTUAL_AMS_MAIN_ID_STR || ams_id == VIRTUAL_AMS_DEPUTY_ID_STR); }
|
||||
};
|
||||
|
||||
};// namespace Slic3r
|
||||
namespace GUI
|
||||
{
|
||||
// Print source. Defined here (rather than in SelectMachine.hpp) so the shared device-mapping
|
||||
// GUI headers — AmsMappingPopup, wgtDeviceNozzleSelect — can name it without pulling in
|
||||
// SelectMachine.hpp, which would create an include cycle.
|
||||
enum PrintFromType
|
||||
{
|
||||
FROM_NORMAL,
|
||||
FROM_SDCARD_VIEW,
|
||||
};
|
||||
}
|
||||
|
||||
};// namespace Slic3r
|
||||
|
||||
// A nozzle requirement of the sliced plate (or an installed nozzle's identity), compared in the
|
||||
// pre-print slicing-vs-installed nozzle checks.
|
||||
struct NozzleDef
|
||||
{
|
||||
float nozzle_diameter;
|
||||
Slic3r::NozzleFlowType nozzle_flow_type;
|
||||
|
||||
bool operator==(const NozzleDef& other) const
|
||||
{
|
||||
return nozzle_diameter == other.nozzle_diameter && nozzle_flow_type == other.nozzle_flow_type;
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct std::hash<NozzleDef>
|
||||
{
|
||||
std::size_t operator()(const NozzleDef& v) const noexcept
|
||||
{
|
||||
size_t h1 = std::hash<int>{}(v.nozzle_diameter * 1000);
|
||||
size_t h2 = std::hash<int>{}(v.nozzle_flow_type);
|
||||
return h1 ^ (h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2));
|
||||
};
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "DevExtruderSystem.h"
|
||||
#include "DevNozzleSystem.h"
|
||||
#include "DevFilaSystem.h" // complete type for GetFilaSystem()->GetTrayIndexMap() in GetBackupAmsSlotInGroup
|
||||
|
||||
// TODO: remove this include
|
||||
#include "slic3r/GUI/DeviceManager.hpp"
|
||||
@@ -48,17 +49,43 @@ namespace Slic3r
|
||||
|
||||
NozzleType DevExtder::GetNozzleType() const
|
||||
{
|
||||
return system->Owner()->GetNozzleSystem()->GetNozzle(m_current_nozzle_id).m_nozzle_type;
|
||||
return system->Owner()->GetNozzleSystem()->GetExtNozzle(m_current_nozzle_id).m_nozzle_type;
|
||||
}
|
||||
|
||||
NozzleFlowType DevExtder::GetNozzleFlowType() const
|
||||
{
|
||||
return system->Owner()->GetNozzleSystem()->GetNozzle(m_current_nozzle_id).m_nozzle_flow;
|
||||
return system->Owner()->GetNozzleSystem()->GetExtNozzle(m_current_nozzle_id).m_nozzle_flow;
|
||||
}
|
||||
|
||||
float DevExtder::GetNozzleDiameter() const
|
||||
{
|
||||
return system->Owner()->GetNozzleSystem()->GetNozzle(m_current_nozzle_id).m_diameter;
|
||||
return system->Owner()->GetNozzleSystem()->GetExtNozzle(m_current_nozzle_id).m_diameter;
|
||||
}
|
||||
|
||||
std::unordered_map<int, bool> DevExtder::GetBackupStatus(unsigned int fila_back_group)
|
||||
{
|
||||
std::unordered_map<int, bool> trayid_group;
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
if (fila_back_group & (1 << i))
|
||||
{
|
||||
trayid_group[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = 16; j <= 23; j++)/* single ams is from 128*/
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
DevExtderSystem::DevExtderSystem(MachineObject* obj)
|
||||
@@ -143,6 +170,24 @@ namespace Slic3r
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<DevAmsSlotId> DevExtderSystem::GetBackupAmsSlotInGroup(const DevAmsSlotId& ams_slot_id)
|
||||
{
|
||||
std::map<int, DevAmsSlotId> tray_map = Owner()->GetFilaSystem()->GetTrayIndexMap();
|
||||
|
||||
std::vector<DevAmsSlotId> backup_group;
|
||||
for (const auto& extruder : m_extders) {
|
||||
for (int fila_backup : extruder.GetFilamBackup()) {
|
||||
for (auto [tray_id, is_valid] : DevExtder::GetBackupStatus(fila_backup)) {
|
||||
if (is_valid && tray_map.find(tray_id) != tray_map.end() && tray_map[tray_id] != ams_slot_id) {
|
||||
backup_group.emplace_back(tray_map[tray_id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return backup_group;
|
||||
}
|
||||
|
||||
void ExtderSystemParser::ParseV1_0(const nlohmann::json& print_json, DevExtderSystem* system)
|
||||
{
|
||||
if (system->GetTotalExtderCount() != 1)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
#include "libslic3r/CommonDefs.hpp"
|
||||
|
||||
#include "slic3r/Utils/json_diff.hpp"
|
||||
@@ -66,6 +67,10 @@ public:
|
||||
bool HasFilamBackup() const { return !m_filam_bak.empty(); }
|
||||
std::vector<int> GetFilamBackup() const { return m_filam_bak; }
|
||||
|
||||
// Decode a filament-backup bit group into { tray_id -> valid }. Bits 0-15 map directly to tray ids,
|
||||
// bits 16-23 to single-AMS tray ids 128+, bits 24-27 to AMS-Lite-mixed (N9) tray ids 24-27.
|
||||
static std::unordered_map<int, bool> GetBackupStatus(unsigned int fila_back_group);
|
||||
|
||||
// ams binding on current extruder
|
||||
const DevAmsSlotInfo& GetSlotPre() const { return m_spre; }
|
||||
const DevAmsSlotInfo& GetSlotNow() const { return m_snow; }
|
||||
@@ -168,6 +173,11 @@ public:
|
||||
bool HasFilamentBackup() const;
|
||||
bool HasFilamentInExt(int exter_id) { return GetExtderById(exter_id) ? GetExtderById(exter_id)->HasFilamentInExt() : false; }
|
||||
|
||||
// List the other AMS slots that back up the given slot within the same filament-backup group,
|
||||
// across all extruders. Uses DevFilaSystem::GetTrayIndexMap() to translate backup bits (incl. the
|
||||
// A2L/N9 AMS-Lite-mixed trays 24-27) into ams/slot ids.
|
||||
std::vector<DevAmsSlotId> GetBackupAmsSlotInGroup(const DevAmsSlotId& ams_slot_id);
|
||||
|
||||
protected:
|
||||
void AddExtder(const DevExtder& ext) { m_extders[ext.GetExtId()] = ext; };
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
|
||||
#include "DevFilaBlackList.h"
|
||||
#include "DevFilaSystem.h"
|
||||
#include "DevManager.h"
|
||||
#include "DevConfigUtil.h"
|
||||
|
||||
#include "libslic3r/Utils.hpp"
|
||||
|
||||
@@ -73,64 +77,97 @@ static std::string _get_filament_name_from_ams(int ams_id, int slot_id)
|
||||
}
|
||||
|
||||
// moved from tao.wang and zhimin.zeng
|
||||
void check_filaments(std::string model_id,
|
||||
std::string tag_vendor,
|
||||
std::string tag_type,
|
||||
int ams_id,
|
||||
int slot_id,
|
||||
std::string tag_name,
|
||||
bool& in_blacklist,
|
||||
std::string& ac,
|
||||
wxString& info,
|
||||
wxString& wiki_url)
|
||||
// Struct-in / struct-out, accumulate-all parser: every matching rule is pushed into
|
||||
// result.action_items[action] and scanning continues (rather than stopping at the first match).
|
||||
void check_filaments(const DevFilaBlacklist::CheckFilamentInfo& check_info, DevFilaBlacklist::CheckResult& result)
|
||||
{
|
||||
std::string tag_type = check_info.fila_type;
|
||||
std::string tag_name = check_info.fila_name;
|
||||
std::string tag_vendor = check_info.fila_vendor;
|
||||
std::string tag_calib_mode = check_info.calib_mode;
|
||||
|
||||
// Orca: the print-send and calibration consumers do not populate fila_name; recover it from the
|
||||
// selected AMS slot so name / name_suffix rules keep matching.
|
||||
if (tag_name.empty())
|
||||
{
|
||||
tag_name = _get_filament_name_from_ams(ams_id, slot_id);
|
||||
tag_name = _get_filament_name_from_ams(check_info.ams_id, check_info.slot_id);
|
||||
}
|
||||
|
||||
in_blacklist = false;
|
||||
std::transform(tag_vendor.begin(), tag_vendor.end(), tag_vendor.begin(), ::tolower);
|
||||
std::transform(tag_type.begin(), tag_type.end(), tag_type.begin(), ::tolower);
|
||||
std::transform(tag_name.begin(), tag_name.end(), tag_name.begin(), ::tolower);
|
||||
std::transform(tag_calib_mode.begin(), tag_calib_mode.end(), tag_calib_mode.begin(), ::tolower);
|
||||
|
||||
DevFilaBlacklist::load_filaments_blacklist_config();
|
||||
if (DevFilaBlacklist::filaments_blacklist.contains("blacklist"))
|
||||
{
|
||||
for (auto filament_item : DevFilaBlacklist::filaments_blacklist["blacklist"])
|
||||
{
|
||||
|
||||
std::string vendor = filament_item.contains("vendor") ? filament_item["vendor"].get<std::string>() : "";
|
||||
std::string type = filament_item.contains("type") ? filament_item["type"].get<std::string>() : "";
|
||||
std::string type_suffix = filament_item.contains("type_suffix") ? filament_item["type_suffix"].get<std::string>() : "";
|
||||
std::string name = filament_item.contains("name") ? filament_item["name"].get<std::string>() : "";
|
||||
std::string slot = filament_item.contains("slot") ? filament_item["slot"].get<std::string>() : "";
|
||||
std::vector<std::string> model_ids = filament_item.contains("model_id") ? filament_item["model_id"].get<std::vector<std::string>>() : std::vector<std::string>();
|
||||
std::string action = filament_item.contains("action") ? filament_item["action"].get<std::string>() : "";
|
||||
std::string description = filament_item.contains("description") ? filament_item["description"].get<std::string>() : "";
|
||||
|
||||
// blacklist items
|
||||
std::string vendor = filament_item.contains("vendor") ? filament_item["vendor"].get<std::string>() : "";
|
||||
std::string type = filament_item.contains("type") ? filament_item["type"].get<std::string>() : "";
|
||||
std::vector<std::string> types = filament_item.contains("types") ? filament_item["types"].get<std::vector<std::string>>() : std::vector<std::string>();
|
||||
std::string type_suffix = filament_item.contains("type_suffix") ? filament_item["type_suffix"].get<std::string>() : "";
|
||||
std::string name_suffix = filament_item.contains("name_suffix") ? filament_item["name_suffix"].get<std::string>() : "";
|
||||
std::string name = filament_item.contains("name") ? filament_item["name"].get<std::string>() : "";
|
||||
std::string slot = filament_item.contains("slot") ? filament_item["slot"].get<std::string>() : "";
|
||||
std::optional<bool> used_for_print_support = filament_item.contains("used_for_print_support") ? filament_item["used_for_print_support"].get<bool>() : std::optional<bool>();
|
||||
std::optional<bool> used_for_print_object = filament_item.contains("used_for_print_object") ? filament_item["used_for_print_object"].get<bool>() : std::optional<bool>();
|
||||
std::vector<std::string> model_ids = filament_item.contains("model_id") ? filament_item["model_id"].get<std::vector<std::string>>() : std::vector<std::string>();
|
||||
std::vector<int> extruder_ids = filament_item.contains("extruder_id") ? filament_item["extruder_id"].get<std::vector<int>>() : std::vector<int>();
|
||||
std::vector<std::string> nozzle_flows = filament_item.contains("nozzle_flows") ? filament_item["nozzle_flows"].get<std::vector<std::string>>() : std::vector<std::string>();
|
||||
std::string calib_mode = filament_item.contains("calib_mode") ? filament_item["calib_mode"].get<std::string>() : "";
|
||||
|
||||
// check model id
|
||||
if (!model_ids.empty() && std::find(model_ids.begin(), model_ids.end(), model_id) == model_ids.end()) { continue; }
|
||||
if (!model_ids.empty() && std::find(model_ids.begin(), model_ids.end(), check_info.model_id) == model_ids.end()) { continue; }
|
||||
|
||||
// A rule keyed on nozzle_flows/nozzle_diameters only matches when check_info carries real
|
||||
// per-nozzle context (threaded by the print-send consumer). Consumers that leave
|
||||
// nozzle_flow/nozzle_diameter unset (AMS edit, calibration, and the no-nozzle-context
|
||||
// fallback) never satisfy the non-empty match below, so those rules stay dormant there.
|
||||
|
||||
// check nozzle flows
|
||||
if (!nozzle_flows.empty() && std::find(nozzle_flows.begin(), nozzle_flows.end(), check_info.nozzle_flow.value_or("")) == nozzle_flows.end()) { continue; }
|
||||
|
||||
// check nozzle diameter
|
||||
const std::vector<float> nozzle_diameters = filament_item.contains("nozzle_diameters") ? filament_item["nozzle_diameters"].get<std::vector<float>>() : std::vector<float>();
|
||||
if (!nozzle_diameters.empty() && check_info.nozzle_diameter.has_value() &&
|
||||
std::find(nozzle_diameters.begin(), nozzle_diameters.end(), check_info.nozzle_diameter.value()) == nozzle_diameters.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// check vendor
|
||||
std::transform(vendor.begin(), vendor.end(), vendor.begin(), ::tolower);
|
||||
if (!vendor.empty())
|
||||
{
|
||||
if ((vendor == "bambu lab" && (tag_vendor == vendor)) ||
|
||||
(vendor == "third party" && (tag_vendor != "bambu lab")))
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
else
|
||||
if (!((vendor == "bambu lab" && tag_vendor == "bambu lab") ||
|
||||
(vendor == "third party" && tag_vendor != "bambu lab")))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// check calib mode
|
||||
std::transform(calib_mode.begin(), calib_mode.end(), calib_mode.begin(), ::tolower);
|
||||
if (!calib_mode.empty() && calib_mode != tag_calib_mode) { continue; }
|
||||
|
||||
// check type
|
||||
std::transform(type.begin(), type.end(), type.begin(), ::tolower);
|
||||
if (!type.empty() && (type != tag_type)) { continue; }
|
||||
|
||||
// check types (plural): item matches if tag_type is any of the listed types.
|
||||
if (!types.empty())
|
||||
{
|
||||
auto it = std::find_if(types.begin(), types.end(), [&tag_type](std::string ttype) {
|
||||
std::transform(ttype.begin(), ttype.end(), ttype.begin(), ::tolower);
|
||||
return ttype == tag_type;
|
||||
});
|
||||
if (it == types.end()) { continue; }
|
||||
}
|
||||
|
||||
// check type suffix
|
||||
std::transform(type_suffix.begin(), type_suffix.end(), type_suffix.begin(), ::tolower);
|
||||
if (!type_suffix.empty())
|
||||
@@ -143,10 +180,31 @@ void check_filaments(std::string model_id,
|
||||
std::transform(name.begin(), name.end(), name.begin(), ::tolower);
|
||||
if (!name.empty() && (name != tag_name)) { continue; }
|
||||
|
||||
// check name suffix
|
||||
std::transform(name_suffix.begin(), name_suffix.end(), name_suffix.begin(), ::tolower);
|
||||
if (!name_suffix.empty())
|
||||
{
|
||||
if (tag_name.length() < name_suffix.length()) { continue; }
|
||||
if ((tag_name.substr(tag_name.length() - name_suffix.length()) != name_suffix)) { continue; }
|
||||
}
|
||||
|
||||
// check filament used for print support
|
||||
if (used_for_print_support.has_value() && used_for_print_support != check_info.used_for_print_support) { continue; }
|
||||
|
||||
// check filament used for print object
|
||||
if (used_for_print_object.has_value() && used_for_print_object != check_info.used_for_print_object) { continue; }
|
||||
|
||||
// check filament switch
|
||||
if (filament_item.contains("has_filament_switch"))
|
||||
{
|
||||
bool has_filament_switch = filament_item["has_filament_switch"].get<bool>();
|
||||
if (check_info.has_filament_switch != has_filament_switch) { continue; }
|
||||
}
|
||||
|
||||
// check loc
|
||||
if (!slot.empty())
|
||||
{
|
||||
bool is_virtual_slot = devPrinterUtil::IsVirtualSlot(ams_id);
|
||||
bool is_virtual_slot = devPrinterUtil::IsVirtualSlot(check_info.ams_id);
|
||||
bool check_virtual_slot = (slot == "ext");
|
||||
bool check_ams_slot = (slot == "ams");
|
||||
if (is_virtual_slot && !check_virtual_slot)
|
||||
@@ -159,71 +217,115 @@ void check_filaments(std::string model_id,
|
||||
}
|
||||
}
|
||||
|
||||
if (GUI::wxGetApp().app_config->get("skip_ams_blacklist_check") == "true") {
|
||||
action = "warning";
|
||||
// check extruder id
|
||||
if (!extruder_ids.empty() && check_info.extruder_id.has_value() &&
|
||||
std::find(extruder_ids.begin(), extruder_ids.end(), check_info.extruder_id.value()) == extruder_ids.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
in_blacklist = true;
|
||||
ac = action;
|
||||
info = _L(description);
|
||||
wiki_url = filament_item.contains("wiki") ? filament_item["wiki"].get<std::string>() : "";
|
||||
return;
|
||||
// white items: a matched rule is suppressed when the filament is whitelisted
|
||||
{
|
||||
// contains match
|
||||
std::set<std::string> white_names = filament_item.contains("white_names") ? filament_item["white_names"].get<std::set<std::string>>() : std::set<std::string>();
|
||||
if (!white_names.empty() && !tag_name.empty())
|
||||
{
|
||||
auto it = std::find_if(white_names.begin(), white_names.end(), [&tag_name](std::string white_name) {
|
||||
std::transform(white_name.begin(), white_name.end(), white_name.begin(), ::tolower);
|
||||
return tag_name.find(white_name) != std::string::npos;
|
||||
});
|
||||
if (it != white_names.end()) { continue; }
|
||||
}
|
||||
}
|
||||
{
|
||||
// equal match
|
||||
std::set<std::string> white_fila_ids = filament_item.contains("white_fila_ids") ? filament_item["white_fila_ids"].get<std::set<std::string>>() : std::set<std::string>();
|
||||
if (!white_fila_ids.empty() && !check_info.fila_id.empty())
|
||||
{
|
||||
auto it = std::find_if(white_fila_ids.begin(), white_fila_ids.end(), [&check_info](const std::string& white_fila_id) {
|
||||
return white_fila_id == check_info.fila_id;
|
||||
});
|
||||
if (it != white_fila_ids.end()) { continue; }
|
||||
}
|
||||
}
|
||||
|
||||
// the item is matched: accumulate it (do not stop scanning)
|
||||
DevFilaBlacklist::CheckResultItem result_item;
|
||||
result_item.action = action;
|
||||
result_item.wiki_url = filament_item.contains("wiki") ? filament_item["wiki"].get<std::string>() : "";
|
||||
|
||||
if (description == "%s has a risk of nozzle clogging when using 0.4mm high-flow nozzles. Use with caution.") {
|
||||
result_item.info_msg = wxString::Format(_L(description), check_info.fila_name);
|
||||
} else if (description == "%s filaments are hard and brittle and could break in AMS, and there is also a risk of nozzle clogging when using 0.4mm high-flow nozzles. Use with caution.") {
|
||||
result_item.info_msg = wxString::Format(_L(description), check_info.fila_type);
|
||||
} else if (description == "%s has a risk of nozzle clogging when using 0.4, 0.6, 0.8mm high-flow nozzles. Use with caution.") {
|
||||
result_item.info_msg = wxString::Format(_L(description), check_info.fila_name);
|
||||
} else if (description == "%s may fail to load or unload due to the Filament Track Switch. If you wish to continue.") {
|
||||
result_item.info_msg = wxString::Format(_L(description), check_info.fila_name);
|
||||
} else {
|
||||
result_item.info_msg = _L(description);
|
||||
}
|
||||
|
||||
result.action_items[result_item.action].push_back(result_item);
|
||||
continue;
|
||||
|
||||
// Error in description
|
||||
L("TPU is not supported by AMS.");
|
||||
L("AMS does not support 'Bambu Lab PET-CF'.");
|
||||
L("The current filament doesn't support the E3D high-flow nozzle and can't be used.");
|
||||
L("The current filament doesn't support the TPU high-flow nozzle and can't be used.");
|
||||
L("Auto dynamic flow calibration is not supported for TPU filament.");
|
||||
L("Bambu TPU 85A is not supported for printing with 0.4 mm Standard or High Flow nozzles.");
|
||||
|
||||
// Warning in description
|
||||
L("How to feed TPU filament.");
|
||||
L("How to feed TPU filament on X2D.");
|
||||
L("Please cold pull before printing TPU to avoid clogging. You may use cold pull maintenance on the printer.");
|
||||
L("Damp PVA will become flexible and get stuck inside AMS, please take care to dry it before use.");
|
||||
L("Damp PVA is flexible and may get stuck in extruder. Dry it before use.");
|
||||
L("The rough surface of PLA Glow can accelerate wear on the AMS system, particularly on the internal components of the AMS Lite.");
|
||||
L("PLA Glow may wear the AMS first stage feeder. Use an external spool instead.");
|
||||
L("CF/GF filaments are hard and brittle, it's easy to break or get stuck in AMS, please use with caution.");
|
||||
L("PPS-CF is brittle and could break in bended PTFE tube above Toolhead.");
|
||||
L("PPA-CF is brittle and could break in bended PTFE tube above Toolhead.");
|
||||
L("Default settings may affect print quality. Adjust as needed for best results.");
|
||||
L("%s has a risk of nozzle clogging when using 0.4mm high-flow nozzles. Use with caution.");
|
||||
L("%s filaments are hard and brittle and could break in AMS, and there is also a risk of nozzle clogging when using 0.4mm high-flow nozzles. Use with caution.");
|
||||
L("%s has a risk of nozzle clogging when using 0.4, 0.6, 0.8mm high-flow nozzles. Use with caution.");
|
||||
L("%s may fail to load or unload due to the Filament Track Switch. If you wish to continue.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void DevFilaBlacklist::check_filaments_in_blacklist(std::string model_id,
|
||||
std::string tag_vendor,
|
||||
std::string tag_type,
|
||||
const std::string& filament_id,
|
||||
int ams_id,
|
||||
int slot_id,
|
||||
std::string tag_name,
|
||||
bool& in_blacklist,
|
||||
std::string& ac,
|
||||
wxString& info)
|
||||
// Extends the prohibition-only bit check with a 4-level filament_extruder_compatibility model
|
||||
// (0 printable / 1 error / 2 critical warning / 3 warning), bowden-extruder message variants and
|
||||
// toolhead display names for the "%s extruder" name.
|
||||
//
|
||||
// Orca: several deliberate divergences preserve H2D dual-extruder behaviour when the compatibility
|
||||
// key is absent (default 0 = printable):
|
||||
// - The extruder is resolved from the ams_id rather than a threaded check_info.extruder_id with an
|
||||
// early-return: extruder_id is only threaded on the print-send consumer, so falling back on
|
||||
// ams_id keeps the bit-check firing on the AMS-edit / calibration paths.
|
||||
// - The prohibition bit-check keeps check_info.fila_type for its "%s", preserving the exact H2D
|
||||
// AMS-edit message that ships today. The new 4-level messages use the fila_name fallback since
|
||||
// they stay inert until a filament declares the compatibility key (only X2D ships it).
|
||||
// - is_bowden_extruder is assigned correctly here so the Bowden message variants actually fire.
|
||||
void check_filaments_printable(const DevFilaBlacklist::CheckFilamentInfo& check_info, DevFilaBlacklist::CheckResult& result)
|
||||
{
|
||||
wxString wiki_url;
|
||||
check_filaments_in_blacklist_url(model_id, tag_vendor, tag_type, filament_id, ams_id, slot_id, tag_name, in_blacklist, ac, info, wiki_url);
|
||||
}
|
||||
DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
|
||||
if (!dev) { return; }
|
||||
|
||||
MachineObject* obj = dev->get_selected_machine();
|
||||
if (obj == nullptr || !obj->is_multi_extruders()) { return; }
|
||||
|
||||
bool check_filaments_printable(const std::string &tag_vendor, const std::string &tag_type, const std::string& filament_id, int ams_id, bool &in_blacklist, std::string &ac, wxString &info)
|
||||
{
|
||||
DeviceManager *dev = Slic3r::GUI::wxGetApp().getDeviceManager();
|
||||
if (!dev) {
|
||||
return true;
|
||||
}
|
||||
Preset* printer_preset = GUI::get_printer_preset(obj);
|
||||
if (!printer_preset) { return; }
|
||||
|
||||
MachineObject *obj = dev->get_selected_machine();
|
||||
if (obj == nullptr || !obj->is_multi_extruders()) {
|
||||
return true;
|
||||
}
|
||||
ConfigOptionInts* physical_extruder_map_op = dynamic_cast<ConfigOptionInts*>(printer_preset->config.option("physical_extruder_map"));
|
||||
if (!physical_extruder_map_op) { return; }
|
||||
|
||||
Preset *printer_preset = GUI::get_printer_preset(obj);
|
||||
if (!printer_preset)
|
||||
return true;
|
||||
|
||||
ConfigOptionInts *physical_extruder_map_op = dynamic_cast<ConfigOptionInts *>(printer_preset->config.option("physical_extruder_map"));
|
||||
if (!physical_extruder_map_op)
|
||||
return true;
|
||||
std::vector<int> physical_extruder_maps = physical_extruder_map_op->values;
|
||||
int obj_extruder_id = obj->get_extruder_id_by_ams_id(std::to_string(ams_id));
|
||||
int obj_extruder_id = obj->get_extruder_id_by_ams_id(std::to_string(check_info.ams_id));
|
||||
int extruder_idx = obj_extruder_id;
|
||||
for (int index = 0; index < physical_extruder_maps.size(); ++index) {
|
||||
if (physical_extruder_maps[index] == obj_extruder_id) {
|
||||
@@ -232,32 +334,85 @@ bool check_filaments_printable(const std::string &tag_vendor, const std::string
|
||||
}
|
||||
}
|
||||
|
||||
PresetBundle *preset_bundle = GUI::wxGetApp().preset_bundle;
|
||||
std::optional<FilamentBaseInfo> filament_info = preset_bundle->get_filament_by_filament_id(filament_id, printer_preset->name);
|
||||
if (filament_info.has_value() && !(filament_info->filament_printable >> extruder_idx & 1)) {
|
||||
wxString extruder_name = extruder_idx == 0 ? _L("left") : _L("right");
|
||||
ac = "prohibition";
|
||||
info = wxString::Format(_L("%s is not supported by %s extruder."), tag_type, extruder_name);
|
||||
in_blacklist = true;
|
||||
return false;
|
||||
PresetBundle* preset_bundle = GUI::wxGetApp().preset_bundle;
|
||||
std::optional<FilamentBaseInfo> filament_info = preset_bundle->get_filament_by_filament_id(check_info.fila_id, printer_preset->name);
|
||||
if (!filament_info.has_value()) { return; }
|
||||
|
||||
// Physical extruder 0 -> deputy (left), 1 -> main (right); the toolhead display name replaces the
|
||||
// hard-coded left/right labels (falls back to "left"/"right" for printers without an override).
|
||||
int bl_ext_id = (extruder_idx == 0) ? DEPUTY_EXTRUDER_ID : MAIN_EXTRUDER_ID;
|
||||
wxString extruder_name = _L(DevPrinterConfigUtil::get_toolhead_display_name(
|
||||
obj->printer_type, bl_ext_id, ToolHeadComponent::Extruder, ToolHeadNameCase::LowerCase, true));
|
||||
|
||||
// Prohibition-only bit check; keeps fila_type in the "%s" to preserve current behaviour.
|
||||
if (!(filament_info->filament_printable >> extruder_idx & 1)) {
|
||||
DevFilaBlacklist::CheckResultItem item;
|
||||
item.action = "prohibition";
|
||||
item.info_msg = wxString::Format(_L("%s is not supported by %s extruder."), check_info.fila_type, extruder_name);
|
||||
result.action_items[item.action].push_back(item);
|
||||
return;
|
||||
}
|
||||
|
||||
return true;
|
||||
// Is this a bowden extruder? (drives the message variant for compatibility warnings.)
|
||||
bool is_bowden_extruder = false;
|
||||
auto extruder_type_opt = dynamic_cast<const ConfigOptionEnumsGeneric*>(printer_preset->config.option("extruder_type"));
|
||||
if (extruder_type_opt && (int) extruder_type_opt->values.size() > extruder_idx) {
|
||||
ExtruderType extruder_type = (ExtruderType) extruder_type_opt->values[extruder_idx];
|
||||
is_bowden_extruder = (extruder_type == ExtruderType::etBowden);
|
||||
}
|
||||
|
||||
// Compatibility levels: 0 = printable, 1 = error, 2 = critical warning, 3 = warning (4-7 reserved).
|
||||
std::string fila_name = check_info.fila_name.empty() ? check_info.fila_type : check_info.fila_name;
|
||||
int compatible_val = filament_info->get_extruder_compatibility(extruder_idx);
|
||||
if (compatible_val == 0) {
|
||||
// printable, nothing to report
|
||||
} else if (compatible_val == 1) {
|
||||
DevFilaBlacklist::CheckResultItem item;
|
||||
item.action = "prohibition";
|
||||
item.info_msg = wxString::Format(_L("%s is not supported by %s extruder."), fila_name, extruder_name);
|
||||
result.action_items[item.action].push_back(item);
|
||||
} else if (compatible_val == 2) {
|
||||
DevFilaBlacklist::CheckResultItem item;
|
||||
item.action = "warning";
|
||||
item.info_msg = is_bowden_extruder
|
||||
? wxString::Format(_L("There may be critical print quality issues when printing '%s' with %s Bowden extruder. Use with caution!"), fila_name, extruder_name)
|
||||
: wxString::Format(_L("There may be critical print quality issues when printing '%s' with %s extruder. Use with caution!"), fila_name, extruder_name);
|
||||
result.action_items[item.action].push_back(item);
|
||||
} else if (compatible_val == 3) {
|
||||
DevFilaBlacklist::CheckResultItem item;
|
||||
item.action = "warning";
|
||||
item.info_msg = is_bowden_extruder
|
||||
? wxString::Format(_L("There may be print quality issues when printing '%s' with %s Bowden extruder. Use with caution."), fila_name, extruder_name)
|
||||
: wxString::Format(_L("There may be print quality issues when printing '%s' with %s extruder. Use with caution."), fila_name, extruder_name);
|
||||
result.action_items[item.action].push_back(item);
|
||||
}
|
||||
}
|
||||
|
||||
void DevFilaBlacklist::check_filaments_in_blacklist_url(std::string model_id, std::string tag_vendor, std::string tag_type, const std::string& filament_id, int ams_id, int slot_id, std::string tag_name, bool& in_blacklist, std::string& ac, wxString& info, wxString& wiki_url)
|
||||
DevFilaBlacklist::CheckResult DevFilaBlacklist::check_filaments_in_blacklist(const CheckFilamentInfo& info)
|
||||
{
|
||||
if (ams_id < 0 || slot_id < 0)
|
||||
CheckResult result;
|
||||
if (info.ams_id < 0 || info.slot_id < 0)
|
||||
{
|
||||
return;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!check_filaments_printable(tag_vendor, tag_type, filament_id, ams_id, in_blacklist, ac, info))
|
||||
{
|
||||
return;
|
||||
// check the filaments in preset
|
||||
check_filaments_printable(info, result);
|
||||
|
||||
// check the filaments in blacklist file
|
||||
check_filaments(info, result);
|
||||
|
||||
// If skip_ams_blacklist_check is true, demote every prohibition to a warning (aggregate: demote
|
||||
// all at once rather than per-item).
|
||||
if (GUI::wxGetApp().app_config->get("skip_ams_blacklist_check") == "true") {
|
||||
const auto& prohibit_items = result.get_items_by_action("prohibition");
|
||||
if (!prohibit_items.empty()) {
|
||||
for (const auto& prohibit_item : prohibit_items) { result.action_items["warning"].push_back(prohibit_item); }
|
||||
result.action_items.erase("prohibition");
|
||||
}
|
||||
}
|
||||
|
||||
check_filaments(model_id, tag_vendor, tag_type, ams_id, slot_id, tag_name, in_blacklist, ac, info, wiki_url);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,70 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <wx/string.h>
|
||||
#include "slic3r/Utils/json_diff.hpp"
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
class MachineObject;
|
||||
class DevFilaBlacklist
|
||||
{
|
||||
public:
|
||||
// Struct-in / struct-out check API that accumulates all matching rules,
|
||||
// replacing the earlier out-param, first-match overloads.
|
||||
// The per-nozzle dimensions (extruder_id/nozzle_flow/nozzle_diameter), calib_mode and
|
||||
// has_filament_switch are currently left unset, so the engine behaves exactly as the
|
||||
// old first-match parser for the shipping rule set.
|
||||
struct CheckFilamentInfo
|
||||
{
|
||||
std::string dev_id;
|
||||
std::string model_id;
|
||||
|
||||
std::string fila_id;
|
||||
std::string fila_type;
|
||||
std::string fila_name;
|
||||
std::string fila_vendor;
|
||||
|
||||
std::string calib_mode;
|
||||
bool has_filament_switch = false;
|
||||
|
||||
std::optional<bool> used_for_print_support;// optional
|
||||
std::optional<bool> used_for_print_object;// optional
|
||||
|
||||
int ams_id;
|
||||
int slot_id;
|
||||
|
||||
std::optional<int> extruder_id;// optional
|
||||
std::optional<std::string> nozzle_flow;// optional
|
||||
std::optional<float> nozzle_diameter;// optional
|
||||
};
|
||||
|
||||
struct CheckResultItem
|
||||
{
|
||||
std::string action;// warning/prohibition
|
||||
wxString info_msg;
|
||||
wxString wiki_url;
|
||||
};
|
||||
|
||||
struct CheckResult
|
||||
{
|
||||
std::map<std::string, std::vector<CheckResultItem>> action_items;
|
||||
std::vector<CheckResultItem> get_items_by_action(const std::string& action) const
|
||||
{
|
||||
auto it = action_items.find(action);
|
||||
if (it != action_items.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return std::vector<CheckResultItem>();
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
static bool load_filaments_blacklist_config();
|
||||
static void check_filaments_in_blacklist(std::string model_id, std::string tag_vendor, std::string tag_type, const std::string& filament_id, int ams_id, int slot_id, std::string tag_name, bool& in_blacklist, std::string& ac, wxString& info);
|
||||
static void check_filaments_in_blacklist_url(std::string model_id, std::string tag_vendor, std::string tag_type, const std::string& filament_id, int ams_id, int slot_id, std::string tag_name, bool& in_blacklist, std::string& ac, wxString& info, wxString& wiki_url);
|
||||
static CheckResult check_filaments_in_blacklist(const CheckFilamentInfo& info);
|
||||
|
||||
public:
|
||||
static json filaments_blacklist;
|
||||
};// class DevFilaBlacklist
|
||||
|
||||
}// namespace Slic3r
|
||||
}// namespace Slic3r
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
#include "DevExtruderSystem.h"
|
||||
#include "DevFilaSystem.h"
|
||||
#include "DevFilaSwitch.h"
|
||||
#include "DevUtil.h"
|
||||
|
||||
#include "slic3r/GUI/DeviceManager.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
DevFilaSwitch::DevFilaSwitch(MachineObject *owner) { m_owner = owner; }
|
||||
|
||||
bool DevFilaSwitch::IsReady() const
|
||||
{
|
||||
if (!m_is_installed) { return false; }
|
||||
|
||||
const auto& ams_list = m_owner->GetFilaSystem()->GetAmsList();
|
||||
for (const auto& ams_item : ams_list) {
|
||||
if (ams_item.second->GetBindedExtruderSet().empty()) { return false; }
|
||||
if (!ams_item.second->GetSwitcherPos().has_value()) { return false; }
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DevFilaSwitch::Reset()
|
||||
{
|
||||
m_is_installed = false;
|
||||
m_in_a_has_filament.reset();
|
||||
m_in_b_has_filament.reset();
|
||||
m_in_a_slot.reset();
|
||||
m_in_b_slot.reset();
|
||||
m_out_a_extruder_id.reset();
|
||||
m_out_b_extruder_id.reset();
|
||||
m_cali_status = CaliStatus::CALI_IDLE;
|
||||
}
|
||||
|
||||
std::optional<DevAmsTray> DevFilaSwitch::GetInA_Slot() const
|
||||
{
|
||||
auto ams_id_opt = GetInA_SlotId();
|
||||
if (m_owner && ams_id_opt.has_value()) { return m_owner->get_tray(std::to_string(ams_id_opt->first), std::to_string(ams_id_opt->first)); }
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<DevAmsTray> DevFilaSwitch::GetInB_Slot() const
|
||||
{
|
||||
auto ams_id_opt = GetInB_SlotId();
|
||||
if (m_owner && ams_id_opt.has_value()) { return m_owner->get_tray(std::to_string(ams_id_opt->first), std::to_string(ams_id_opt->first)); }
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<DevExtder> DevFilaSwitch::GetOutA_Extruder() const
|
||||
{
|
||||
auto ext_id_opt = GetOutA_ExtruderId();
|
||||
if (m_owner && ext_id_opt.has_value()) { return m_owner->GetExtderSystem()->GetExtderById(ext_id_opt.value()); }
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<DevExtder> DevFilaSwitch::GetOutB_Extruder() const
|
||||
{
|
||||
auto ext_id_opt = GetOutB_ExtruderId();
|
||||
if (m_owner && ext_id_opt.has_value()) { return m_owner->GetExtderSystem()->GetExtderById(ext_id_opt.value()); }
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void DevFilaSwitch::ParseFilaSwitchInfo(const nlohmann::json &print_jj)
|
||||
{
|
||||
if (print_jj.contains("aux")) {
|
||||
const auto &info_bits = DevJsonValParser::GetVal<std::string>(print_jj, "aux");
|
||||
if (m_is_installed != (DevUtil::get_flag_bits(info_bits, 29, 1) == 1)) {
|
||||
m_is_installed = (DevUtil::get_flag_bits(info_bits, 29, 1) == 1);
|
||||
if (!m_is_installed) { Reset(); }
|
||||
};
|
||||
}
|
||||
|
||||
if (print_jj.contains("device") && print_jj["device"].contains("fila_switch")) {
|
||||
const auto &fila_switch_jj = print_jj["device"]["fila_switch"];
|
||||
if (fila_switch_jj.contains("in")) {
|
||||
const auto &in_vec = DevJsonValParser::GetVal<std::vector<int>>(fila_switch_jj, "in");
|
||||
if (in_vec.size() == 2) {
|
||||
if (in_vec[0] != -1) {
|
||||
DevAmsSlotId slot_id;
|
||||
slot_id.first = DevUtil::get_flag_bits(in_vec[0], 8, 8);
|
||||
slot_id.second = DevUtil::get_flag_bits(in_vec[0], 0, 8);
|
||||
m_in_b_slot = slot_id;
|
||||
} else {
|
||||
m_in_b_slot = std::nullopt;
|
||||
};
|
||||
|
||||
if (in_vec[1] != -1) {
|
||||
DevAmsSlotId slot_id;
|
||||
slot_id.first = DevUtil::get_flag_bits(in_vec[1], 8, 8);
|
||||
slot_id.second = DevUtil::get_flag_bits(in_vec[1], 0, 8);
|
||||
m_in_a_slot = slot_id;
|
||||
} else {
|
||||
m_in_a_slot.reset();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (fila_switch_jj.contains("out")) {
|
||||
const auto &out_vec = DevJsonValParser::GetVal<std::vector<int>>(fila_switch_jj, "out");
|
||||
if (out_vec.size() == 2) {
|
||||
if (out_vec[0] != 0xE) {
|
||||
m_out_b_extruder_id = out_vec[0];
|
||||
} else {
|
||||
m_out_b_extruder_id.reset();
|
||||
}
|
||||
|
||||
if (out_vec[1] != 0xE) {
|
||||
m_out_a_extruder_id = out_vec[1];
|
||||
} else {
|
||||
m_out_a_extruder_id.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fila_switch_jj.contains("stat")) { m_cali_status = DevJsonValParser::GetVal<CaliStatus>(fila_switch_jj, "stat"); }
|
||||
|
||||
if (fila_switch_jj.contains("info")) {
|
||||
const auto &info_bits = DevJsonValParser::GetVal<int>(fila_switch_jj, "info");
|
||||
m_in_b_has_filament = DevUtil::get_flag_bits(info_bits, 0, 1);
|
||||
m_in_a_has_filament = DevUtil::get_flag_bits(info_bits, 0, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}; // namespace Slic3r
|
||||
@@ -0,0 +1,91 @@
|
||||
#pragma once
|
||||
#include "DevDefs.h"
|
||||
#include <optional>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
// Previous definitions
|
||||
class MachineObject;
|
||||
class DevExtder;
|
||||
class DevAmsTray;
|
||||
|
||||
// Filament Track Switch (H2-family hardware).
|
||||
// The filament blacklist consumes IsInstalled() for the `has_filament_switch`
|
||||
// rule (e.g. PLA Silk). m_is_installed defaults to false and is only raised when
|
||||
// the device reports the switch over MQTT (aux bit 29), so the rule stays inert on
|
||||
// every printer that does not report an installed switch.
|
||||
class DevFilaSwitch
|
||||
{
|
||||
public:
|
||||
enum class CaliStatus : int
|
||||
{
|
||||
CALI_IDLE = 0,
|
||||
CALI_STEPING = 1,
|
||||
};
|
||||
|
||||
enum class CaliStep : int
|
||||
{
|
||||
CALI_IDLE = 0,
|
||||
CALI_SWITCHING = 1,
|
||||
CALI_SWITCH_CHECK = 2,
|
||||
CALI_FILA_CHECK = 3,
|
||||
CALI_FILA_TO_AMS = 4,
|
||||
CALI_FILA_TO_SWITCH = 5,
|
||||
CALI_FILA_BACK = 9,
|
||||
CALI_FINISHED = 14,
|
||||
};
|
||||
|
||||
enum SwitchPos : int
|
||||
{
|
||||
POS_IN_B = 0,
|
||||
POS_IN_A = 1,
|
||||
};
|
||||
|
||||
public:
|
||||
DevFilaSwitch(MachineObject* owner);
|
||||
virtual ~DevFilaSwitch() = default;
|
||||
|
||||
public:
|
||||
bool IsInstalled() const { return m_is_installed; };
|
||||
|
||||
// Ready once installed and every AMS has resolved both its extruder binding and its
|
||||
// switcher track position from the device. The send dialog and sidebar sync UX only
|
||||
// treat the switch as usable when this is true.
|
||||
bool IsReady() const;
|
||||
|
||||
std::optional<bool> IsInA_HasFilament() const { return m_in_a_has_filament; };
|
||||
std::optional<bool> IsInB_HasFilament() const { return m_in_b_has_filament; };
|
||||
|
||||
std::optional<DevAmsTray> GetInA_Slot() const;
|
||||
std::optional<DevAmsTray> GetInB_Slot() const;
|
||||
std::optional<DevAmsSlotId> GetInA_SlotId() const { return m_in_a_slot; };
|
||||
std::optional<DevAmsSlotId> GetInB_SlotId() const { return m_in_b_slot; };
|
||||
|
||||
std::optional<DevExtder> GetOutA_Extruder() const;
|
||||
std::optional<DevExtder> GetOutB_Extruder() const;
|
||||
std::optional<int> GetOutA_ExtruderId() const { return m_out_a_extruder_id; };
|
||||
std::optional<int> GetOutB_ExtruderId() const { return m_out_b_extruder_id; };
|
||||
|
||||
CaliStatus GetCaliStatus() const { return m_cali_status; };
|
||||
|
||||
void Reset();
|
||||
void ParseFilaSwitchInfo(const nlohmann::json& print_jj);
|
||||
|
||||
private:
|
||||
MachineObject* m_owner;
|
||||
|
||||
bool m_is_installed = false;
|
||||
|
||||
std::optional<bool> m_in_a_has_filament;
|
||||
std::optional<bool> m_in_b_has_filament;
|
||||
|
||||
std::optional<DevAmsSlotId> m_in_a_slot;
|
||||
std::optional<DevAmsSlotId> m_in_b_slot;
|
||||
|
||||
std::optional<int> m_out_a_extruder_id;
|
||||
std::optional<int> m_out_b_extruder_id;
|
||||
|
||||
CaliStatus m_cali_status = CaliStatus::CALI_IDLE;
|
||||
};
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "DevFilaSystem.h"
|
||||
#include "DevNozzleSystem.h" // DevNozzle / DevNozzleSystem for GetNozzleFlowStringByAmsId
|
||||
|
||||
// TODO: remove this include
|
||||
#include "slic3r/GUI/DeviceManager.hpp"
|
||||
@@ -117,7 +118,7 @@ DevAms::DevAms(const std::string& ams_id, int nozzle_id, int type)
|
||||
m_ams_id = ams_id;
|
||||
m_ext_id = nozzle_id;
|
||||
m_ams_type = (AmsType)type;
|
||||
assert(EXT_SPOOL < type && m_ams_type <= N3S);
|
||||
assert(EXT_SPOOL < type && m_ams_type <= AMS_LITE_MIXED);
|
||||
}
|
||||
|
||||
DevAms::~DevAms()
|
||||
@@ -143,7 +144,8 @@ static unordered_map<int, wxString> s_ams_display_formats = {
|
||||
wxString DevAms::GetDisplayName() const
|
||||
{
|
||||
wxString ams_display_format;
|
||||
auto iter = s_ams_display_formats.find(m_ams_type);
|
||||
// GetAmsType() maps AMS_LITE_MIXED -> AMS_LITE so N9 shows the AMS-Lite name.
|
||||
auto iter = s_ams_display_formats.find(GetAmsType());
|
||||
if (iter != s_ams_display_formats.end())
|
||||
{
|
||||
ams_display_format = iter->second;
|
||||
@@ -172,11 +174,13 @@ wxString DevAms::GetDisplayName() const
|
||||
|
||||
int DevAms::GetSlotCount() const
|
||||
{
|
||||
if (m_ams_type == AMS || m_ams_type == AMS_LITE || m_ams_type == N3F)
|
||||
// GetAmsType() maps AMS_LITE_MIXED -> AMS_LITE, so N9 reports 4 slots like AMS-Lite.
|
||||
auto ams_type = GetAmsType();
|
||||
if (ams_type == AMS || ams_type == AMS_LITE || ams_type == N3F)
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
else if (m_ams_type == N3S)
|
||||
else if (ams_type == N3S)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
@@ -285,6 +289,44 @@ int DevFilaSystem::GetExtruderIdByAmsId(const std::string& ams_id) const
|
||||
return 0; // not found
|
||||
}
|
||||
|
||||
std::string DevFilaSystem::GetNozzleFlowStringByAmsId(const std::string& ams_id) const
|
||||
{
|
||||
auto extruder_id = GetExtruderIdByAmsId(ams_id);
|
||||
auto nozzle = GetOwner()->GetNozzleSystem()->GetExtNozzle(extruder_id);
|
||||
return DevNozzle::GetNozzleFlowTypeString(nozzle.GetNozzleFlowType());
|
||||
}
|
||||
|
||||
std::map<int, DevAmsSlotId> DevFilaSystem::GetTrayIndexMap()
|
||||
{
|
||||
std::map<int, DevAmsSlotId> tray_id_map;
|
||||
tray_id_map[VIRTUAL_TRAY_MAIN_ID] = DevAmsSlotId{VIRTUAL_TRAY_MAIN_ID, 0};
|
||||
tray_id_map[VIRTUAL_TRAY_DEPUTY_ID] = DevAmsSlotId{VIRTUAL_TRAY_DEPUTY_ID, 0};
|
||||
|
||||
for (auto& [ams_id, ams_item] : GetAmsList()) {
|
||||
for (auto &[slot_id, slot_item] : ams_item->GetTrays()) {
|
||||
if (ams_item && slot_item) {
|
||||
try {
|
||||
int ams_id_int = stoi(ams_id);
|
||||
int slot_id_int = stoi(slot_id);
|
||||
int tray_index = -1;
|
||||
if (ams_item->GetAmsType() == DevAms::N3S) {
|
||||
tray_index = ams_id_int;
|
||||
} else if(ams_item->GetAmsType() == DevAms::AMS_LITE && ams_item->IsAmsLiteMixed()) {
|
||||
tray_index = 24 + slot_id_int;
|
||||
} else {
|
||||
tray_index = (ams_id_int * 4 + slot_id_int);
|
||||
}
|
||||
tray_id_map[tray_index] = {ams_id_int, slot_id_int};
|
||||
} catch(...) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << "invalid ams id: " << ams_id << " or slot id: " << slot_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tray_id_map;
|
||||
}
|
||||
|
||||
bool DevFilaSystem::IsAmsSettingUp() const
|
||||
{
|
||||
int setting_up_stat = DevUtil::get_flag_bits(m_ams_cali_stat, 0, 8);
|
||||
@@ -331,6 +373,15 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
|
||||
|
||||
if (!key_field_only)
|
||||
{
|
||||
// Newer firmware sends the exact filament-change step sequence in ams.cfs, so the
|
||||
// client no longer hardcodes the steps per model. Absent on current firmware, which
|
||||
// keeps the list empty and the legacy hardcoded/ams_status_sub step path in effect.
|
||||
if (jj["ams"].contains("cfs")) {
|
||||
system->m_filament_change_steps = DevJsonValParser::GetVal<std::vector<DevFilamentStep>>(jj["ams"], "cfs");
|
||||
} else {
|
||||
system->m_filament_change_steps.clear();
|
||||
}
|
||||
|
||||
if (jj["ams"].contains("tray_read_done_bits"))
|
||||
{
|
||||
obj->tray_read_done_bits = stol(jj["ams"]["tray_read_done_bits"].get<std::string>(), nullptr, 16);
|
||||
@@ -388,17 +439,43 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
|
||||
int type_id = 1; // 0:dummy 1:ams 2:ams-lite 3:n3f 4:n3s
|
||||
|
||||
/*ams info*/
|
||||
std::set<int> binded_extruder_set;
|
||||
std::optional<DevFilaSwitch::SwitchPos> binded_switcher_pos;
|
||||
if (it->contains("info")) {
|
||||
const std::string& info = (*it)["info"].get<std::string>();
|
||||
type_id = DevUtil::get_flag_bits(info, 0, 4);
|
||||
extuder_id = DevUtil::get_flag_bits(info, 8, 4);
|
||||
if (extuder_id == 0xE && obj->GetFilaSwitch()->IsInstalled()) {
|
||||
int bind_switch_in = DevUtil::get_flag_bits(info, 24, 4);
|
||||
if (bind_switch_in == 0 || bind_switch_in == 1) {
|
||||
binded_extruder_set = { MAIN_EXTRUDER_ID, DEPUTY_EXTRUDER_ID };
|
||||
}
|
||||
|
||||
if (bind_switch_in == 0) {
|
||||
binded_switcher_pos = DevFilaSwitch::SwitchPos::POS_IN_B;
|
||||
} else if (bind_switch_in == 1) {
|
||||
binded_switcher_pos = DevFilaSwitch::SwitchPos::POS_IN_A;
|
||||
}
|
||||
|
||||
// Orca: the switch feeds every AMS to both extruders; pin a deterministic
|
||||
// single-extruder id so legacy GetExtruderId() consumers keep a valid value
|
||||
// while switch-aware code reads the binding set instead.
|
||||
extuder_id = MAIN_EXTRUDER_ID;
|
||||
} else if (extuder_id != 0xE) {
|
||||
binded_extruder_set = { extuder_id };
|
||||
}
|
||||
} else {
|
||||
if (!obj->is_enable_ams_np && obj->get_printer_ams_type() == "f1") {
|
||||
type_id = DevAms::AMS_LITE;
|
||||
}
|
||||
binded_extruder_set = { MAIN_EXTRUDER_ID };
|
||||
}
|
||||
|
||||
/*AMS without initialization*/
|
||||
// Orca: an AMS reporting extruder 0xE without a Filament Track Switch installed has
|
||||
// no usable extruder binding; drop it, preserving the existing display for
|
||||
// half-initialized printers. With the switch installed the 0xE case is remapped
|
||||
// above to both extruders and falls through to normal handling.
|
||||
if (extuder_id == 0xE)
|
||||
{
|
||||
ams_id_set.erase(ams_id);
|
||||
@@ -430,6 +507,13 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
|
||||
/*set ams type flag*/
|
||||
curr_ams->SetAmsType(type_id);
|
||||
|
||||
// Refresh the switch-aware extruder binding on every push (both create and
|
||||
// update paths) so it can't go stale after an AMS is re-homed to another
|
||||
// extruder. Without the switch the set holds the single bound extruder and
|
||||
// the track position stays empty.
|
||||
curr_ams->m_binded_extruder_set = binded_extruder_set;
|
||||
curr_ams->m_binded_switcher_pos = binded_switcher_pos;
|
||||
|
||||
|
||||
/*set ams exist flag*/
|
||||
try
|
||||
@@ -442,6 +526,11 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
|
||||
{
|
||||
curr_ams->m_exist = (obj->ams_exist_bits & (1 << ams_id_int)) != 0 ? true : false;
|
||||
}
|
||||
else if (type_id == DevAms::AMS_LITE_MIXED)
|
||||
{
|
||||
// Mixed AMS-Lite (A2L / N9) exist flag lives at bit 12.
|
||||
curr_ams->m_exist = DevUtil::get_flag_bits(obj->ams_exist_bits, 12);
|
||||
}
|
||||
else
|
||||
{
|
||||
curr_ams->m_exist = DevUtil::get_flag_bits(obj->ams_exist_bits, 4 + (ams_id_int - 128));
|
||||
@@ -679,6 +768,11 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
|
||||
{
|
||||
curr_tray->is_exists = (obj->tray_exist_bits & (1 << (ams_id_int * 4 + tray_id_int))) != 0 ? true : false;
|
||||
}
|
||||
else if (type_id == DevAms::AMS_LITE_MIXED)
|
||||
{
|
||||
// Mixed AMS-Lite (A2L / N9) trays occupy tray-exist bits 24..27.
|
||||
curr_tray->is_exists = DevUtil::get_flag_bits(obj->tray_exist_bits, AMS_LITE_MIXED_TRAY_INDEX_OFFSET + tray_id_int);
|
||||
}
|
||||
else
|
||||
{
|
||||
curr_tray->is_exists = DevUtil::get_flag_bits(obj->tray_exist_bits, 16 + (ams_id_int - 128));
|
||||
|
||||
@@ -4,9 +4,12 @@
|
||||
|
||||
#include "DevDefs.h"
|
||||
#include "DevFilaAmsSetting.h"
|
||||
#include "DevFilaSwitch.h"
|
||||
#include "DevUtil.h"
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
@@ -142,6 +145,7 @@ public:
|
||||
static constexpr AmsType AMS_LITE = DevAmsType::AMS_LITE;
|
||||
static constexpr AmsType N3F = DevAmsType::N3F;
|
||||
static constexpr AmsType N3S = DevAmsType::N3S;
|
||||
static constexpr AmsType AMS_LITE_MIXED = DevAmsType::AMS_LITE_MIXED;
|
||||
|
||||
public:
|
||||
|
||||
@@ -209,11 +213,16 @@ public:
|
||||
|
||||
void SetAmsType(int type) { m_ams_type = (AmsType)type; }
|
||||
void SetAmsType(AmsType type) { m_ams_type = type; }
|
||||
AmsType GetAmsType() const { return m_ams_type; }
|
||||
// AMS-Lite-mixed (N9) is a variant of AMS-Lite: report AMS_LITE to generic callers so all
|
||||
// existing AMS_LITE handling applies. Mixed-specific code uses IsAmsLiteMixed().
|
||||
AmsType GetAmsType() const { return m_ams_type == AMS_LITE_MIXED ? AMS_LITE : m_ams_type; }
|
||||
|
||||
// exist or not
|
||||
bool IsExist() const { return m_exist; }
|
||||
|
||||
// AMS-Lite-mixed for N9 (A2L)
|
||||
bool IsAmsLiteMixed() const { return m_ams_type == AMS_LITE_MIXED; }
|
||||
|
||||
// slots
|
||||
int GetSlotCount() const;
|
||||
DevAmsTray* GetTray(const std::string& tray_id) const;
|
||||
@@ -222,6 +231,20 @@ public:
|
||||
// installed on the extruder
|
||||
int GetExtruderId() const { return m_ext_id; }
|
||||
|
||||
// Extruders this AMS can currently feed. Without a Filament Track Switch installed this
|
||||
// is the single {m_ext_id}; with the switch installed the device binds it to both extruders.
|
||||
const std::set<int>& GetBindedExtruderSet() const { return m_binded_extruder_set; }
|
||||
|
||||
// The bound extruder when exactly one is bound, empty otherwise. Used to attribute an AMS
|
||||
// to a single extruder on printers without the switch.
|
||||
std::optional<int> GetUniqueBindedExtruderId() const
|
||||
{
|
||||
return m_binded_extruder_set.size() == 1 ? std::optional<int>(*m_binded_extruder_set.begin()) : std::nullopt;
|
||||
}
|
||||
|
||||
// Which switch input track (A/B) feeds this AMS, set only when a Filament Track Switch is installed.
|
||||
std::optional<DevFilaSwitch::SwitchPos> GetSwitcherPos() const { return m_binded_switcher_pos; }
|
||||
|
||||
// temperature and humidity
|
||||
float GetCurrentTemperature() const { return m_current_temperature; }
|
||||
|
||||
@@ -247,6 +270,12 @@ private:
|
||||
AmsType m_ams_type = AmsType::AMS;
|
||||
std::string m_ams_id;
|
||||
int m_ext_id;//extruder id
|
||||
// Orca: keeps the legacy single m_ext_id for existing consumers and carries the
|
||||
// switch-aware binding alongside it (BambuStudio stores only the set). Without a Filament
|
||||
// Track Switch the set is {m_ext_id}; with one installed the device binds both extruders and
|
||||
// records which input track (A/B) feeds this AMS.
|
||||
std::set<int> m_binded_extruder_set;
|
||||
std::optional<DevFilaSwitch::SwitchPos> m_binded_switcher_pos;
|
||||
bool m_exist = false;
|
||||
|
||||
// slots and trays
|
||||
@@ -319,9 +348,16 @@ public:
|
||||
DevAmsTray* GetAmsTray(const std::string& ams_id, const std::string& tray_id) const;
|
||||
void CollectAmsColors(std::vector<wxColour>& ams_colors) const;
|
||||
|
||||
// Map a linear tray index -> {ams_id, slot_id}. Includes the two virtual (external-spool) trays,
|
||||
// N3S single-slot units, and the A2L/N9 AMS-Lite-mixed layout (trays 24-27).
|
||||
std::map<int, DevAmsSlotId> GetTrayIndexMap();
|
||||
|
||||
// extruder
|
||||
int GetExtruderIdByAmsId(const std::string& ams_id) const;
|
||||
|
||||
// nozzle: untranslated flow-type string of the extruder bound to this ams (for blacklist matching)
|
||||
std::string GetNozzleFlowStringByAmsId(const std::string& ams_id) const;
|
||||
|
||||
/* AMS settings*/
|
||||
DevAmsSystemSetting& GetAmsSystemSetting() { return m_ams_system_setting; }
|
||||
std::optional<bool> IsDetectOnInsertEnabled() const { return m_ams_system_setting.IsDetectOnInsertEnabled(); };
|
||||
@@ -331,6 +367,11 @@ public:
|
||||
|
||||
std::weak_ptr<DevAmsSystemFirmwareSwitch> GetAmsFirmwareSwitch() const { return m_ams_firmware_switch;}
|
||||
|
||||
// filament change steps
|
||||
// The exact change-step sequence reported by the AMS (ams.cfs). Empty on current firmware,
|
||||
// which leaves the legacy hardcoded/ams_status_sub step handling in effect.
|
||||
const std::vector<DevFilamentStep>& GetFilamentChangeSteps() const { return m_filament_change_steps; }
|
||||
|
||||
public:
|
||||
// ctrls
|
||||
int CtrlAmsReset() const;
|
||||
@@ -348,6 +389,9 @@ private:
|
||||
/* ams properties */
|
||||
int m_ams_cali_stat = 0;
|
||||
|
||||
// Change-step sequence reported by the AMS (ams.cfs); empty when firmware doesn't send it.
|
||||
std::vector<DevFilamentStep> m_filament_change_steps;
|
||||
|
||||
std::map<std::string, DevAms*, NumericStrCompare> amsList;// key: ams[id], start with 0
|
||||
|
||||
DevAmsSystemSetting m_ams_system_setting{ this };
|
||||
|
||||
@@ -44,6 +44,8 @@ public:
|
||||
bool isLaszer() const { return product_name.Contains("Laser"); }
|
||||
bool isCuttingModule() const { return product_name.Contains("Cutting Module"); }
|
||||
bool isExtinguishSystem() const { return product_name.Contains("Extinguishing System"); }
|
||||
bool isWTM() const { return name.find("wtm") != std::string::npos; } // nozzle (rack hotend) module firmware
|
||||
bool isFilaTrackSwitch() const { return product_name.Contains("Filament Track"); }
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -95,6 +95,10 @@ namespace Slic3r
|
||||
{
|
||||
result.id = ams_id + slot_id;
|
||||
}
|
||||
else if (type == DevAms::AMS_LITE_MIXED)
|
||||
{
|
||||
result.id = AMS_LITE_MIXED_TRAY_INDEX_OFFSET + slot_id;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.id = ams_id * 4 + slot_id;
|
||||
@@ -117,6 +121,11 @@ namespace Slic3r
|
||||
{
|
||||
std::string ams_id = ams->second->GetAmsId();
|
||||
auto ams_type = ams->second->GetAmsType();
|
||||
// GetAmsType() maps mixed -> AMS_LITE; recover the mixed type so N9 trays index at 24+slot.
|
||||
if (ams_type == DevAms::AMS_LITE && ams->second->IsAmsLiteMixed())
|
||||
{
|
||||
ams_type = DevAms::AMS_LITE_MIXED;
|
||||
}
|
||||
for (auto tray = ams->second->GetTrays().begin(); tray != ams->second->GetTrays().end(); tray++)
|
||||
{
|
||||
int ams_id = atoi(ams->first.c_str());
|
||||
@@ -126,6 +135,10 @@ namespace Slic3r
|
||||
{
|
||||
tray_index = ams_id * 4 + tray_id;
|
||||
}
|
||||
else if (ams_type == DevAms::AMS_LITE_MIXED)
|
||||
{
|
||||
tray_index = AMS_LITE_MIXED_TRAY_INDEX_OFFSET + tray_id;
|
||||
}
|
||||
else if (ams_type == DevAms::N3S)
|
||||
{
|
||||
tray_index = ams_id + tray_id;
|
||||
|
||||
@@ -0,0 +1,485 @@
|
||||
#include "DevMapping.h"
|
||||
#include "DevMappingNozzle.h"
|
||||
|
||||
#include "DevNozzleRack.h"
|
||||
#include "DevNozzleSystem.h"
|
||||
|
||||
#include "DevUtil.h"
|
||||
#include "DevUtilBackend.h"
|
||||
|
||||
#include "libslic3r/MultiNozzleUtils.hpp"
|
||||
#include "libslic3r/Print.hpp"
|
||||
|
||||
#include "slic3r/GUI/DeviceManager.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/BackgroundSlicingProcess.hpp"
|
||||
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
using namespace nlohmann;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
void MachineObject::clear_auto_nozzle_mapping()
|
||||
{
|
||||
if (m_nozzle_mapping_ptr) {
|
||||
m_nozzle_mapping_ptr->Clear();
|
||||
}
|
||||
}
|
||||
|
||||
static std::string s_get_diameter_str(float diameter)
|
||||
{
|
||||
return (boost::format("%.2f") % diameter).str();
|
||||
}
|
||||
|
||||
static std::string s_get_diameter_str(const std::string& diameter)
|
||||
{
|
||||
try {
|
||||
float dia = boost::lexical_cast<float>(diameter);
|
||||
return s_get_diameter_str(dia);
|
||||
} catch (...) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed to boost::lexical_cast: " << diameter;
|
||||
return diameter;
|
||||
}
|
||||
|
||||
try {
|
||||
float dia = std::stof(diameter);
|
||||
return s_get_diameter_str(dia);
|
||||
} catch (...) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " std::stof: " << diameter;
|
||||
return diameter;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// get auto nozzle mapping through AP
|
||||
// warnings:
|
||||
// (1) the fila_id here is 1 based index
|
||||
// (2) the AP wants the diameter string with 2 decimal places
|
||||
int DevNozzleMappingCtrl::CtrlGetAutoNozzleMappingV0(Slic3r::GUI::Plater* plater,
|
||||
const std::vector<FilamentInfo>& ams_mapping,
|
||||
int flow_cali_opt, int pa_value)
|
||||
{
|
||||
Clear();
|
||||
|
||||
m_plater = plater;
|
||||
if (!plater) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": plater is nullptr";
|
||||
return -1;
|
||||
}
|
||||
|
||||
GCodeProcessorResult* gcode_result = plater->background_process().get_current_gcode_result();
|
||||
if (!gcode_result) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": gcode_result is nullptr";
|
||||
return -1;
|
||||
}
|
||||
|
||||
const auto& result = gcode_result->nozzle_group_result;
|
||||
if (!result) {
|
||||
assert(false && "gcode_result->nozzle_group_result");
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": gcode_result->nozzle_group_result is NULL";
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ams_mapping.empty()) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": the ams mapping is empty";
|
||||
return -1;// the ams mapping is empty
|
||||
}
|
||||
|
||||
json command_jj;
|
||||
command_jj["print"]["command"] = "get_auto_nozzle_mapping";
|
||||
m_sequence_id = std::to_string(MachineObject::m_sequence_id++);
|
||||
command_jj["print"]["sequence_id"] = m_sequence_id;
|
||||
command_jj["print"]["calibration"] = flow_cali_opt;
|
||||
command_jj["print"]["extrude_cali_manual_mode"] = pa_value;
|
||||
|
||||
// filament seq
|
||||
json filament_seq_jj;
|
||||
int max_fila_id = 0;
|
||||
std::unordered_set<int> filaid_set;
|
||||
for (int idx = 0; idx < gcode_result->filament_change_sequence.size(); idx++) {
|
||||
int fila_id = gcode_result->filament_change_sequence[idx];
|
||||
if (filaid_set.count(fila_id) == 0) {
|
||||
filaid_set.insert(fila_id);
|
||||
filament_seq_jj[fila_id + 1] = idx;
|
||||
max_fila_id = std::max(max_fila_id, fila_id + 1);
|
||||
}
|
||||
}
|
||||
|
||||
for (int fila_id = 0; fila_id <= max_fila_id; fila_id++) {
|
||||
if (filament_seq_jj[fila_id].is_null()) {
|
||||
filament_seq_jj[fila_id] = -1;// fill the used fila_id with -1
|
||||
}
|
||||
}
|
||||
|
||||
command_jj["print"]["filament_seq"] = filament_seq_jj;
|
||||
|
||||
// ams mapping
|
||||
std::vector<int> ams_mapping_vec(33, 0xFFFF);/* AP ask to fill them*/
|
||||
for (auto item : ams_mapping) {
|
||||
try {
|
||||
int ams_id = stoi(item.ams_id);
|
||||
int slot_id = !item.slot_id.empty() ? stoi(item.slot_id) : 0;
|
||||
ams_mapping_vec[item.id + 1] = (ams_id << 8) | slot_id;/*using ams_id << 8 | slot_id*/
|
||||
} catch (std::exception& e) {
|
||||
assert(false && "invalid ams_id or slot_id");
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " exception: " << e.what();
|
||||
ams_mapping_vec[item.id + 1] = item.tray_id;
|
||||
}
|
||||
}
|
||||
command_jj["print"]["ams_mapping"] = ams_mapping_vec;
|
||||
|
||||
// filament info
|
||||
json filament_info_jj;
|
||||
for (int fila_id = 0; fila_id < ams_mapping.size(); fila_id++) {
|
||||
const auto& fila = ams_mapping[fila_id];
|
||||
const auto& nozzle_list = result->get_nozzles_for_filament(fila.id);
|
||||
if (nozzle_list.empty()) {
|
||||
assert(false && "nozzle_list should not be empty");
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "nozzle_list should not be nullptr";
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto& nozzle_info : nozzle_list) {
|
||||
json fila_item_jj;
|
||||
fila_item_jj["id"] = (fila.id + 1);
|
||||
fila_item_jj["direction"] = (nozzle_info.extruder_id == LOGIC_L_EXTRUDER_ID ? 1 : 2);
|
||||
fila_item_jj["group"] = nozzle_info.group_id;
|
||||
fila_item_jj["nozzle_d"] = s_get_diameter_str(nozzle_info.diameter);
|
||||
fila_item_jj["nozzle_v"] = (nozzle_info.volume_type == NozzleVolumeType::nvtHighFlow) ? "High Flow" : "Standard";
|
||||
fila_item_jj["cate"] = fila.filament_id;
|
||||
fila_item_jj["color"] = fila.color;
|
||||
filament_info_jj.push_back(fila_item_jj);
|
||||
}
|
||||
}
|
||||
|
||||
command_jj["print"]["fila_info"] = filament_info_jj;
|
||||
|
||||
// nozzle info
|
||||
json nozzle_info_jj;
|
||||
const auto& nozzle_system = m_obj->GetNozzleSystem();
|
||||
const auto& extruder_nozzles = nozzle_system->GetExtNozzles();
|
||||
for (const auto& nozzle : extruder_nozzles) {
|
||||
if (nozzle.second.IsNormal()) {
|
||||
json nozzle_item_jj;
|
||||
nozzle_item_jj["pos"] = nozzle.second.GetNozzleId();
|
||||
if (nozzle_system->GetReplaceNozzleTar().has_value() && nozzle_item_jj["pos"] == MAIN_EXTRUDER_ID) {
|
||||
nozzle_item_jj["pos"] = nozzle_system->GetReplaceNozzleTar().value();// special case of tar_id. see protocol definition
|
||||
}
|
||||
|
||||
nozzle_item_jj["nozzle_d"] = s_get_diameter_str(nozzle.second.GetNozzleDiameter());
|
||||
nozzle_item_jj["nozzle_v"] = DevNozzle::ToNozzleFlowString(nozzle.second.GetNozzleFlowType());
|
||||
nozzle_item_jj["wear"] = nozzle.second.GetNozzleWear();
|
||||
nozzle_item_jj["cate"] = nozzle.second.GetFilamentId();
|
||||
nozzle_item_jj["color"] = nozzle.second.GetFilamentColor();
|
||||
nozzle_info_jj.push_back(nozzle_item_jj);
|
||||
}
|
||||
}
|
||||
|
||||
const auto& rack_nozzles = nozzle_system->GetNozzleRack()->GetRackNozzles();
|
||||
for (const auto& nozzle : rack_nozzles) {
|
||||
if (nozzle.second.IsNormal()) {
|
||||
json nozzle_item_jj;
|
||||
nozzle_item_jj["pos"] = (nozzle.second.GetNozzleId() + 0x10);
|
||||
if (nozzle_system->GetReplaceNozzleTar().has_value() && nozzle_item_jj["pos"] == MAIN_EXTRUDER_ID) {
|
||||
nozzle_item_jj["pos"] = nozzle_system->GetReplaceNozzleTar().value();// special case of tar_id. see protocol definition
|
||||
}
|
||||
|
||||
nozzle_item_jj["nozzle_d"] = s_get_diameter_str(nozzle.second.GetNozzleDiameter());
|
||||
nozzle_item_jj["nozzle_v"] = DevNozzle::ToNozzleFlowString(nozzle.second.GetNozzleFlowType());
|
||||
nozzle_item_jj["wear"] = nozzle.second.GetNozzleWear();
|
||||
nozzle_item_jj["cate"] = nozzle.second.GetFilamentId();
|
||||
nozzle_item_jj["color"] = nozzle.second.GetFilamentColor();
|
||||
nozzle_info_jj.push_back(nozzle_item_jj);
|
||||
}
|
||||
}
|
||||
|
||||
command_jj["print"]["nozzle_info"] = nozzle_info_jj;
|
||||
m_req_version = NozzleMappingVersion::Version_V0;
|
||||
return m_obj->publish_json(command_jj);
|
||||
}
|
||||
|
||||
int DevNozzleMappingCtrl::CtrlGetAutoNozzleMappingV1(Slic3r::GUI::Plater* plater)
|
||||
{
|
||||
Clear();
|
||||
m_plater = plater;
|
||||
if (!m_plater) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto nozzle_group_res = DevUtilBackend::GetNozzleGroupResult(m_plater);
|
||||
if (!nozzle_group_res) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": gcode_result->nozzle_group_result is NULL";
|
||||
return -1;
|
||||
}
|
||||
|
||||
json command_jj;
|
||||
command_jj["print"]["command"] = "get_auto_nozzle_mapping";
|
||||
m_sequence_id = std::to_string(MachineObject::m_sequence_id++);
|
||||
command_jj["print"]["sequence_id"] = m_sequence_id;
|
||||
|
||||
json nozzle_group_info_jj;
|
||||
std::unordered_set<int> used_logic_groups;
|
||||
const auto& used_logic_nozzles = nozzle_group_res->get_used_nozzles_in_extruder();
|
||||
for (const auto& used_logic_nozzle : used_logic_nozzles) {
|
||||
if (used_logic_groups.count(used_logic_nozzle.group_id) != 0) {
|
||||
continue;
|
||||
}
|
||||
used_logic_groups.insert(used_logic_nozzle.group_id);
|
||||
|
||||
json nozzle_info;
|
||||
nozzle_info["id"] = used_logic_nozzle.group_id;
|
||||
nozzle_info["ext"] = (used_logic_nozzle.extruder_id + 1);
|
||||
|
||||
try {
|
||||
nozzle_info["dia"] = std::stof(used_logic_nozzle.diameter);
|
||||
} catch(const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": e=" << e.what();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (used_logic_nozzle.volume_type == NozzleVolumeType::nvtHighFlow) {
|
||||
nozzle_info["vol"] = "High Flow";
|
||||
} else if(used_logic_nozzle.volume_type == NozzleVolumeType::nvtStandard){
|
||||
nozzle_info["vol"] = "Standard";
|
||||
} else if(used_logic_nozzle.volume_type == NozzleVolumeType::nvtTPUHighFlow){
|
||||
nozzle_info["vol"] = "TPU Flow";
|
||||
} else {
|
||||
assert(0);
|
||||
continue;
|
||||
}
|
||||
|
||||
nozzle_group_info_jj.push_back(nozzle_info);
|
||||
}
|
||||
command_jj["print"]["group_info"] = nozzle_group_info_jj;
|
||||
|
||||
|
||||
m_req_version = NozzleMappingVersion::Version_V1;
|
||||
command_jj["print"]["version"] = (int)m_req_version;
|
||||
return m_obj->publish_json(command_jj);
|
||||
}
|
||||
|
||||
|
||||
void DevNozzleMappingCtrl::ParseAutoNozzleMapping(const json& print_jj)
|
||||
{
|
||||
if (print_jj.contains("command") && print_jj["command"].get<std::string>() == "get_auto_nozzle_mapping") {
|
||||
if (print_jj.contains("sequence_id") && print_jj["sequence_id"] == m_sequence_id) {
|
||||
Clear();
|
||||
DevJsonValParser::ParseVal(print_jj, "result", m_result);
|
||||
DevJsonValParser::ParseVal(print_jj, "reason", m_mqtt_reason);
|
||||
DevJsonValParser::ParseVal(print_jj, "errno", m_errno);
|
||||
DevJsonValParser::ParseVal(print_jj, "detail", m_detail_json);
|
||||
DevJsonValParser::ParseVal(print_jj, "type", m_type);
|
||||
DevJsonValParser::ParseVal(print_jj, "version", m_ack_version);
|
||||
|
||||
if (print_jj.contains("mapping")) {
|
||||
m_nozzle_mapping_json = print_jj["mapping"];
|
||||
const auto& mapping = print_jj["mapping"].get<std::vector<int>>();
|
||||
for (int fila_id = 0; fila_id < mapping.size(); ++fila_id) {
|
||||
m_nozzle_mapping[fila_id] = mapping[fila_id];
|
||||
}
|
||||
}
|
||||
|
||||
m_flush_weight_base = GetFlushWeight(m_obj);
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": get_auto_nozzle_mapping: " << m_result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DevNozzleMappingCtrl::Clear()
|
||||
{
|
||||
m_sequence_id.clear();
|
||||
m_result.clear();
|
||||
m_mqtt_reason.clear();
|
||||
m_type.clear();
|
||||
m_errno = 0;
|
||||
m_detail_msg.clear();
|
||||
m_detail_json.clear();
|
||||
m_nozzle_mapping.clear();
|
||||
m_nozzle_mapping_json.clear();
|
||||
|
||||
m_flush_weight_base = -1;
|
||||
m_flush_weight_current = -1;
|
||||
m_ack_version = NozzleMappingVersion::Version_V0;
|
||||
}
|
||||
|
||||
|
||||
void DevNozzleMappingCtrl::SetManualNozzleMappingByFila(int fila_id, int nozzle_pos_id)
|
||||
{
|
||||
if (nozzle_pos_id == MAIN_EXTRUDER_ID && m_obj->GetNozzleSystem()->GetReplaceNozzleTar().has_value()){
|
||||
nozzle_pos_id = m_obj->GetNozzleSystem()->GetReplaceNozzleTar().value();// special case of tar_id. see protocol definition
|
||||
}
|
||||
|
||||
if (m_nozzle_mapping[fila_id] != nozzle_pos_id) {
|
||||
m_nozzle_mapping[fila_id] = nozzle_pos_id;
|
||||
m_flush_weight_current = GetFlushWeight(m_obj);
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": fila_id=" << fila_id << ", nozzle_pos_id=" << nozzle_pos_id;
|
||||
}
|
||||
|
||||
try {
|
||||
auto mapping = m_nozzle_mapping_json.get<std::vector<int>>();
|
||||
if (mapping.at(fila_id) != nozzle_pos_id) {
|
||||
mapping[fila_id] = nozzle_pos_id;
|
||||
m_nozzle_mapping_json = mapping;
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": updated to " << m_nozzle_mapping_json.dump();
|
||||
}
|
||||
} catch (std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": exception: " << e.what();
|
||||
};
|
||||
}
|
||||
|
||||
static std::unordered_set<int> sGetLogicExtruders(const std::vector<MultiNozzleUtils::NozzleInfo>& nozzle_infos)
|
||||
{
|
||||
std::unordered_set<int> extruder_ids;
|
||||
for (const auto& nozzle : nozzle_infos) {
|
||||
if (nozzle.extruder_id >= 0) {
|
||||
extruder_ids.insert(nozzle.extruder_id);
|
||||
}
|
||||
}
|
||||
|
||||
return extruder_ids;
|
||||
};
|
||||
|
||||
|
||||
std::vector<int> DevNozzleMappingCtrl::GetMappedNozzlePosVecByFilaId(int fila_id) const
|
||||
{
|
||||
std::vector<int> physic_nozzle_pos_vec;
|
||||
auto nozzle_group_res = DevUtilBackend::GetNozzleGroupResult(m_plater);
|
||||
if (!nozzle_group_res) {
|
||||
return physic_nozzle_pos_vec;
|
||||
}
|
||||
|
||||
const auto& used_logic_nozzles = nozzle_group_res->get_nozzles_for_filament(fila_id);
|
||||
if (m_ack_version == NozzleMappingVersion::Version_V0) {
|
||||
if (auto iter = m_nozzle_mapping.find(fila_id); iter != m_nozzle_mapping.end()) {
|
||||
if (m_obj->GetNozzleSystem()->GetReplaceNozzleTar().has_value() &&
|
||||
iter->second == m_obj->GetNozzleSystem()->GetReplaceNozzleTar().value()) {
|
||||
physic_nozzle_pos_vec.push_back(MAIN_EXTRUDER_ID);
|
||||
} else {
|
||||
physic_nozzle_pos_vec.push_back(iter->second);
|
||||
}
|
||||
} else {
|
||||
const auto& used_logic_extruders = sGetLogicExtruders(used_logic_nozzles);
|
||||
if (used_logic_extruders.size() == 1 && *used_logic_extruders.begin() == LOGIC_L_EXTRUDER_ID) {
|
||||
physic_nozzle_pos_vec.push_back(1);
|
||||
return physic_nozzle_pos_vec; // only left extruder is used
|
||||
}
|
||||
}
|
||||
} else if (m_ack_version == NozzleMappingVersion::Version_V1) {
|
||||
for (const auto& used_logic_nozzle : used_logic_nozzles) {
|
||||
if (auto iter = m_nozzle_mapping.find(used_logic_nozzle.group_id); iter != m_nozzle_mapping.end()) {
|
||||
if (m_obj->GetNozzleSystem()->GetReplaceNozzleTar().has_value() &&
|
||||
iter->second == m_obj->GetNozzleSystem()->GetReplaceNozzleTar().value()) {
|
||||
physic_nozzle_pos_vec.push_back(MAIN_EXTRUDER_ID);
|
||||
} else {
|
||||
physic_nozzle_pos_vec.push_back(iter->second);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return physic_nozzle_pos_vec;
|
||||
}
|
||||
|
||||
wxString DevNozzleMappingCtrl::GetMappedNozzlePosStrByFilaId(int fila_id, const wxString& default_str) const
|
||||
{
|
||||
wxString display_str;
|
||||
const auto& physic_nozzle_pos_vec = GetMappedNozzlePosVecByFilaId(fila_id);
|
||||
for (int idx = 0; idx < physic_nozzle_pos_vec.size(); idx++) {
|
||||
int pos_id = physic_nozzle_pos_vec[idx];
|
||||
if (pos_id == MAIN_EXTRUDER_ID) {
|
||||
display_str += "R";
|
||||
} else if (pos_id == DEPUTY_EXTRUDER_ID) {
|
||||
display_str += "L";
|
||||
} else if (pos_id >= 0x10) {
|
||||
display_str += wxString::Format("R%d", pos_id - 0x10 + 1);// display 1~n for rack hotends
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (idx < physic_nozzle_pos_vec.size() - 1) {
|
||||
display_str += " ";
|
||||
}
|
||||
}
|
||||
|
||||
return !display_str.empty() ? display_str : default_str;
|
||||
}
|
||||
|
||||
// flush weight for multi-nozzle printers: total flush volume across all filament changes
|
||||
float DevNozzleMappingCtrl::GetFlushWeight(Slic3r::MachineObject* obj) const
|
||||
{
|
||||
auto plater = Slic3r::GUI::wxGetApp().plater();
|
||||
if (!plater) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": plater is nullptr";
|
||||
return -1;
|
||||
}
|
||||
|
||||
GCodeProcessorResult* gcode_result = plater->background_process().get_current_gcode_result();
|
||||
if (!gcode_result) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": gcode_result is nullptr";
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (gcode_result->filament_change_sequence.empty()) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": filament_change_sequence is empty";
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!Slic3r::GUI::wxGetApp().preset_bundle) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": preset_bundle is nullptr";
|
||||
return -1;
|
||||
};
|
||||
|
||||
const std::vector<std::vector<std::vector<float>>>& flush_matrix = Slic3r::GUI::wxGetApp().preset_bundle->get_full_flush_matrix();
|
||||
|
||||
float total_flush_volume = 0;
|
||||
MultiNozzleUtils::NozzleStatusRecorder recorder;
|
||||
for (auto filament : gcode_result->filament_change_sequence) {
|
||||
const auto& nozzle_pos_vec = GetMappedNozzlePosVecByFilaId(filament);
|
||||
if (nozzle_pos_vec.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto nozzle_pos = nozzle_pos_vec.at(0);
|
||||
if (nozzle_pos == -1){
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": nozzle_pos is -1 for fila_id=" << filament;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto nozzle_info = obj->GetNozzleSystem()->GetNozzleByPosId(nozzle_pos);
|
||||
if (nozzle_info.IsEmpty()) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": nozzle_info IsEmpty for fila_id=" << filament << ", nozzle_pos=" << nozzle_pos;
|
||||
continue;
|
||||
}
|
||||
|
||||
int extruder_id = nozzle_info.GetLogicExtruderId();
|
||||
int nozzle_id = nozzle_info.GetNozzlePosId();
|
||||
int last_filament = recorder.get_filament_in_nozzle(nozzle_id);
|
||||
|
||||
if (last_filament != -1 && last_filament != filament) {
|
||||
if (flush_matrix.size() > extruder_id &&
|
||||
flush_matrix[extruder_id].size() > last_filament &&
|
||||
flush_matrix[extruder_id][last_filament].size() > filament){
|
||||
total_flush_volume += flush_matrix[extruder_id][last_filament][filament];
|
||||
}
|
||||
else{
|
||||
assert(false && "missing flush volume");
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": missing flush volume for extruder_id=" << extruder_id
|
||||
<< ", last_filament=" << last_filament << ", filament=" << filament;
|
||||
}
|
||||
}
|
||||
|
||||
recorder.set_nozzle_status(nozzle_id, filament);
|
||||
}
|
||||
|
||||
// estimate the flush weight: total_flush_volume * 1.26 * 0.001
|
||||
return total_flush_volume * 1.26 * 0.001;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* @file DevMappingNozzle.h
|
||||
* @brief Support nozzle mapping for hotend rack
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <wx/string.h>
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
|
||||
namespace GUI
|
||||
{
|
||||
class Plater;
|
||||
}
|
||||
|
||||
struct FilamentInfo; // libslic3r/ProjectTask.hpp
|
||||
|
||||
class MachineObject;
|
||||
class DevNozzleMappingCtrl
|
||||
{
|
||||
friend class MachineObject;
|
||||
public:
|
||||
enum class NozzleMappingVersion : int {
|
||||
Version_V0 = 0, // V0 means support logical fila_id -> physical nozzle_id mapping
|
||||
Version_V1 = 1, // V1 means support logical nozzle_id -> physical nozzle_id mapping
|
||||
};
|
||||
|
||||
enum class ErrNoV1 : int {
|
||||
Success = 0,
|
||||
ErrorBadRequest3mf = 1,
|
||||
ErrorBadRequestVersion = 2,
|
||||
ErrorBadRequestGroupId = 3,
|
||||
ErrorNoValidNozzles = 4,
|
||||
ErrorBadRequestReadFail = 5,
|
||||
ErrorRackCaliFail = 6,
|
||||
ErrorRackMoving = 7,
|
||||
ErrorRackFull = 8,
|
||||
WarningNotEnoughNozzles = 9
|
||||
};
|
||||
|
||||
public:
|
||||
DevNozzleMappingCtrl(MachineObject* obj) : m_obj(obj) {};
|
||||
|
||||
public:
|
||||
void SetPlater(Slic3r::GUI::Plater* plater) { m_plater = plater; }
|
||||
void Clear();
|
||||
|
||||
bool HasResult() const { return !m_result.empty(); }
|
||||
std::string GetResultStr() const { return m_result; }
|
||||
|
||||
// mqtt error info
|
||||
std::string GetMqttReason() const { return m_mqtt_reason; }
|
||||
|
||||
// command error info
|
||||
int GetErrno() const { return m_errno; }
|
||||
ErrNoV1 GetErrnoV1() const { return static_cast<ErrNoV1>(m_errno); }
|
||||
std::string GetDetailMsg() const { return m_detail_msg; }
|
||||
|
||||
// nozzle mapping
|
||||
std::unordered_map<int, int> GetNozzleMappingMap() const { return m_nozzle_mapping; }
|
||||
nlohmann::json GetNozzleMappingJson() const { return m_nozzle_mapping_json; }
|
||||
void SetManualNozzleMappingByFila(int fila_id, int nozzle_pos_id);
|
||||
|
||||
std::vector<int> GetMappedNozzlePosVecByFilaId(int fila_id) const;// return empy if not mapped
|
||||
wxString GetMappedNozzlePosStrByFilaId(int fila_id, const wxString& default_str = "?") const;
|
||||
|
||||
// flush weight
|
||||
float GetFlushWeightBase() const { return m_flush_weight_base; }
|
||||
float GetFlushWeightCurrent() const { return m_flush_weight_current; }
|
||||
|
||||
public:
|
||||
void ParseAutoNozzleMapping(const nlohmann::json& print_jj);
|
||||
int CtrlGetAutoNozzleMappingV0(Slic3r::GUI::Plater* plater, const std::vector<FilamentInfo>& ams_mapping, int flow_cali_opt, int pa_value);
|
||||
int CtrlGetAutoNozzleMappingV1(Slic3r::GUI::Plater* plater);
|
||||
|
||||
private:
|
||||
float GetFlushWeight(Slic3r::MachineObject* obj) const;
|
||||
|
||||
private:
|
||||
MachineObject* m_obj;
|
||||
Slic3r::GUI::Plater* m_plater = nullptr;
|
||||
|
||||
std::string m_sequence_id;
|
||||
|
||||
std::string m_result;
|
||||
std::string m_mqtt_reason;
|
||||
std::string m_type; // auto or manual
|
||||
|
||||
int m_errno;
|
||||
std::string m_detail_msg;
|
||||
nlohmann::json m_detail_json;
|
||||
|
||||
nlohmann::json m_nozzle_mapping_json;
|
||||
std::unordered_map<int, int> m_nozzle_mapping; // v0: fila_id -> physical_nozzle_id, v1: logical_nozzle_id -> physical_nozzle_id
|
||||
|
||||
float m_flush_weight_base = -1;// the base weight for flush
|
||||
float m_flush_weight_current = -1;// the weight current
|
||||
|
||||
NozzleMappingVersion m_req_version = NozzleMappingVersion::Version_V0;
|
||||
NozzleMappingVersion m_ack_version = NozzleMappingVersion::Version_V0;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
#include "DevNozzleRack.h"
|
||||
#include "DevUtil.h"
|
||||
|
||||
#include "slic3r/GUI/DeviceManager.hpp"
|
||||
|
||||
wxDEFINE_EVENT(DEV_RACK_EVENT_READING_FINISHED, wxCommandEvent);
|
||||
namespace Slic3r
|
||||
{
|
||||
|
||||
DevNozzleRack::DevNozzleRack(DevNozzleSystem* nozzle_system)
|
||||
: wxEvtHandler(), m_nozzle_system(nozzle_system)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
void DevNozzleRack::Reset()
|
||||
{
|
||||
m_position = RACK_POS_UNKNOWN;
|
||||
m_status = RACK_STATUS_UNKNOWN;
|
||||
m_rack_nozzles.clear();
|
||||
}
|
||||
|
||||
void DevNozzleRack::SendReadingFinished()
|
||||
{
|
||||
wxCommandEvent evt(DEV_RACK_EVENT_READING_FINISHED);
|
||||
evt.SetEventObject(this);
|
||||
wxPostEvent(this, evt);
|
||||
}
|
||||
|
||||
DevNozzle DevNozzleRack::GetNozzle(int idx) const
|
||||
{
|
||||
auto iter = m_rack_nozzles.find(idx);
|
||||
if (iter == m_rack_nozzles.end()) {
|
||||
DevNozzle nozzle;
|
||||
nozzle.SetOnRack(true);
|
||||
return nozzle;
|
||||
}
|
||||
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
DevFirmwareVersionInfo DevNozzleRack::GetNozzleFirmwareInfo(int nozzle_id) const
|
||||
{
|
||||
auto iter = m_rack_nozzles_firmware.find(nozzle_id);
|
||||
return iter != m_rack_nozzles_firmware.end() ? iter->second : DevFirmwareVersionInfo();
|
||||
}
|
||||
|
||||
|
||||
bool DevNozzleRack::HasUnreliableNozzles() const
|
||||
{
|
||||
for (const auto& nozzle : m_rack_nozzles)
|
||||
{
|
||||
if (!nozzle.second.IsInfoReliable())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DevNozzleRack::HasUnknownNozzles() const
|
||||
{
|
||||
for (const auto& nozzle : m_rack_nozzles)
|
||||
{
|
||||
if (nozzle.second.IsUnknown())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int DevNozzleRack::GetKnownNozzleCount() const
|
||||
{
|
||||
int count = 0;
|
||||
for (const auto& nozzle : m_rack_nozzles)
|
||||
{
|
||||
if (!nozzle.second.IsEmpty() && !nozzle.second.IsUnknown())
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
void DevNozzleRack::ParseRackInfo(const nlohmann::json& rack_info)
|
||||
{
|
||||
ParseRackInfoV1_0(rack_info);
|
||||
}
|
||||
|
||||
void DevNozzleRack::ParseRackInfoV1_0(const nlohmann::json& rack_info)
|
||||
{
|
||||
DevJsonValParser::ParseVal(rack_info, "stat", m_status, RACK_STATUS_UNKNOWN);
|
||||
if (m_status < RACK_STATUS_UNKNOWN || m_status >= RACK_STATUS_END)
|
||||
{
|
||||
m_status = RACK_STATUS_UNKNOWN; // Reset to default if out of range
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": Invalid rack status: " << m_status << ", reset";
|
||||
}
|
||||
|
||||
DevJsonValParser::ParseVal(rack_info, "pos", m_position, RACK_POS_UNKNOWN);
|
||||
if (m_position < RACK_POS_UNKNOWN || m_position >= RACK_POS_END)
|
||||
{
|
||||
m_position = RACK_POS_UNKNOWN; // Reset to default if out of range
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": Invalid rack position: " << m_position << ", reset";
|
||||
}
|
||||
|
||||
DevJsonValParser::ParseVal(rack_info, "info", m_cali_status);
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,134 @@
|
||||
#pragma once
|
||||
#include "libslic3r/CommonDefs.hpp"
|
||||
#include "libslic3r/MultiNozzleUtils.hpp"
|
||||
#include "slic3r/Utils/json_diff.hpp"
|
||||
|
||||
#include "DevNozzleSystem.h"
|
||||
#include "DevFirmware.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
// wx
|
||||
#include <wx/string.h>
|
||||
#include <wx/event.h>
|
||||
|
||||
wxDECLARE_EVENT(DEV_RACK_EVENT_READING_FINISHED, wxCommandEvent);
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
// Previous definitions
|
||||
class DevNozzleSystem;
|
||||
|
||||
class DevNozzleRack: public wxEvtHandler
|
||||
{
|
||||
public:
|
||||
enum RackStatus : int
|
||||
{
|
||||
RACK_STATUS_UNKNOWN = -1,
|
||||
RACK_STATUS_IDLE = 0,
|
||||
RACK_STATUS_HOTEND_CENTRE = 1,
|
||||
RACK_STATUS_TOOLHEAD_CENTRE = 2,
|
||||
RACK_STATUS_CALIBRATE_HOTEND_RACK = 3,
|
||||
RACK_STATUS_CUT_MATERIAL = 4,
|
||||
RACK_STATUS_UNLOCK_HOTEND = 5,
|
||||
RACK_STATUS_LIFT_HOTEND_RACK = 6,
|
||||
RACK_STATUS_PLACE_HOTEND = 7,
|
||||
RACK_STATUS_PICK_HOTEND = 8,
|
||||
RACK_STATUS_LOCK_HOTEND = 9,
|
||||
RACK_STATUS_END,
|
||||
};
|
||||
|
||||
enum RackPos : int
|
||||
{
|
||||
RACK_POS_UNKNOWN = 0,
|
||||
RACK_POS_A_TOP = 1,
|
||||
RACK_POS_B_TOP = 2,
|
||||
RACK_POS_CENTRE = 3,
|
||||
RACK_POS_END,
|
||||
};
|
||||
|
||||
enum RackCaliStatus
|
||||
{
|
||||
Rack_CALI_UNKNOWN = -1,
|
||||
Rack_CALI_NOT = 0,
|
||||
Rack_CALI_OK = 1,
|
||||
};
|
||||
|
||||
public:
|
||||
DevNozzleRack(DevNozzleSystem* nozzle_system);
|
||||
~DevNozzleRack() = default;
|
||||
|
||||
public:
|
||||
// Is supported by the printer
|
||||
bool IsSupported() const { return m_is_supported; };
|
||||
void SetSupported(bool supported) { m_is_supported = supported; }
|
||||
|
||||
// getters
|
||||
DevNozzleSystem* GetNozzleSystem() const { return m_nozzle_system; }
|
||||
|
||||
RackPos GetPosition() const { return m_position; }
|
||||
RackStatus GetStatus() const { return m_status; }
|
||||
RackCaliStatus GetCaliStatus() const { return m_cali_status;}
|
||||
|
||||
DevNozzle GetNozzle(int idx) const;
|
||||
const std::map<int, DevNozzle>& GetRackNozzles() const { return m_rack_nozzles; }
|
||||
|
||||
// status
|
||||
bool HasUnreliableNozzles() const;
|
||||
bool HasUnknownNozzles() const;
|
||||
int GetKnownNozzleCount() const;
|
||||
|
||||
// refreshing
|
||||
int GetReadingIdx() const { return m_nozzle_system->GetReadingIdx(); }
|
||||
int GetReadingCount() const { return m_nozzle_system->GetReadingCount(); }
|
||||
void SendReadingFinished();
|
||||
|
||||
// firmware
|
||||
void AddNozzleFirmwareInfo(int nozzle_id, const DevFirmwareVersionInfo& info) { m_rack_nozzles_firmware[nozzle_id] = info; }
|
||||
void ClearNozzleFirmwareInfo() { m_rack_nozzles_firmware.clear(); }
|
||||
DevFirmwareVersionInfo GetNozzleFirmwareInfo(int nozzle_id) const;
|
||||
|
||||
// setters
|
||||
void Reset();
|
||||
void AddRackNozzle(DevNozzle& nozzle) { nozzle.SetOnRack(true); m_rack_nozzles[nozzle.m_nozzle_id] = nozzle; };
|
||||
void ClearRackNozzles() { m_rack_nozzles.clear(); }
|
||||
|
||||
public:
|
||||
void ParseRackInfo(const nlohmann::json& rack_info);
|
||||
|
||||
void CtrlRackPosMove(RackPos new_pos) const;
|
||||
void CtrlRackPosGoHome() const;
|
||||
|
||||
void CtrlRackConfirmNozzle(int rack_nozzle_id) const;
|
||||
void CtrlRackConfirmAll() const;
|
||||
|
||||
void CrtlRackReadNozzle(int rack_nozzle_id) const;
|
||||
void CtrlRackReadAll(bool gui_check = false) const;
|
||||
bool CtrlCanReadAll() const;
|
||||
|
||||
// the upgrade is not supported
|
||||
// the GUI interface is removed
|
||||
int CtrlRackUpgradeExtruderNozzle() const;
|
||||
int CtrlRackUpgradeRackNozzle(int rack_nozzle_id) const;;
|
||||
int CtrlRackUpgradeAll() const;;
|
||||
bool CtrlCanUpdateAll() const;
|
||||
|
||||
private:
|
||||
void ParseRackInfoV1_0(const nlohmann::json& rack_info);
|
||||
|
||||
int CtrlRackUpgrade(const std::string& module_str) const;
|
||||
|
||||
bool CheckRackMoveWarningDlg() const;
|
||||
|
||||
private:
|
||||
DevNozzleSystem* m_nozzle_system = nullptr;
|
||||
|
||||
bool m_is_supported = false; // Indicates if the nozzle rack is supported by the printer
|
||||
RackPos m_position = RACK_POS_UNKNOWN;
|
||||
RackStatus m_status = RACK_STATUS_UNKNOWN;
|
||||
RackCaliStatus m_cali_status = Rack_CALI_UNKNOWN;
|
||||
|
||||
std::map<int, DevNozzle> m_rack_nozzles; // Map of nozzle ID to DevNozzle objects
|
||||
std::map<int, DevFirmwareVersionInfo> m_rack_nozzles_firmware;
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,283 @@
|
||||
#include "DevNozzleRack.h"
|
||||
#include "DevUtil.h"
|
||||
#include "DevExtruderSystem.h"
|
||||
|
||||
#include "slic3r/GUI/DeviceManager.hpp"
|
||||
#include "slic3r/GUI/MsgDialog.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
|
||||
#include <wx/thread.h>
|
||||
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
|
||||
void DevNozzleRack::CtrlRackPosMove(RackPos new_pos) const
|
||||
{
|
||||
if (!CheckRackMoveWarningDlg())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int action_id = -1;
|
||||
if (new_pos == RACK_POS_A_TOP)
|
||||
{
|
||||
action_id = 1;
|
||||
}
|
||||
else if(new_pos == RACK_POS_B_TOP)
|
||||
{
|
||||
action_id = 2;
|
||||
}
|
||||
|
||||
if (action_id != -1)
|
||||
{
|
||||
json j;
|
||||
j["print"]["command"] = "nozzle_holder_ctrl";
|
||||
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
|
||||
j["print"]["action"] = action_id;
|
||||
m_nozzle_system->GetOwner()->publish_json(j);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void DevNozzleRack::CtrlRackPosGoHome() const
|
||||
{
|
||||
if (!CheckRackMoveWarningDlg())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
json j;
|
||||
j["print"]["command"] = "nozzle_holder_ctrl";
|
||||
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
|
||||
j["print"]["action"] = 0;
|
||||
m_nozzle_system->GetOwner()->publish_json(j);
|
||||
}
|
||||
|
||||
bool DevNozzleRack::CheckRackMoveWarningDlg() const
|
||||
{
|
||||
if (wxThread::IsMain())
|
||||
{
|
||||
static bool s_show_move_warning = true;
|
||||
if (s_show_move_warning)
|
||||
{
|
||||
Slic3r::GUI::MessageDialog dlg(nullptr, _L("The toolhead and hotend rack may move. Please keep your hands away from the chamber."),
|
||||
_L("Warning"), wxICON_WARNING | wxOK);
|
||||
dlg.show_dsa_button();
|
||||
if (dlg.ShowModal() != wxID_OK)
|
||||
{
|
||||
s_show_move_warning = !dlg.get_checkbox_state();
|
||||
return false;
|
||||
}
|
||||
|
||||
s_show_move_warning = !dlg.get_checkbox_state();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DevNozzleRack::CtrlRackConfirmNozzle(int rack_nozzle_id) const
|
||||
{
|
||||
json j;
|
||||
j["print"]["command"] = "nozzle_info_confirm";
|
||||
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
|
||||
j["print"]["id"] = (rack_nozzle_id + 16); // from 0x16
|
||||
m_nozzle_system->GetOwner()->publish_json(j);
|
||||
}
|
||||
|
||||
void DevNozzleRack::CtrlRackConfirmAll() const
|
||||
{
|
||||
json j;
|
||||
j["print"]["command"] = "nozzle_info_confirm";
|
||||
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
|
||||
j["print"]["id"] = 0xff;
|
||||
m_nozzle_system->GetOwner()->publish_json(j);
|
||||
}
|
||||
|
||||
void DevNozzleRack::CrtlRackReadNozzle(int rack_nozzle_id) const
|
||||
{
|
||||
json j;
|
||||
j["print"]["command"] = "holder_nozzle_refresh";
|
||||
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
|
||||
j["print"]["id"] = (rack_nozzle_id + 16); // from 0x16
|
||||
m_nozzle_system->GetOwner()->publish_json(j);
|
||||
}
|
||||
|
||||
|
||||
bool DevNozzleRack::CtrlCanReadAll() const
|
||||
{
|
||||
//is in print
|
||||
if (m_nozzle_system->GetOwner()->is_in_printing())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_nozzle_system->GetOwner()->is_in_upgrading()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//if have nozzle
|
||||
bool has_nozzle_on_rack = false;
|
||||
for (const auto &nozzle_pair : m_rack_nozzles)
|
||||
{
|
||||
if (!nozzle_pair.second.IsEmpty())
|
||||
{
|
||||
has_nozzle_on_rack = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool has_nozzle_on_ext = false;
|
||||
if (m_nozzle_system->ContainsExtNozzle(MAIN_EXTRUDER_ID)) {
|
||||
has_nozzle_on_ext = true;
|
||||
}
|
||||
|
||||
if (!has_nozzle_on_rack && !has_nozzle_on_ext) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//if is in loading
|
||||
if (m_nozzle_system->GetOwner()->ams_status_main == AMS_STATUS_MAIN_FILAMENT_CHANGE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto ext = m_nozzle_system->GetOwner()->GetExtderSystem();
|
||||
if (ext && ext->IsBusyLoading()) return false;
|
||||
|
||||
if (GetReadingCount() > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return m_status == RACK_STATUS_IDLE;
|
||||
}
|
||||
|
||||
void DevNozzleRack::CtrlRackReadAll(bool gui_check) const
|
||||
{
|
||||
if (gui_check && wxThread::IsMain())
|
||||
{
|
||||
#if 0
|
||||
if (!HasUnknownNozzles()) {
|
||||
Slic3r::GUI::MessageDialog dlg(nullptr, _L("Hotend information may be inaccurate. "
|
||||
"Would you like to re-read the hotend? (Hotend information may change during power-off)."),
|
||||
_L("Warning"), wxICON_WARNING | wxOK | wxYES);
|
||||
dlg.SetButtonLabel(wxID_OK, _L("I confirm all"));
|
||||
dlg.SetButtonLabel(wxID_YES, _L("Re-read all"));
|
||||
|
||||
int rtn = dlg.ShowModal();
|
||||
if (rtn == wxID_OK) {
|
||||
CtrlRackConfirmAll();
|
||||
} else if (rtn == wxID_YES) {
|
||||
if (CheckRackMoveWarningDlg()) {
|
||||
CtrlRackReadAll(false);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (CheckRackMoveWarningDlg()) {
|
||||
CtrlRackReadAll(false);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
json j;
|
||||
j["print"]["command"] = "holder_nozzle_refresh";
|
||||
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
|
||||
j["print"]["id"] = 0xff;
|
||||
m_nozzle_system->GetOwner()->publish_json(j);
|
||||
}
|
||||
|
||||
|
||||
bool DevNozzleRack::CtrlCanUpdateAll() const
|
||||
{
|
||||
if (m_nozzle_system->GetOwner()->is_in_printing())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto ext_nozzle = m_nozzle_system->GetExtNozzle(MAIN_EXTRUDER_ID);
|
||||
if (ext_nozzle.IsAbnormal())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
auto ext_nozzle_firmware = m_nozzle_system->GetExtruderNozzleFirmware();
|
||||
if (!ext_nozzle_firmware.sw_new_ver.empty() &&
|
||||
ext_nozzle_firmware.sw_new_ver != ext_nozzle_firmware.sw_ver)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for (auto val : m_rack_nozzles_firmware)
|
||||
{
|
||||
const auto& firmware_info = val.second;
|
||||
if (!firmware_info.sw_new_ver.empty() && firmware_info.sw_new_ver != firmware_info.sw_ver)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (GetNozzle(val.first).IsAbnormal())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int DevNozzleRack::CtrlRackUpgradeExtruderNozzle() const
|
||||
{
|
||||
return CtrlRackUpgrade("wtm");
|
||||
}
|
||||
|
||||
int DevNozzleRack::CtrlRackUpgradeRackNozzle(int rack_nozzle_id) const
|
||||
{
|
||||
return CtrlRackUpgrade("wtm/" + std::to_string(0x10 + rack_nozzle_id));
|
||||
}
|
||||
|
||||
int DevNozzleRack::CtrlRackUpgradeAll() const
|
||||
{
|
||||
return CtrlRackUpgrade("wtm_all");
|
||||
}
|
||||
|
||||
int DevNozzleRack::CtrlRackUpgrade(const std::string& module_str) const
|
||||
{
|
||||
if (wxThread::IsMain())
|
||||
{
|
||||
if (GetReadingCount() > 0)
|
||||
{
|
||||
Slic3r::GUI::MessageDialog dlg(nullptr, _L("Reading the hotends, please wait."), _L("Warning"), wxICON_WARNING | wxOK);
|
||||
dlg.ShowModal();
|
||||
return -1;
|
||||
}
|
||||
|
||||
static bool s_show_upgrade_warning = true;
|
||||
if (s_show_upgrade_warning)
|
||||
{
|
||||
Slic3r::GUI::MessageDialog dlg(nullptr, _L("During the hotend upgrade, the toolhead will move. Don't reach into the chamber."),
|
||||
_L("Warning"), wxICON_WARNING | wxOK | wxCANCEL);
|
||||
dlg.show_dsa_button();
|
||||
dlg.SetButtonLabel(wxID_OK, _L("Update"));
|
||||
int rtn = dlg.ShowModal();
|
||||
s_show_upgrade_warning = !dlg.get_checkbox_state();
|
||||
if (rtn != wxID_OK)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
json j;
|
||||
j["upgrade"]["command"] = "wtm_upgrade";
|
||||
j["upgrade"]["module"] = module_str;
|
||||
j["upgrade"]["src_id"] = 1;
|
||||
j["upgrade"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
|
||||
return m_nozzle_system->GetOwner()->publish_json(j);
|
||||
}
|
||||
|
||||
};
|
||||
@@ -1,35 +1,413 @@
|
||||
#include "DevExtruderSystem.h"
|
||||
|
||||
#include "DevNozzleRack.h"
|
||||
#include "DevNozzleSystem.h"
|
||||
#include "DevUtil.h"
|
||||
|
||||
#include "slic3r/GUI/DeviceManager.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
|
||||
DevNozzle DevNozzleSystem::GetNozzle(int id) const
|
||||
// ---- DevNozzle: flow/volume conversions (Standard / High Flow / TPU High Flow) ----------------------
|
||||
// Device-reported U_FLOW nozzles map to nvtTPUHighFlow so a synced TPU-HF rack activates the H2C
|
||||
// change_filament_gcode TPU-kit branch. nvtHybrid is a slicer-only sentinel with no device
|
||||
// representation, so ToNozzleFlowType(nvtHybrid) falls through to NONE_FLOWTYPE.
|
||||
|
||||
NozzleFlowType DevNozzle::ToNozzleFlowType(const NozzleVolumeType& type)
|
||||
{
|
||||
if (m_nozzles.find(id) != m_nozzles.end())
|
||||
switch (type) {
|
||||
case NozzleVolumeType::nvtStandard: return NozzleFlowType::S_FLOW;
|
||||
case NozzleVolumeType::nvtHighFlow: return NozzleFlowType::H_FLOW;
|
||||
case NozzleVolumeType::nvtTPUHighFlow: return NozzleFlowType::U_FLOW;
|
||||
default: return NozzleFlowType::NONE_FLOWTYPE;
|
||||
}
|
||||
}
|
||||
|
||||
NozzleVolumeType DevNozzle::ToNozzleVolumeType(const NozzleFlowType& type)
|
||||
{
|
||||
switch (type) {
|
||||
case NozzleFlowType::S_FLOW: return NozzleVolumeType::nvtStandard;
|
||||
case NozzleFlowType::H_FLOW: return NozzleVolumeType::nvtHighFlow;
|
||||
case NozzleFlowType::U_FLOW: return NozzleVolumeType::nvtTPUHighFlow;
|
||||
default: {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << "nozzle flow type None convert to nozzle volume type Standard";
|
||||
return NozzleVolumeType::nvtStandard;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wxString DevNozzle::GetNozzleFlowTypeStr(NozzleFlowType type)
|
||||
{
|
||||
switch (type) {
|
||||
case NozzleFlowType::H_FLOW: return _L("High Flow");
|
||||
case NozzleFlowType::S_FLOW: return _L("Standard");
|
||||
case NozzleFlowType::U_FLOW: return _L("TPU High Flow");
|
||||
default: break;
|
||||
}
|
||||
|
||||
return _L("Unknown");
|
||||
}
|
||||
|
||||
// Untranslated flow-type literal used for filaments_blacklist.json nozzle_flows matching.
|
||||
// Keep the raw English strings; the translated GetNozzleFlowTypeStr must not be used for JSON matching.
|
||||
std::string DevNozzle::GetNozzleFlowTypeString(NozzleFlowType type)
|
||||
{
|
||||
switch (type) {
|
||||
case NozzleFlowType::H_FLOW: return "High Flow";
|
||||
case NozzleFlowType::S_FLOW: return "Standard";
|
||||
case NozzleFlowType::U_FLOW: return "TPU High Flow";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
// Untranslated flow-type literal serialized into the get_auto_nozzle_mapping payload.
|
||||
// Differs from GetNozzleFlowTypeString only in the fallback: "" (empty) rather than "Unknown".
|
||||
std::string DevNozzle::ToNozzleFlowString(const NozzleFlowType& type)
|
||||
{
|
||||
switch (type) {
|
||||
case NozzleFlowType::S_FLOW: return "Standard";
|
||||
case NozzleFlowType::H_FLOW: return "High Flow";
|
||||
case NozzleFlowType::U_FLOW: return "TPU High Flow";
|
||||
default: return std::string();
|
||||
}
|
||||
}
|
||||
|
||||
wxString DevNozzle::GetNozzleTypeStr(NozzleType type)
|
||||
{
|
||||
switch (type) {
|
||||
case Slic3r::ntHardenedSteel: return _L("Hardened Steel");
|
||||
case Slic3r::ntStainlessSteel: return _L("Stainless Steel");
|
||||
case Slic3r::ntTungstenCarbide: return _L("Tungsten Carbide");
|
||||
default: break;
|
||||
}
|
||||
|
||||
return _L("Unknown");
|
||||
}
|
||||
|
||||
// ---- DevNozzle: status ------------------------------------------------------------------------------
|
||||
|
||||
bool DevNozzle::IsInfoReliable() const
|
||||
{
|
||||
if (IsEmpty()) { return false; }
|
||||
return DevUtil::get_flag_bits(m_stat, 0, 1) == 0;
|
||||
}
|
||||
|
||||
bool DevNozzle::IsNormal() const
|
||||
{
|
||||
if (IsEmpty()) { return false; }
|
||||
return DevUtil::get_flag_bits(m_stat, 1, 2) == 0;
|
||||
}
|
||||
|
||||
bool DevNozzle::IsAbnormal() const
|
||||
{
|
||||
return DevUtil::get_flag_bits(m_stat, 1, 2) == (1 << 0);
|
||||
}
|
||||
|
||||
bool DevNozzle::IsUnknown() const
|
||||
{
|
||||
return DevUtil::get_flag_bits(m_stat, 1, 2) == (1 << 1);
|
||||
}
|
||||
|
||||
int DevNozzle::GetNozzlePosId() const
|
||||
{
|
||||
return IsOnRack() ? (m_nozzle_id + 0x10) : m_nozzle_id;
|
||||
}
|
||||
|
||||
NozzleDiameterType DevNozzle::GetNozzleDiameterType() const
|
||||
{
|
||||
if (is_approx(m_diameter, 0.2f))
|
||||
return NozzleDiameterType::NOZZLE_DIAMETER_0_2;
|
||||
else if (is_approx(m_diameter, 0.4f))
|
||||
return NozzleDiameterType::NOZZLE_DIAMETER_0_4;
|
||||
else if (is_approx(m_diameter, 0.6f))
|
||||
return NozzleDiameterType::NOZZLE_DIAMETER_0_6;
|
||||
else if (is_approx(m_diameter, 0.8f))
|
||||
return NozzleDiameterType::NOZZLE_DIAMETER_0_8;
|
||||
else
|
||||
return NozzleDiameterType::NONE_DIAMETER_TYPE;
|
||||
}
|
||||
|
||||
DevFirmwareVersionInfo DevNozzle::GetFirmwareInfo() const
|
||||
{
|
||||
const auto& nozzle_rack = m_nozzle_rack.lock();
|
||||
if (nozzle_rack) {
|
||||
if (IsOnRack()) {
|
||||
return nozzle_rack->GetNozzleFirmwareInfo(m_nozzle_id);
|
||||
} else if (m_nozzle_id == 0) {
|
||||
return nozzle_rack->GetNozzleSystem()->GetExtruderNozzleFirmware();
|
||||
}
|
||||
}
|
||||
|
||||
return DevFirmwareVersionInfo();
|
||||
}
|
||||
|
||||
// ---- DevNozzle: location ----------------------------------------------------------------------------
|
||||
|
||||
int DevNozzle::GetLogicExtruderId() const
|
||||
{
|
||||
int total_ext_count = GetTotalExtruderCount();
|
||||
if (total_ext_count == 1) {
|
||||
return LOGIC_UNIQUE_EXTRUDER_ID;
|
||||
} else if (total_ext_count == 2) {
|
||||
if (AtLeftExtruder()) {
|
||||
return LOGIC_L_EXTRUDER_ID;
|
||||
} else if (AtRightExtruder()) {
|
||||
return LOGIC_R_EXTRUDER_ID;
|
||||
}
|
||||
}
|
||||
|
||||
assert(0);
|
||||
return LOGIC_UNIQUE_EXTRUDER_ID;
|
||||
}
|
||||
|
||||
int DevNozzle::GetExtruderId() const
|
||||
{
|
||||
int total_ext_count = GetTotalExtruderCount();
|
||||
if (total_ext_count == 1) {
|
||||
return MAIN_EXTRUDER_ID;
|
||||
} else if (total_ext_count == 2) {
|
||||
if (AtRightExtruder()) {
|
||||
return MAIN_EXTRUDER_ID;
|
||||
} else if (AtLeftExtruder()) {
|
||||
return DEPUTY_EXTRUDER_ID;
|
||||
}
|
||||
}
|
||||
|
||||
return MAIN_EXTRUDER_ID;
|
||||
}
|
||||
|
||||
bool DevNozzle::AtLeftExtruder() const
|
||||
{
|
||||
assert(GetTotalExtruderCount() == 2);
|
||||
if (IsOnRack()) { return false; }
|
||||
return m_nozzle_id == DEPUTY_EXTRUDER_ID;
|
||||
}
|
||||
|
||||
bool DevNozzle::AtRightExtruder() const
|
||||
{
|
||||
assert(GetTotalExtruderCount() == 2);
|
||||
if (IsOnRack()) { return true; }
|
||||
return m_nozzle_id == MAIN_EXTRUDER_ID;
|
||||
}
|
||||
|
||||
int DevNozzle::GetTotalExtruderCount() const
|
||||
{
|
||||
auto rack = m_nozzle_rack.lock();
|
||||
if (rack) {
|
||||
MachineObject* obj = rack->GetNozzleSystem()->GetOwner();
|
||||
return obj->GetExtderSystem()->GetTotalExtderCount();
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ---- DevNozzleSystem --------------------------------------------------------------------------------
|
||||
|
||||
DevNozzleSystem::DevNozzleSystem(MachineObject* owner)
|
||||
: m_owner(owner), m_nozzle_rack(std::make_shared<DevNozzleRack>(this))
|
||||
{
|
||||
}
|
||||
|
||||
DevNozzle DevNozzleSystem::GetExtNozzle(int id) const
|
||||
{
|
||||
if (m_ext_nozzles.find(id) != m_ext_nozzles.end())
|
||||
{
|
||||
return m_nozzles.at(id);
|
||||
return m_ext_nozzles.at(id);
|
||||
}
|
||||
|
||||
return DevNozzle();
|
||||
}
|
||||
|
||||
void DevNozzleSystem::Reset()
|
||||
DevNozzle DevNozzleSystem::GetRackNozzle(int idx) const
|
||||
{
|
||||
m_nozzles.clear();
|
||||
m_extder_exist = 0;
|
||||
m_state = 0; // idle state
|
||||
return m_nozzle_rack->GetNozzle(idx);
|
||||
}
|
||||
|
||||
const std::map<int, DevNozzle>& DevNozzleSystem::GetRackNozzles() const
|
||||
{
|
||||
return m_nozzle_rack->GetRackNozzles();
|
||||
}
|
||||
|
||||
void DevNozzleSystem::SetSupportNozzleRack(bool supported)
|
||||
{
|
||||
m_nozzle_rack->SetSupported(supported);
|
||||
}
|
||||
|
||||
const std::vector<DevNozzle> DevNozzleSystem::CollectNozzles(int ext_loc, NozzleFlowType flow_type, float diameter) const
|
||||
{
|
||||
auto s_match = [&](const DevNozzle& nozzle) -> bool {
|
||||
if (nozzle.IsEmpty() || nozzle.IsAbnormal()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (diameter >= 0.0f && nozzle.m_diameter != diameter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_owner->is_nozzle_flow_type_supported() && flow_type != nozzle.GetNozzleFlowType()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
std::vector<DevNozzle> result;
|
||||
|
||||
auto ext_nozzle = GetExtNozzle(ext_loc);
|
||||
if (s_match(ext_nozzle)) {
|
||||
result.push_back(ext_nozzle);
|
||||
}
|
||||
|
||||
if (ext_loc == MAIN_EXTRUDER_ID) {
|
||||
auto rack_nozzles = m_nozzle_rack->GetRackNozzles();
|
||||
for (auto rack_nozzle : rack_nozzles) {
|
||||
if (s_match(rack_nozzle.second)) {
|
||||
result.push_back(rack_nozzle.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<MultiNozzleUtils::NozzleGroupInfo> DevNozzleSystem::GetNozzleGroups() const
|
||||
{
|
||||
std::vector<MultiNozzleUtils::NozzleGroupInfo> nozzle_groups;
|
||||
|
||||
auto nozzle_in_extruder = this->GetExtNozzles();
|
||||
for (auto& elem : nozzle_in_extruder) {
|
||||
auto& nozzle = elem.second;
|
||||
MultiNozzleUtils::NozzleGroupInfo info;
|
||||
info.extruder_id = nozzle.GetLogicExtruderId();
|
||||
info.diameter = format_diameter_to_str(nozzle.m_diameter);
|
||||
info.volume_type = DevNozzle::ToNozzleVolumeType(nozzle.m_nozzle_flow);
|
||||
info.nozzle_count = 1;
|
||||
nozzle_groups.emplace_back(std::move(info));
|
||||
}
|
||||
|
||||
auto nozzle_rack = this->GetNozzleRack();
|
||||
if (!nozzle_rack)
|
||||
return nozzle_groups;
|
||||
|
||||
auto nozzle_in_rack = nozzle_rack->GetRackNozzles(); // nozzles in rack
|
||||
for (auto& elem : nozzle_in_rack) {
|
||||
auto& nozzle = elem.second;
|
||||
if (nozzle.IsUnknown() || nozzle.IsAbnormal())
|
||||
continue;
|
||||
int extruder_id = nozzle.GetLogicExtruderId();
|
||||
std::string diameter = format_diameter_to_str(nozzle.m_diameter);
|
||||
NozzleVolumeType volume_type = DevNozzle::ToNozzleVolumeType(nozzle.m_nozzle_flow);
|
||||
|
||||
bool found = false;
|
||||
for (auto& group : nozzle_groups) {
|
||||
if (group.extruder_id == extruder_id &&
|
||||
group.volume_type == volume_type &&
|
||||
group.diameter == diameter) {
|
||||
found = true;
|
||||
group.nozzle_count += 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
nozzle_groups.emplace_back(diameter, volume_type, extruder_id, 1);
|
||||
}
|
||||
return nozzle_groups;
|
||||
}
|
||||
|
||||
bool DevNozzleSystem::IsRackMaximumInstalled() const
|
||||
{
|
||||
auto ext_nozzle = GetExtNozzle(MAIN_EXTRUDER_ID);
|
||||
if (ext_nozzle.IsEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& rack_nozzles = m_nozzle_rack->GetRackNozzles();
|
||||
if (rack_nozzles.size() < 6) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto item : rack_nozzles) {
|
||||
if (item.second.IsEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DevNozzleSystem::HasUnreliableNozzles() const
|
||||
{
|
||||
for (auto nozzle : m_ext_nozzles) {
|
||||
if (!nozzle.second.IsInfoReliable()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_nozzle_rack->HasUnreliableNozzles()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DevNozzleSystem::HasUnknownNozzles() const
|
||||
{
|
||||
for (auto nozzle : m_ext_nozzles) {
|
||||
if (nozzle.second.IsUnknown()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_nozzle_rack->HasUnknownNozzles()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void DevNozzleSystem::AddFirmwareInfoWTM(const DevFirmwareVersionInfo& info)
|
||||
{
|
||||
static const std::string s_wtm_prefix = "wtm/";
|
||||
auto pos = info.name.find(s_wtm_prefix);
|
||||
if (pos == std::string::npos) {
|
||||
m_ext_nozzle_firmware_info = info;
|
||||
} else {
|
||||
try {
|
||||
auto str = info.name.substr(s_wtm_prefix.size()); // remove "wtm/" prefix
|
||||
int rack_nozzle_id = std::stoi(str) - 0x10; // rack nozzle IDs start from 0x10
|
||||
m_nozzle_rack->AddNozzleFirmwareInfo(rack_nozzle_id, info);
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": Invalid nozzle ID in firmware name: "
|
||||
<< info.name << ", error: " << e.what();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DevNozzleSystem::ClearFirmwareInfoWTM()
|
||||
{
|
||||
m_ext_nozzle_firmware_info = DevFirmwareVersionInfo();
|
||||
m_nozzle_rack->ClearNozzleFirmwareInfo();
|
||||
}
|
||||
|
||||
void DevNozzleSystem::ClearNozzles()
|
||||
{
|
||||
m_ext_nozzles.clear();
|
||||
m_nozzle_rack->ClearRackNozzles();
|
||||
}
|
||||
|
||||
// ---- parsing ----------------------------------------------------------------------------------------
|
||||
|
||||
static unordered_map<string, NozzleFlowType> _str2_nozzle_flow_type = {
|
||||
{"S", NozzleFlowType::S_FLOW},
|
||||
{"H", NozzleFlowType::H_FLOW},
|
||||
{"A", NozzleFlowType::S_FLOW},
|
||||
{"X", NozzleFlowType::S_FLOW}
|
||||
{"X", NozzleFlowType::S_FLOW},
|
||||
{"E", NozzleFlowType::H_FLOW}, // E3D high-flow
|
||||
{"U", NozzleFlowType::U_FLOW}, // TPU 1.75 high-flow -> nvtTPUHighFlow
|
||||
};
|
||||
|
||||
static unordered_map<string, NozzleType> _str2_nozzle_type = {
|
||||
@@ -57,6 +435,11 @@ static void s_parse_nozzle_type(const std::string& nozzle_type_str, DevNozzle& n
|
||||
nozzle.m_nozzle_type = _str2_nozzle_type[type_str];
|
||||
}
|
||||
}
|
||||
else if (nozzle_type_str == "N/A")
|
||||
{
|
||||
nozzle.m_nozzle_type = NozzleType::ntUndefine;
|
||||
nozzle.m_nozzle_flow = NozzleFlowType::NONE_FLOWTYPE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,8 +449,9 @@ void DevNozzleSystemParser::ParseV1_0(const nlohmann::json& nozzletype_json,
|
||||
std::optional<int> flag_e3d)
|
||||
{
|
||||
//Since both the old and new protocols push data.
|
||||
// assert(system->m_nozzles.size() < 2);
|
||||
// assert(system->m_ext_nozzles.size() < 2);
|
||||
DevNozzle nozzle;
|
||||
nozzle.SetRack(system->GetNozzleRack());
|
||||
nozzle.m_nozzle_id = 0;
|
||||
nozzle.m_nozzle_flow = NozzleFlowType::S_FLOW; // default flow type
|
||||
|
||||
@@ -112,32 +496,88 @@ void DevNozzleSystemParser::ParseV1_0(const nlohmann::json& nozzletype_json,
|
||||
}
|
||||
}
|
||||
|
||||
system->m_nozzles[nozzle.m_nozzle_id] = nozzle;
|
||||
system->m_ext_nozzles[nozzle.m_nozzle_id] = nozzle;
|
||||
}
|
||||
|
||||
|
||||
void DevNozzleSystemParser::ParseV2_0(const json& nozzle_json, DevNozzleSystem* system)
|
||||
void DevNozzleSystemParser::ParseV2_0(const json& device_json, DevNozzleSystem* system)
|
||||
{
|
||||
system->Reset();
|
||||
|
||||
if (nozzle_json.contains("exist"))
|
||||
if (device_json.contains("nozzle"))
|
||||
{
|
||||
system->m_extder_exist = DevUtil::get_flag_bits(nozzle_json["exist"].get<int>(), 0, 16);
|
||||
const json& nozzle_json = device_json["nozzle"];
|
||||
if (nozzle_json.contains("exist"))
|
||||
{
|
||||
system->m_extder_exist = DevUtil::get_flag_bits(nozzle_json["exist"].get<int>(), 0, 16);
|
||||
}
|
||||
|
||||
if (nozzle_json.contains("state"))
|
||||
{
|
||||
system->m_state_0_4 = DevUtil::get_flag_bits(nozzle_json["state"].get<int>(), 0, 4);
|
||||
int new_reading_idx = DevUtil::get_flag_bits(nozzle_json["state"].get<int>(), 8, 4);
|
||||
int new_reading_count = DevUtil::get_flag_bits(nozzle_json["state"].get<int>(), 4, 4);
|
||||
if (system->m_reading_count != new_reading_count || system->m_reading_idx != new_reading_idx)
|
||||
{
|
||||
system->m_reading_idx = new_reading_idx;
|
||||
system->m_reading_count = new_reading_count;
|
||||
if (system->m_reading_count == 0)
|
||||
{
|
||||
system->m_nozzle_rack->SendReadingFinished();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// replace-nozzle state: a rack nozzle standing in for the toolhead position.
|
||||
// Absent for every non-rack printer's "nozzle" block => both stay std::nullopt (inert).
|
||||
if (nozzle_json.contains("src_id"))
|
||||
{
|
||||
system->m_replace_nozzle_src = std::make_optional(nozzle_json["src_id"].get<int>());
|
||||
}
|
||||
|
||||
if (nozzle_json.contains("tar_id"))
|
||||
{
|
||||
system->m_replace_nozzle_tar = std::make_optional(nozzle_json["tar_id"].get<int>());
|
||||
}
|
||||
|
||||
system->ClearNozzles();
|
||||
for (auto it = nozzle_json["info"].begin(); it != nozzle_json["info"].end(); it++)
|
||||
{
|
||||
DevNozzle nozzle_obj;
|
||||
nozzle_obj.SetRack(system->GetNozzleRack());
|
||||
|
||||
const auto& njon = it.value();
|
||||
nozzle_obj.m_nozzle_id = DevUtil::get_hex_bits(njon["id"].get<int>(), 0);
|
||||
nozzle_obj.m_diameter = njon["diameter"].get<float>();
|
||||
s_parse_nozzle_type(njon["type"].get<std::string>(), nozzle_obj);
|
||||
if (njon.contains("stat"))/*maybe not contains*/
|
||||
{
|
||||
nozzle_obj.SetStatus(njon["stat"].get<int>());
|
||||
}
|
||||
|
||||
DevJsonValParser::ParseVal(njon, "fila_id", nozzle_obj.m_fila_id);
|
||||
DevJsonValParser::ParseVal(njon, "wear", nozzle_obj.m_wear);
|
||||
if (njon.contains("p_t"))/*maybe not contains*/
|
||||
{
|
||||
nozzle_obj.m_nozzle_print_time = njon["p_t"].get<int>();
|
||||
}
|
||||
if (njon.contains("color_m"))/*maybe not contains*/
|
||||
{
|
||||
nozzle_obj.m_filament_clr = njon["color_m"].get<std::string>();
|
||||
}
|
||||
|
||||
if (DevUtil::get_hex_bits(njon["id"].get<int>(), 1) == 1)
|
||||
{
|
||||
system->m_nozzle_rack->AddRackNozzle(nozzle_obj);
|
||||
}
|
||||
else
|
||||
{
|
||||
system->m_ext_nozzles[nozzle_obj.m_nozzle_id] = nozzle_obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nozzle_json.contains("state"))
|
||||
if (device_json.contains("holder"))
|
||||
{
|
||||
system->m_state = DevUtil::get_flag_bits(nozzle_json["state"].get<int>(), 0, 4);
|
||||
}
|
||||
|
||||
for (auto it = nozzle_json["info"].begin(); it != nozzle_json["info"].end(); it++)
|
||||
{
|
||||
DevNozzle nozzle_obj;
|
||||
const auto& njon = it.value();
|
||||
nozzle_obj.m_nozzle_id = njon["id"].get<int>();
|
||||
nozzle_obj.m_diameter = njon["diameter"].get<float>();
|
||||
s_parse_nozzle_type(njon["type"].get<std::string>(), nozzle_obj);
|
||||
system->m_nozzles[nozzle_obj.m_nozzle_id] = nozzle_obj;
|
||||
system->m_nozzle_rack->ParseRackInfo(device_json["holder"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,28 +1,112 @@
|
||||
#pragma once
|
||||
#include "DevDefs.h"
|
||||
#include "DevFirmware.h"
|
||||
|
||||
#include "libslic3r/CommonDefs.hpp"
|
||||
#include "libslic3r/MultiNozzleUtils.hpp"
|
||||
#include "slic3r/Utils/json_diff.hpp"
|
||||
|
||||
#include <wx/string.h>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
// Device flow-type mapping note (nozzle rack): the device U_FLOW value maps to nvtTPUHighFlow
|
||||
// (TPU High Flow), so a device-synced TPU-HF rack resolves to nvtTPUHighFlow and the H2C
|
||||
// change_filament_gcode TPU-kit branch can activate. nvtHybrid is a slicer-only sentinel with no
|
||||
// device representation, so it stays out of these device flow-type conversions.
|
||||
//
|
||||
// GetExtruderNozzleInfo (and its ExtruderNozzleInfos aggregate) is intentionally omitted here;
|
||||
// the SelectMachine slicing-vs-installed nozzle comparison collects its per-extruder NozzleDef
|
||||
// entries itself (s_get_slicing_extuder_nozzles).
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
// Previous definitions
|
||||
class MachineObject;
|
||||
class DevNozzleRack;
|
||||
|
||||
struct DevNozzle
|
||||
{
|
||||
friend class DevNozzleSystemParser;
|
||||
|
||||
public:
|
||||
int m_nozzle_id = -1;
|
||||
NozzleFlowType m_nozzle_flow = NozzleFlowType::S_FLOW;// 0-common 1-high flow
|
||||
NozzleType m_nozzle_type = NozzleType::ntUndefine;// 0-stainless_steel 1-hardened_steel 5-tungsten_carbide
|
||||
float m_diameter = 0.0f;// unknown until reported by the printer
|
||||
|
||||
public:
|
||||
// flow/volume conversions (Standard / High Flow / TPU High Flow)
|
||||
static NozzleFlowType ToNozzleFlowType(const NozzleVolumeType& type);
|
||||
static NozzleVolumeType ToNozzleVolumeType(const NozzleFlowType& type);
|
||||
|
||||
static wxString GetNozzleFlowTypeStr(NozzleFlowType type);
|
||||
static std::string GetNozzleFlowTypeString(NozzleFlowType type);// untranslated literal ("High Flow"/"Standard"/"TPU High Flow") — the filament blacklist JSON matches these raw strings, so it must NOT use the translated GetNozzleFlowTypeStr (would break non-English locales)
|
||||
static std::string ToNozzleFlowString(const NozzleFlowType& type);// untranslated "Standard"/"High Flow"/"TPU High Flow" ("" for none) — the raw literal serialized into the get_auto_nozzle_mapping payload
|
||||
static wxString GetNozzleTypeStr(NozzleType type);
|
||||
|
||||
public:
|
||||
bool IsEmpty() const { return m_nozzle_id < 0; }
|
||||
|
||||
void SetRack(const std::weak_ptr<DevNozzleRack>& rack) { m_nozzle_rack = rack; }
|
||||
|
||||
int GetNozzleId() const { return m_nozzle_id; }
|
||||
int GetNozzlePosId() const;// physical position id: rack nozzle -> id + 0x10, else id
|
||||
NozzleType GetNozzleType() const { return m_nozzle_type; }
|
||||
NozzleFlowType GetNozzleFlowType() const { return m_nozzle_flow; }
|
||||
NozzleDiameterType GetNozzleDiameterType() const;
|
||||
float GetNozzleDiameter() const { return m_diameter; }
|
||||
float GetNozzleWear() const { return m_wear; }
|
||||
int GetNozzlePrintTime() const { return m_nozzle_print_time; }
|
||||
|
||||
// firmware (rack hotend WTM / extruder nozzle firmware)
|
||||
DevFirmwareVersionInfo GetFirmwareInfo() const;
|
||||
|
||||
// display
|
||||
wxString GetNozzleDiameterStr() const { return wxString::Format("%.1f mm", m_diameter); }
|
||||
wxString GetNozzleFlowTypeStr() const { return GetNozzleFlowTypeStr(m_nozzle_flow); }
|
||||
wxString GetNozzleTypeStr() const { return GetNozzleTypeStr(m_nozzle_type); }
|
||||
|
||||
std::string GetFilamentId() const { return m_fila_id; }
|
||||
std::string GetFilamentColor() const { return m_filament_clr; }
|
||||
|
||||
// location
|
||||
bool AtLeftExtruder() const;
|
||||
bool AtRightExtruder() const;
|
||||
|
||||
int GetLogicExtruderId() const;// warning: logical extruder id
|
||||
int GetExtruderId() const;// warning: physical extruder id
|
||||
|
||||
/* holder nozzle */
|
||||
bool IsOnRack() const { return m_on_rack; }
|
||||
bool IsInfoReliable() const;
|
||||
|
||||
bool IsNormal() const;
|
||||
bool IsAbnormal() const;
|
||||
bool IsUnknown() const;
|
||||
|
||||
void SetOnRack(bool on_rack) { m_on_rack = on_rack; }
|
||||
void SetStatus(int stat) { m_stat = stat; }
|
||||
|
||||
private:
|
||||
int GetTotalExtruderCount() const;
|
||||
|
||||
private:
|
||||
bool m_on_rack = false;
|
||||
|
||||
int m_stat = 0;
|
||||
float m_wear = 0.0f;
|
||||
|
||||
std::string m_fila_id; // main material
|
||||
std::string m_filament_clr; // main color
|
||||
|
||||
std::weak_ptr<DevNozzleRack> m_nozzle_rack; // weak pointer to the nozzle rack
|
||||
int m_nozzle_print_time{0};
|
||||
};
|
||||
|
||||
class DevNozzleSystem
|
||||
{
|
||||
friend class DevNozzleSystemParser;
|
||||
public:
|
||||
DevNozzleSystem(MachineObject* owner) : m_owner(owner) {}
|
||||
private:
|
||||
enum Status : int
|
||||
{
|
||||
@@ -31,27 +115,81 @@ namespace Slic3r
|
||||
};
|
||||
|
||||
public:
|
||||
bool ContainsNozzle(int id) const { return m_nozzles.find(id) != m_nozzles.end(); }
|
||||
DevNozzle GetNozzle(int id) const;
|
||||
const std::map<int, DevNozzle>& GetNozzles() const { return m_nozzles;}
|
||||
bool IsRefreshing() const { return m_state == 1; }
|
||||
DevNozzleSystem(MachineObject* owner);
|
||||
~DevNozzleSystem() = default;
|
||||
|
||||
public:
|
||||
MachineObject* GetOwner() const { return m_owner; }
|
||||
|
||||
// nozzle by position id: pos_id < 0x10 -> extruder nozzle, else rack nozzle at (pos_id - 0x10)
|
||||
DevNozzle GetNozzleByPosId(int pos_id) const { return pos_id < 0x10 ? GetExtNozzle(pos_id) : GetRackNozzle(pos_id - 0x10); }
|
||||
|
||||
// nozzles on extruder
|
||||
bool ContainsExtNozzle(int id) const { return m_ext_nozzles.find(id) != m_ext_nozzles.end(); }
|
||||
DevNozzle GetExtNozzle(int id) const;
|
||||
const std::map<int, DevNozzle>& GetExtNozzles() const { return m_ext_nozzles; }
|
||||
int GetExtNozzleCount() const { return (int) m_ext_nozzles.size(); }
|
||||
|
||||
// nozzles on rack
|
||||
void SetSupportNozzleRack(bool supported);
|
||||
std::shared_ptr<DevNozzleRack> GetNozzleRack() const { return m_nozzle_rack; }
|
||||
DevNozzle GetRackNozzle(int idx) const;
|
||||
const std::map<int, DevNozzle>& GetRackNozzles() const;
|
||||
|
||||
// nozzles on extruder and rack
|
||||
bool IsRackMaximumInstalled() const;// true when the main extruder + all 6 rack slots hold nozzles
|
||||
|
||||
// grouping (drives the MultiNozzleSyncDialog options)
|
||||
const std::vector<DevNozzle> CollectNozzles(int ext_loc, NozzleFlowType flow_type, float diameter = -1.0f) const;
|
||||
std::vector<MultiNozzleUtils::NozzleGroupInfo> GetNozzleGroups() const;
|
||||
|
||||
bool IsIdle() const { return m_state_0_4 == NOZZLE_SYSTEM_IDLE; }
|
||||
bool IsRefreshing() const { return m_state_0_4 == NOZZLE_SYSTEM_REFRESHING; }
|
||||
|
||||
bool HasUnreliableNozzles() const;// any extruder or rack nozzle whose reported info is not reliable
|
||||
bool HasUnknownNozzles() const; // any extruder or rack nozzle of unknown state
|
||||
|
||||
/* reading */
|
||||
int GetReadingIdx() const { return m_reading_idx; }
|
||||
int GetReadingCount() const { return m_reading_count; }
|
||||
|
||||
/* firmware */
|
||||
void AddFirmwareInfoWTM(const DevFirmwareVersionInfo& info);// route a "wtm/<id>" module version to the rack nozzle, else to the extruder nozzle
|
||||
void ClearFirmwareInfoWTM();
|
||||
DevFirmwareVersionInfo GetExtruderNozzleFirmware() const { return m_ext_nozzle_firmware_info; }
|
||||
|
||||
/* replace nozzle (device reports src/tar position while a rack nozzle stands in for the toolhead) */
|
||||
std::optional<int> GetReplaceNozzleSrc() const { return m_replace_nozzle_src; }
|
||||
std::optional<int> GetReplaceNozzleTar() const { return m_replace_nozzle_tar; }
|
||||
|
||||
private:
|
||||
void Reset();
|
||||
void ClearNozzles();
|
||||
|
||||
private:
|
||||
MachineObject* m_owner = nullptr;
|
||||
|
||||
int m_extder_exist = 0; //0- none exist 1-exist, unused
|
||||
int m_state = 0; //0-idle 1-checking, unused
|
||||
std::map<int, DevNozzle> m_nozzles;
|
||||
int m_extder_exist = 0; //0- none exist 1-exist, unused
|
||||
int m_state_0_4 = 0; //0-idle 1-refreshing
|
||||
|
||||
std::optional<int> m_replace_nozzle_src; // replace nozzle source position (device-reported)
|
||||
std::optional<int> m_replace_nozzle_tar; // replace nozzle target position (device-reported)
|
||||
|
||||
/* refreshing */
|
||||
int m_reading_idx = 0;
|
||||
int m_reading_count = 0;
|
||||
|
||||
// nozzles on extruder
|
||||
std::map<int, DevNozzle> m_ext_nozzles;
|
||||
DevFirmwareVersionInfo m_ext_nozzle_firmware_info;
|
||||
|
||||
// nozzles on rack
|
||||
std::shared_ptr<DevNozzleRack> m_nozzle_rack;
|
||||
};
|
||||
|
||||
class DevNozzleSystemParser
|
||||
{
|
||||
public:
|
||||
static void ParseV1_0(const nlohmann::json& nozzletype_json, const nlohmann::json& diameter_json, DevNozzleSystem* system, std::optional<int> flag_e3d);
|
||||
static void ParseV2_0(const json& nozzle_json, DevNozzleSystem* system);
|
||||
static void ParseV2_0(const json& device_json, DevNozzleSystem* system);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -28,6 +28,10 @@ public:
|
||||
static int get_flag_bits(std::string str, int start, int count = 1);
|
||||
static int get_flag_bits(int num, int start, int count = 1, int base = 10);
|
||||
|
||||
// Extract the hex digit at position `pos` (0 = least-significant).
|
||||
// eg. get_hex_bits(16, 1, 10) = 1
|
||||
static int get_hex_bits(int num, int pos, int input_num_base = 10) { return get_flag_bits(num, pos * 4, 4, input_num_base); }
|
||||
|
||||
static float string_to_float(const std::string& str_value);
|
||||
|
||||
static std::string convertToIp(long long ip);
|
||||
|
||||
@@ -1,14 +1,24 @@
|
||||
#include "DevUtilBackend.h"
|
||||
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/BackgroundSlicingProcess.hpp"
|
||||
|
||||
#include "libslic3r/Preset.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
|
||||
std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase> DevUtilBackend::GetNozzleGroupResult(Slic3r::GUI::Plater* plater)
|
||||
{
|
||||
if (plater && plater->background_process().get_current_gcode_result()) {
|
||||
return plater->background_process().get_current_gcode_result()->nozzle_group_result;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static std::unordered_map<std::string, DevAmsType> s_ams_type_map = {
|
||||
{"0", DevAmsType::N3F},
|
||||
{"1", DevAmsType::N3S},
|
||||
|
||||
@@ -7,11 +7,13 @@
|
||||
#include "DevDefs.h"
|
||||
#include "DevFilaSystem.h"
|
||||
|
||||
#include "libslic3r/MultiNozzleUtils.hpp"
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
namespace GUI { class Plater; }
|
||||
|
||||
class DevUtilBackend
|
||||
{
|
||||
@@ -19,6 +21,12 @@ public:
|
||||
DevUtilBackend() = delete;
|
||||
|
||||
public:
|
||||
|
||||
// for rack: the slicer's per-filament -> logical-nozzle grouping for the current plate, read off
|
||||
// the post-slice GCodeProcessorResult (plater->background_process().get_current_gcode_result()).
|
||||
// Returns nullptr when there is no plater / no current result.
|
||||
static std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase> GetNozzleGroupResult(Slic3r::GUI::Plater* plater);
|
||||
|
||||
// for filament preset
|
||||
static std::optional<DevFilamentDryingPreset> GetFilamentDryingPreset(const std::string& fila_id);
|
||||
};
|
||||
|
||||
@@ -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 = std::make_shared<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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -613,6 +618,9 @@ MachineObject::~MachineObject()
|
||||
m_ctrl = nullptr;
|
||||
|
||||
|
||||
delete m_fila_switch;
|
||||
m_fila_switch = nullptr;
|
||||
|
||||
delete m_hms_system;
|
||||
m_hms_system = nullptr;
|
||||
|
||||
@@ -859,7 +867,11 @@ void MachineObject::clear_version_info()
|
||||
laser_version_info = DevFirmwareVersionInfo();
|
||||
cutting_module_version_info = DevFirmwareVersionInfo();
|
||||
extinguish_version_info = DevFirmwareVersionInfo();
|
||||
filatrack_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)
|
||||
@@ -872,6 +884,13 @@ 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);
|
||||
} else if (info.isFilaTrackSwitch()) {
|
||||
filatrack_version_info = info;
|
||||
}
|
||||
|
||||
module_vers.emplace(info.name, info);
|
||||
@@ -1554,7 +1573,7 @@ int MachineObject::check_resume_condition()
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
int MachineObject::command_ams_change_filament(bool load, std::string ams_id, std::string slot_id, int old_temp, int new_temp)
|
||||
int MachineObject::command_ams_change_filament(bool load, std::string ams_id, std::string slot_id, int old_temp, int new_temp, std::optional<int> extruder_id)
|
||||
{
|
||||
json j;
|
||||
try {
|
||||
@@ -1586,6 +1605,13 @@ int MachineObject::command_ams_change_filament(bool load, std::string ams_id, st
|
||||
j["print"]["slot_id"] = atoi(slot_id.c_str());
|
||||
}
|
||||
|
||||
// Filament Track Switch: route the load to the chosen extruder. Only present when the
|
||||
// caller supplied a value (FTS-installed+ready path), so every other caller is unchanged.
|
||||
if (extruder_id.has_value())
|
||||
{
|
||||
j["print"]["extruder_id"] = *extruder_id;
|
||||
}
|
||||
|
||||
} catch (const std::exception &) {}
|
||||
return this->publish_json(j);
|
||||
}
|
||||
@@ -1974,6 +2000,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();
|
||||
@@ -1992,6 +2022,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();
|
||||
@@ -2012,6 +2046,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);
|
||||
@@ -2038,6 +2077,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();
|
||||
@@ -2377,6 +2420,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;
|
||||
@@ -2894,6 +2938,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()) {
|
||||
@@ -2998,6 +3043,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()) {
|
||||
@@ -4080,6 +4127,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()) {
|
||||
@@ -4177,6 +4232,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())
|
||||
@@ -5002,6 +5065,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);
|
||||
@@ -5030,6 +5094,7 @@ void MachineObject::parse_new_info(json print)
|
||||
if (!fun2.empty()) {
|
||||
is_support_print_with_emmc = get_flag_bits_no_border(fun2, 0) == 1;
|
||||
is_support_remote_dry = (get_flag_bits_no_border(fun2, 5) == 1);
|
||||
is_support_check_track_switch_match_slice_printer = get_flag_bits_no_border(fun2, 19) == 1;
|
||||
}
|
||||
|
||||
/*aux*/
|
||||
@@ -5065,7 +5130,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); }
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <memory>
|
||||
#include <chrono>
|
||||
#include <unordered_set>
|
||||
#include <optional>
|
||||
#include <boost/thread.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#include "nlohmann/json.hpp"
|
||||
@@ -83,10 +84,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 +118,7 @@ private:
|
||||
DevExtderSystem* m_extder_system;
|
||||
DevNozzleSystem* m_nozzle_system;
|
||||
std::shared_ptr<DevFilaSystem> m_fila_system;
|
||||
DevFilaSwitch* m_fila_switch;
|
||||
DevFan* m_fan;
|
||||
DevBed * m_bed;
|
||||
DevStorage* m_storage;
|
||||
@@ -131,6 +135,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 +338,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
|
||||
|
||||
std::shared_ptr<DevFilaSystem> GetFilaSystem() const { return m_fila_system;}
|
||||
DevFilaSwitch* GetFilaSwitch() const { return m_fila_switch;}
|
||||
bool HasAms() const;
|
||||
|
||||
DevLamp* GetLamp() const { return m_lamp; }
|
||||
@@ -365,6 +379,7 @@ public:
|
||||
DevFirmwareVersionInfo laser_version_info;
|
||||
DevFirmwareVersionInfo cutting_module_version_info;
|
||||
DevFirmwareVersionInfo extinguish_version_info;
|
||||
DevFirmwareVersionInfo filatrack_version_info;
|
||||
std::map<std::string, DevFirmwareVersionInfo> module_vers;
|
||||
std::map<std::string, DevFirmwareVersionInfo> new_ver_list;
|
||||
bool m_new_ver_list_exist = false;
|
||||
@@ -621,6 +636,7 @@ public:
|
||||
// fun2
|
||||
bool is_support_print_with_emmc{false};
|
||||
bool is_support_remote_dry = false;
|
||||
bool is_support_check_track_switch_match_slice_printer{false};
|
||||
|
||||
bool installed_upgrade_kit{false};
|
||||
int bed_temperature_limit = -1;
|
||||
@@ -734,7 +750,7 @@ public:
|
||||
int check_resume_condition();
|
||||
// ams controls
|
||||
//int command_ams_switch(int tray_index, int old_temp = 210, int new_temp = 210);
|
||||
int command_ams_change_filament(bool load, std::string ams_id, std::string slot_id, int old_temp = 210, int new_temp = 210);
|
||||
int command_ams_change_filament(bool load, std::string ams_id, std::string slot_id, int old_temp = 210, int new_temp = 210, std::optional<int> extruder_id = std::nullopt);
|
||||
int command_ams_user_settings(bool start_read_opt, bool tray_read_opt, bool remain_flag = false);
|
||||
int command_ams_switch_filament(bool switch_filament);
|
||||
int command_ams_air_print_detect(bool air_print_detect);
|
||||
|
||||
@@ -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("#009688");
|
||||
|
||||
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("#009688");
|
||||
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("#009688"));
|
||||
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("#009688");
|
||||
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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1,9 +1,14 @@
|
||||
#include "DragDropPanel.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "Widgets/Label.hpp"
|
||||
#include "Widgets/StateColor.hpp"
|
||||
#include <slic3r/GUI/wxExtensions.hpp>
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
wxDEFINE_EVENT(wxEVT_DRAG_DROP_COMPLETED, wxCommandEvent);
|
||||
|
||||
struct CustomData
|
||||
{
|
||||
int filament_id;
|
||||
@@ -201,7 +206,7 @@ wxDragResult ColorDropTarget::OnData(wxCoord x, wxCoord y, wxDragResult def)
|
||||
/////////////// ColorDropTarget end ////////////////////////
|
||||
|
||||
|
||||
DragDropPanel::DragDropPanel(wxWindow *parent, const wxString &label, bool is_auto)
|
||||
DragDropPanel::DragDropPanel(wxWindow *parent, const wxString &label, bool is_auto, bool has_title, bool is_sub)
|
||||
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE)
|
||||
, m_is_auto(is_auto)
|
||||
{
|
||||
@@ -209,22 +214,31 @@ DragDropPanel::DragDropPanel(wxWindow *parent, const wxString &label, bool is_au
|
||||
|
||||
m_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
auto title_panel = new wxPanel(this);
|
||||
title_panel->SetBackgroundColour(0xEEEEEE);
|
||||
auto title_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
title_panel->SetSizer(title_sizer);
|
||||
if (has_title) {
|
||||
auto title_panel = new wxPanel(this);
|
||||
title_panel->SetBackgroundColour(is_sub ? wxColour(0xF8F8F8) : wxColour(0xEEEEEE));
|
||||
auto title_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
title_panel->SetSizer(title_sizer);
|
||||
|
||||
Label* static_text = new Label(this, label);
|
||||
static_text->SetFont(Label::Head_13);
|
||||
static_text->SetBackgroundColour(0xEEEEEE);
|
||||
m_title_label = new Label(this, label);
|
||||
m_title_label->SetFont(is_sub ? Label::Body_12 : Label::Head_13);
|
||||
if (is_sub)
|
||||
m_title_label->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#6B6B6B")));
|
||||
m_title_label->SetBackgroundColour(is_sub ? wxColour(0xF8F8F8) : wxColour(0xEEEEEE));
|
||||
|
||||
title_sizer->Add(static_text, 0, wxALIGN_CENTER | wxALL, FromDIP(5));
|
||||
title_sizer->Add(m_title_label, 0, wxALIGN_CENTER | wxALL, FromDIP(5));
|
||||
|
||||
m_sizer->Add(title_panel, 0, wxEXPAND);
|
||||
m_sizer->AddSpacer(10);
|
||||
m_sizer->Add(title_panel, 0, wxEXPAND);
|
||||
m_sizer->AddSpacer(10);
|
||||
}
|
||||
|
||||
m_grid_item_sizer = new wxGridSizer(0, 6, FromDIP(8), FromDIP(8)); // row = 0, col = 3, 10 10 is space
|
||||
m_sizer->Add(m_grid_item_sizer, 1, wxEXPAND | wxALL, FromDIP(8));
|
||||
if (is_sub) {
|
||||
m_grid_item_sizer = new wxGridSizer(0, 3, FromDIP(6), FromDIP(6));
|
||||
m_sizer->Add(m_grid_item_sizer, 0, wxEXPAND);
|
||||
} else {
|
||||
m_grid_item_sizer = new wxGridSizer(0, 6, FromDIP(8), FromDIP(8)); // row = 0, col = 3, 10 10 is space
|
||||
m_sizer->Add(m_grid_item_sizer, 1, wxEXPAND | wxALL, FromDIP(8));
|
||||
}
|
||||
|
||||
// set droptarget
|
||||
auto drop_target = new ColorDropTarget(this);
|
||||
@@ -242,20 +256,35 @@ void DragDropPanel::AddColorBlock(const wxColour &color, const std::string &type
|
||||
m_grid_item_sizer->Add(panel, 0);
|
||||
m_filament_blocks.push_back(panel);
|
||||
if (update_ui) {
|
||||
m_filament_blocks.front()->Refresh(); // FIX BUG: STUDIO-8467
|
||||
Freeze();
|
||||
|
||||
m_grid_item_sizer->Layout();
|
||||
Layout();
|
||||
Fit();
|
||||
GetParent()->GetParent()->Layout();
|
||||
GetParent()->GetParent()->Fit();
|
||||
m_filament_blocks.front()->Refresh(); // FIX BUG: STUDIO-8467
|
||||
|
||||
Thaw();
|
||||
NotifyDragDropCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
void DragDropPanel::RemoveColorBlock(ColorPanel *panel, bool update_ui)
|
||||
{
|
||||
m_sizer->Detach(panel);
|
||||
m_grid_item_sizer->Detach(panel);
|
||||
panel->Destroy();
|
||||
m_filament_blocks.erase(std::remove(m_filament_blocks.begin(), m_filament_blocks.end(), panel), m_filament_blocks.end());
|
||||
if (update_ui) {
|
||||
Freeze();
|
||||
|
||||
Layout();
|
||||
Fit();
|
||||
GetParent()->GetParent()->Layout();
|
||||
GetParent()->GetParent()->Fit();
|
||||
|
||||
Thaw();
|
||||
NotifyDragDropCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,6 +299,15 @@ void DragDropPanel::DoDragDrop(ColorPanel *panel, const wxColour &color, const s
|
||||
}
|
||||
}
|
||||
|
||||
void DragDropPanel::UpdateLabel(const wxString &label)
|
||||
{
|
||||
if (m_title_label) {
|
||||
m_title_label->SetLabel(label);
|
||||
m_title_label->Refresh();
|
||||
Layout();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<int> DragDropPanel::GetAllFilaments() const
|
||||
{
|
||||
std::vector<int> filaments;
|
||||
@@ -287,4 +325,327 @@ std::vector<int> DragDropPanel::GetAllFilaments() const
|
||||
return filaments;
|
||||
}
|
||||
|
||||
void DragDropPanel::NotifyDragDropCompleted()
|
||||
{
|
||||
wxCommandEvent event(wxEVT_DRAG_DROP_COMPLETED);
|
||||
event.SetEventObject(this);
|
||||
|
||||
wxWindow *parent = GetParent();
|
||||
while (parent) {
|
||||
auto name = parent->GetName();
|
||||
if (name == wxT("FilamentMapManualPanel")) {
|
||||
parent->GetEventHandler()->ProcessEvent(event);
|
||||
break;
|
||||
}
|
||||
parent = parent->GetParent();
|
||||
}
|
||||
}
|
||||
|
||||
class SeparatedColorDropTarget : public wxDropTarget
|
||||
{
|
||||
public:
|
||||
SeparatedColorDropTarget(SeparatedDragDropPanel *panel) : wxDropTarget(), m_panel(panel)
|
||||
{
|
||||
m_data = new ColorDataObject();
|
||||
SetDataObject(m_data);
|
||||
}
|
||||
|
||||
virtual wxDragResult OnData(wxCoord x, wxCoord y, wxDragResult def) override;
|
||||
virtual bool OnDrop(wxCoord x, wxCoord y) override { return true; }
|
||||
|
||||
private:
|
||||
SeparatedDragDropPanel *m_panel;
|
||||
ColorDataObject *m_data;
|
||||
};
|
||||
|
||||
wxDragResult SeparatedColorDropTarget::OnData(wxCoord x, wxCoord y, wxDragResult def)
|
||||
{
|
||||
if (!GetData()) return wxDragNone;
|
||||
|
||||
m_panel->AddColorBlock(m_data->GetColor(), m_data->GetType(), m_data->GetFilament(), false);
|
||||
return wxDragCopy;
|
||||
}
|
||||
|
||||
SeparatedDragDropPanel::SeparatedDragDropPanel(wxWindow *parent, const wxString &label, bool use_separation) : wxPanel(parent), m_use_separation(use_separation)
|
||||
{
|
||||
SetBackgroundColour(0xF8F8F8);
|
||||
|
||||
m_main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
auto title_panel = new wxPanel(this);
|
||||
title_panel->SetBackgroundColour(0xEEEEEE);
|
||||
auto title_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
title_panel->SetSizer(title_sizer);
|
||||
|
||||
m_label = new Label(title_panel, label);
|
||||
m_label->SetFont(Label::Head_13);
|
||||
m_label->SetBackgroundColour(0xEEEEEE);
|
||||
|
||||
title_sizer->Add(m_label, 0, wxALIGN_CENTER | wxALL, FromDIP(5));
|
||||
|
||||
m_main_sizer->Add(title_panel, 0, wxEXPAND);
|
||||
m_main_sizer->AddSpacer(10);
|
||||
|
||||
m_content_panel = new wxPanel(this);
|
||||
m_content_panel->SetBackgroundColour(0xF8F8F8);
|
||||
m_content_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
m_content_panel->SetSizer(m_content_sizer);
|
||||
|
||||
m_high_flow_panel = new DragDropPanel(m_content_panel, _L("High Flow"), false, true, true);
|
||||
m_standard_panel = new DragDropPanel(m_content_panel, _L("Standard"), false, true, true);
|
||||
m_unified_panel = new DragDropPanel(m_content_panel, wxEmptyString, false, false);
|
||||
|
||||
m_high_flow_panel->SetBackgroundColour(0xF8F8F8);
|
||||
m_standard_panel->SetBackgroundColour(0xF8F8F8);
|
||||
m_unified_panel->SetBackgroundColour(0xF8F8F8);
|
||||
m_unified_panel->SetMinSize({FromDIP(260), -1});
|
||||
|
||||
m_main_sizer->Add(m_content_panel, 1, wxEXPAND);
|
||||
|
||||
UpdateLayout();
|
||||
|
||||
auto drop_target = new SeparatedColorDropTarget(this);
|
||||
SetDropTarget(drop_target);
|
||||
|
||||
SetSizer(m_main_sizer);
|
||||
Layout();
|
||||
wxGetApp().UpdateDarkUIWin(this);
|
||||
}
|
||||
|
||||
void SeparatedDragDropPanel::UpdateLayout()
|
||||
{
|
||||
m_content_sizer->Clear(false);
|
||||
|
||||
if (m_use_separation) {
|
||||
m_unified_panel->Hide();
|
||||
m_high_flow_panel->Show();
|
||||
m_standard_panel->Show();
|
||||
|
||||
wxSize content_size = m_content_panel->GetSize();
|
||||
int panel_width = (content_size.GetWidth() - FromDIP(1) - FromDIP(8)) / 2;
|
||||
if (panel_width > 0) {
|
||||
m_high_flow_panel->SetMinSize(wxSize(panel_width, -1));
|
||||
m_standard_panel->SetMinSize(wxSize(panel_width, -1));
|
||||
}
|
||||
|
||||
m_content_sizer->Add(m_high_flow_panel, 1, wxEXPAND | wxLEFT, FromDIP(8));
|
||||
|
||||
// Create the separator once and re-attach it on relayout; Clear(false) above only
|
||||
// detaches, so allocating here would leak a window per relayout.
|
||||
if (!m_separator) {
|
||||
m_separator = new wxPanel(m_content_panel, wxID_ANY);
|
||||
m_separator->SetBackgroundColour(StateColor::darkModeColorFor(wxColour(0xCE, 0xCE, 0xCE)));
|
||||
m_separator->SetMinSize(wxSize(FromDIP(1), -1));
|
||||
}
|
||||
m_separator->Show();
|
||||
m_content_sizer->Add(m_separator, 0, wxEXPAND | wxALL, FromDIP(4));
|
||||
|
||||
m_content_sizer->Add(m_standard_panel, 1, wxEXPAND | wxRIGHT, FromDIP(8));
|
||||
} else {
|
||||
m_high_flow_panel->Hide();
|
||||
m_standard_panel->Hide();
|
||||
if (m_separator)
|
||||
m_separator->Hide();
|
||||
m_unified_panel->Show();
|
||||
|
||||
m_content_sizer->Add(m_unified_panel, 1, wxEXPAND);
|
||||
}
|
||||
m_content_sizer->Layout();
|
||||
Layout();
|
||||
Fit();
|
||||
if (GetParent()) {
|
||||
GetParent()->Layout();
|
||||
if (GetParent()->GetParent()) {
|
||||
GetParent()->GetParent()->Layout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SeparatedDragDropPanel::UpdateLabel(const wxString &label)
|
||||
{
|
||||
if (m_label) {
|
||||
m_label->SetLabel(label);
|
||||
m_label->Refresh();
|
||||
Layout();
|
||||
}
|
||||
}
|
||||
|
||||
void SeparatedDragDropPanel::SetUseSeparation(bool use_separation)
|
||||
{
|
||||
if (m_use_separation != use_separation) {
|
||||
m_use_separation = use_separation;
|
||||
|
||||
if (use_separation) {
|
||||
auto blocks = m_unified_panel->get_filament_blocks();
|
||||
for (auto &block : blocks) {
|
||||
m_standard_panel->AddColorBlock(block->GetColor(), block->GetType(), block->GetFilamentId(), false);
|
||||
m_unified_panel->RemoveColorBlock(block, false);
|
||||
}
|
||||
} else {
|
||||
auto high_flow_blocks = m_high_flow_panel->get_filament_blocks();
|
||||
auto standard_blocks = m_standard_panel->get_filament_blocks();
|
||||
|
||||
for (auto &block : high_flow_blocks) {
|
||||
m_unified_panel->AddColorBlock(block->GetColor(), block->GetType(), block->GetFilamentId(), false);
|
||||
m_high_flow_panel->RemoveColorBlock(block, false);
|
||||
}
|
||||
|
||||
for (auto &block : standard_blocks) {
|
||||
m_unified_panel->AddColorBlock(block->GetColor(), block->GetType(), block->GetFilamentId(), false);
|
||||
m_standard_panel->RemoveColorBlock(block, false);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateLayout();
|
||||
}
|
||||
}
|
||||
|
||||
void SeparatedDragDropPanel::AddColorBlock(const wxColour &color, const std::string &type, int filament_id, bool is_high_flow, bool update_ui)
|
||||
{
|
||||
if (m_use_separation) {
|
||||
if (is_high_flow) {
|
||||
m_high_flow_panel->AddColorBlock(color, type, filament_id, update_ui);
|
||||
} else {
|
||||
m_standard_panel->AddColorBlock(color, type, filament_id, update_ui);
|
||||
}
|
||||
|
||||
if (update_ui) {
|
||||
CallAfter([this]() {
|
||||
Layout();
|
||||
GetParent()->Layout();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
m_unified_panel->AddColorBlock(color, type, filament_id, update_ui);
|
||||
}
|
||||
}
|
||||
|
||||
void SeparatedDragDropPanel::RemoveColorBlock(ColorPanel *panel, bool update_ui)
|
||||
{
|
||||
auto high_flow_blocks = m_high_flow_panel->get_filament_blocks();
|
||||
auto standard_blocks = m_standard_panel->get_filament_blocks();
|
||||
auto unified_blocks = m_unified_panel->get_filament_blocks();
|
||||
|
||||
if (std::find(high_flow_blocks.begin(), high_flow_blocks.end(), panel) != high_flow_blocks.end()) {
|
||||
m_high_flow_panel->RemoveColorBlock(panel, update_ui);
|
||||
} else if (std::find(standard_blocks.begin(), standard_blocks.end(), panel) != standard_blocks.end()) {
|
||||
m_standard_panel->RemoveColorBlock(panel, update_ui);
|
||||
} else if (std::find(unified_blocks.begin(), unified_blocks.end(), panel) != unified_blocks.end()) {
|
||||
m_unified_panel->RemoveColorBlock(panel, update_ui);
|
||||
}
|
||||
|
||||
if (update_ui && m_use_separation) {
|
||||
CallAfter([this]() {
|
||||
Layout();
|
||||
GetParent()->Layout();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<int> SeparatedDragDropPanel::GetAllFilaments() const
|
||||
{
|
||||
if (m_use_separation) {
|
||||
auto high_flow = m_high_flow_panel->GetAllFilaments();
|
||||
auto standard = m_standard_panel->GetAllFilaments();
|
||||
|
||||
std::vector<int> result;
|
||||
result.insert(result.end(), high_flow.begin(), high_flow.end());
|
||||
result.insert(result.end(), standard.begin(), standard.end());
|
||||
return result;
|
||||
} else {
|
||||
return m_unified_panel->GetAllFilaments();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<int> SeparatedDragDropPanel::GetHighFlowFilaments() const
|
||||
{
|
||||
if (m_use_separation) {
|
||||
return m_high_flow_panel->GetAllFilaments();
|
||||
}
|
||||
auto nozzle_volumes = wxGetApp().preset_bundle->project_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type");
|
||||
const int right_eid = 1;
|
||||
if (nozzle_volumes->values.size() > right_eid) {
|
||||
int volume_type = nozzle_volumes->values[right_eid];
|
||||
if (volume_type == static_cast<int>(NozzleVolumeType::nvtHighFlow)) {
|
||||
return m_unified_panel->GetAllFilaments();
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<int> SeparatedDragDropPanel::GetStandardFilaments() const
|
||||
{
|
||||
if (m_use_separation) {
|
||||
return m_standard_panel->GetAllFilaments();
|
||||
}
|
||||
auto nozzle_volumes = wxGetApp().preset_bundle->project_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type");
|
||||
const int right_eid = 1;
|
||||
if (nozzle_volumes->values.size() > right_eid) {
|
||||
int volume_type = nozzle_volumes->values[right_eid];
|
||||
if (volume_type == static_cast<int>(NozzleVolumeType::nvtStandard)) {
|
||||
return m_unified_panel->GetAllFilaments();
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<int> SeparatedDragDropPanel::GetTPUHighFlowFilaments() const
|
||||
{
|
||||
if (m_use_separation) {
|
||||
return {};
|
||||
}
|
||||
auto nozzle_volumes = wxGetApp().preset_bundle->project_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type");
|
||||
const int right_eid = 1;
|
||||
if (nozzle_volumes->values.size() > right_eid) {
|
||||
int volume_type = nozzle_volumes->values[right_eid];
|
||||
if (volume_type == static_cast<int>(NozzleVolumeType::nvtTPUHighFlow)) {
|
||||
return m_unified_panel->GetAllFilaments();
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<ColorPanel *> SeparatedDragDropPanel::get_filament_blocks() const
|
||||
{
|
||||
if (m_use_separation) {
|
||||
auto high_flow = m_high_flow_panel->get_filament_blocks();
|
||||
auto standard = m_standard_panel->get_filament_blocks();
|
||||
|
||||
std::vector<ColorPanel *> result;
|
||||
result.insert(result.end(), high_flow.begin(), high_flow.end());
|
||||
result.insert(result.end(), standard.begin(), standard.end());
|
||||
return result;
|
||||
} else {
|
||||
return m_unified_panel->get_filament_blocks();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ColorPanel *> SeparatedDragDropPanel::get_high_flow_blocks() const
|
||||
{
|
||||
return m_high_flow_panel->get_filament_blocks();
|
||||
}
|
||||
|
||||
std::vector<ColorPanel *> SeparatedDragDropPanel::get_standard_blocks() const
|
||||
{
|
||||
return m_standard_panel->get_filament_blocks();
|
||||
}
|
||||
|
||||
void SeparatedDragDropPanel::ClearAllBlocks()
|
||||
{
|
||||
auto high_flow_blocks = m_high_flow_panel->get_filament_blocks();
|
||||
for (auto &block : high_flow_blocks) {
|
||||
m_high_flow_panel->RemoveColorBlock(block, false);
|
||||
}
|
||||
|
||||
auto standard_blocks = m_standard_panel->get_filament_blocks();
|
||||
for (auto &block : standard_blocks) {
|
||||
m_standard_panel->RemoveColorBlock(block, false);
|
||||
}
|
||||
|
||||
auto unified_blocks = m_unified_panel->get_filament_blocks();
|
||||
for (auto &block : unified_blocks) {
|
||||
m_unified_panel->RemoveColorBlock(block, false);
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_Utils.hpp"
|
||||
#include "Widgets/Label.hpp"
|
||||
|
||||
#include <wx/simplebook.h>
|
||||
#include <wx/dialog.h>
|
||||
@@ -13,17 +14,20 @@
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
wxDECLARE_EVENT(wxEVT_DRAG_DROP_COMPLETED, wxCommandEvent);
|
||||
|
||||
wxColor Hex2Color(const std::string& str);
|
||||
|
||||
class ColorPanel;
|
||||
class DragDropPanel : public wxPanel
|
||||
{
|
||||
public:
|
||||
DragDropPanel(wxWindow *parent, const wxString &label, bool is_auto);
|
||||
DragDropPanel(wxWindow *parent, const wxString &label, bool is_auto, bool has_title = true, bool is_sub = false);
|
||||
|
||||
void AddColorBlock(const wxColour &color, const std::string &type, int filament_id, bool update_ui = true);
|
||||
void RemoveColorBlock(ColorPanel *panel, bool update_ui = true);
|
||||
void DoDragDrop(ColorPanel *panel, const wxColour &color, const std::string &type, int filament_id);
|
||||
void UpdateLabel(const wxString &label);
|
||||
|
||||
std::vector<int> GetAllFilaments() const;
|
||||
|
||||
@@ -35,9 +39,12 @@ public:
|
||||
private:
|
||||
wxBoxSizer *m_sizer;
|
||||
wxGridSizer *m_grid_item_sizer;
|
||||
Label *m_title_label = nullptr;
|
||||
bool m_is_auto;
|
||||
|
||||
std::vector<ColorPanel *> m_filament_blocks;
|
||||
|
||||
void NotifyDragDropCompleted();
|
||||
private:
|
||||
bool m_is_draging = false;
|
||||
};
|
||||
@@ -64,6 +71,49 @@ private:
|
||||
int m_filament_id;
|
||||
|
||||
};
|
||||
|
||||
// Drop zone for one extruder that can split into two sub-zones (High Flow / Standard)
|
||||
// when the extruder mixes nozzle volume types (Hybrid flow), and collapses back to a
|
||||
// single unified zone otherwise.
|
||||
class SeparatedDragDropPanel : public wxPanel
|
||||
{
|
||||
public:
|
||||
SeparatedDragDropPanel(wxWindow *parent, const wxString &label, bool use_separation = false);
|
||||
|
||||
void AddColorBlock(const wxColour &color, const std::string &type, int filament_id, bool is_high_flow = false, bool update_ui = true);
|
||||
void RemoveColorBlock(ColorPanel *panel, bool update_ui = true);
|
||||
|
||||
std::vector<int> GetAllFilaments() const;
|
||||
std::vector<int> GetHighFlowFilaments() const;
|
||||
std::vector<int> GetStandardFilaments() const;
|
||||
std::vector<int> GetTPUHighFlowFilaments() const;
|
||||
|
||||
std::vector<ColorPanel *> get_filament_blocks() const;
|
||||
std::vector<ColorPanel *> get_high_flow_blocks() const;
|
||||
std::vector<ColorPanel *> get_standard_blocks() const;
|
||||
|
||||
void SetUseSeparation(bool use_separation);
|
||||
bool IsUseSeparation() const { return m_use_separation; }
|
||||
void ClearAllBlocks();
|
||||
void UpdateLabel(const wxString &label);
|
||||
|
||||
private:
|
||||
void UpdateLayout();
|
||||
|
||||
wxBoxSizer *m_main_sizer;
|
||||
wxPanel *m_content_panel;
|
||||
wxBoxSizer *m_content_sizer;
|
||||
wxPanel *m_separator = nullptr;
|
||||
Label *m_label;
|
||||
|
||||
DragDropPanel *m_high_flow_panel;
|
||||
DragDropPanel *m_standard_panel;
|
||||
|
||||
DragDropPanel *m_unified_panel;
|
||||
|
||||
bool m_use_separation;
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
#endif /* slic3r_DragDropPanel_hpp_ */
|
||||
|
||||
@@ -174,6 +174,9 @@ FilamentGroupPopup::FilamentGroupPopup(wxWindow *parent) : PopupWindow(parent, w
|
||||
button_labels[idx]->Bind(wxEVT_LEAVE_WINDOW, [this](auto &) { UpdateButtonStatus(); });
|
||||
}
|
||||
|
||||
// Smart filament assign section
|
||||
MakeSmartFilamentSection(top_sizer, horizontal_margin, vertical_padding);
|
||||
|
||||
{
|
||||
wxBoxSizer *button_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
// ORCA Unified hyperlinks
|
||||
@@ -257,6 +260,7 @@ void FilamentGroupPopup::Init()
|
||||
SetFilamentMapMode(m_mode);
|
||||
}
|
||||
|
||||
UpdateSmartFilamentSection();
|
||||
UpdateButtonStatus();
|
||||
GUI::wxGetApp().UpdateDarkUIWin(this);
|
||||
}
|
||||
@@ -408,4 +412,65 @@ void FilamentGroupPopup::UpdateButtonStatus(int hover_idx)
|
||||
Fit();
|
||||
}
|
||||
|
||||
void FilamentGroupPopup::MakeSmartFilamentSection(wxSizer *top_sizer, int horizontal_margin, int vertical_padding)
|
||||
{
|
||||
m_smart_filament_panel = new StaticBox(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0);
|
||||
m_smart_filament_panel->SetCornerRadius(FromDIP(4));
|
||||
m_smart_filament_panel->SetBorderWidth(FromDIP(1));
|
||||
m_smart_filament_panel->SetBorderColor(wxColour("#CECECE"));
|
||||
m_smart_filament_panel->SetBackgroundColor(StateColor(std::pair<wxColour, int>(wxColour("#F8F8F8"), StateColor::Normal)));
|
||||
|
||||
auto *label = new Label(m_smart_filament_panel, _L("Enable smart filament assign: Assign one filament to multiple nozzles to maximize savings"));
|
||||
label->SetFont(Label::Body_12);
|
||||
label->SetForegroundColour(GreyColor);
|
||||
label->SetBackgroundColour(wxColour("#F8F8F8"));
|
||||
label->Wrap(FromDIP(240));
|
||||
|
||||
m_smart_filament_switch = new SwitchButton(m_smart_filament_panel);
|
||||
m_smart_filament_switch->Bind(wxEVT_TOGGLEBUTTON, &FilamentGroupPopup::OnSmartFilamentToggle, this);
|
||||
#ifdef __WXOSX__
|
||||
// wxEVT_TOGGLEBUTTON event not handled well by PopupWindow on MacOS
|
||||
// we bind a wxEVT_LEFT_DOWN event as a workaround
|
||||
m_smart_filament_switch->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &) {
|
||||
wxCommandEvent evt(wxEVT_TOGGLEBUTTON);
|
||||
evt.SetInt(!m_smart_filament_switch->GetValue());
|
||||
m_smart_filament_switch->Command(evt);
|
||||
});
|
||||
#endif
|
||||
|
||||
auto *panel_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
panel_sizer->Add(label, 1, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(10));
|
||||
panel_sizer->Add(m_smart_filament_switch, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(10));
|
||||
m_smart_filament_panel->SetSizer(panel_sizer);
|
||||
|
||||
top_sizer->Add(m_smart_filament_panel, 0, wxEXPAND | wxLEFT | wxRIGHT, horizontal_margin);
|
||||
m_smart_filament_spacer = top_sizer->AddSpacer(vertical_padding);
|
||||
|
||||
// Hidden by default; shown in Init() when the filament track switch is ready
|
||||
m_smart_filament_panel->Show(false);
|
||||
m_smart_filament_spacer->Show(false);
|
||||
}
|
||||
|
||||
void FilamentGroupPopup::OnSmartFilamentToggle(wxCommandEvent &event)
|
||||
{
|
||||
auto &config = wxGetApp().preset_bundle->project_config;
|
||||
auto *dynamic_filament = dynamic_cast<ConfigOptionBool *>(config.option("enable_filament_dynamic_map"));
|
||||
if (dynamic_filament) { dynamic_filament->value = m_smart_filament_switch->GetValue(); }
|
||||
plater_ref->update();
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void FilamentGroupPopup::UpdateSmartFilamentSection()
|
||||
{
|
||||
bool show = wxGetApp().sidebar().is_fila_switch_ready();
|
||||
m_smart_filament_panel->Show(show);
|
||||
m_smart_filament_spacer->Show(show);
|
||||
|
||||
if (show) {
|
||||
auto &config = wxGetApp().preset_bundle->project_config;
|
||||
auto *dynamic_filament = dynamic_cast<ConfigOptionBool *>(config.option("enable_filament_dynamic_map"));
|
||||
if (dynamic_filament) { m_smart_filament_switch->SetValue(dynamic_filament->value); }
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
@@ -3,10 +3,13 @@
|
||||
|
||||
#include <wx/bitmap.h>
|
||||
#include <wx/bmpbuttn.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/timer.h>
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "Widgets/PopupWindow.hpp"
|
||||
#include "Widgets/Label.hpp"
|
||||
#include "Widgets/SwitchButton.hpp"
|
||||
#include "Widgets/StaticBox.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
@@ -42,6 +45,10 @@ private:
|
||||
void Init();
|
||||
void UpdateButtonStatus(int hover_idx = -1);
|
||||
void DrawRoundedCorner(int radius);
|
||||
|
||||
void MakeSmartFilamentSection(wxSizer *top_sizer, int horizontal_margin, int vertical_padding);
|
||||
void UpdateSmartFilamentSection();
|
||||
void OnSmartFilamentToggle(wxCommandEvent &event);
|
||||
private:
|
||||
FilamentMapMode GetFilamentMapMode() const;
|
||||
void SetFilamentMapMode(const FilamentMapMode mode);
|
||||
@@ -75,6 +82,10 @@ private:
|
||||
wxStaticText *wiki_link;
|
||||
wxStaticText *video_link;
|
||||
|
||||
StaticBox *m_smart_filament_panel{nullptr};
|
||||
wxSizerItem *m_smart_filament_spacer{nullptr};
|
||||
SwitchButton *m_smart_filament_switch{nullptr};
|
||||
|
||||
PartPlate* partplate_ref{ nullptr };
|
||||
Plater* plater_ref{ nullptr };
|
||||
};
|
||||
|
||||
@@ -36,9 +36,78 @@ static std::vector<int> get_applied_map(DynamicConfig& proj_config, const Plater
|
||||
return plater_ref->get_global_filament_map();
|
||||
}
|
||||
|
||||
static std::vector<int> get_applied_volume_map(DynamicConfig& proj_config, const Plater* plater_ref, const PartPlate* partplate_ref, const bool sync_plate)
|
||||
{
|
||||
if (sync_plate)
|
||||
return partplate_ref->get_real_filament_volume_maps(proj_config);
|
||||
return plater_ref->get_global_filament_volume_map();
|
||||
}
|
||||
|
||||
extern std::string& get_left_extruder_unprintable_text();
|
||||
extern std::string& get_right_extruder_unprintable_text();
|
||||
|
||||
// Orca: minimal smart-filament toggle. When a filament track switch is ready every AMS filament is
|
||||
// reachable from both nozzles, so one filament can be assigned to multiple nozzles to maximize
|
||||
// savings. The checkbox drives the enable_filament_dynamic_map project flag.
|
||||
class SmartFilamentPanel : public wxPanel
|
||||
{
|
||||
static constexpr int spacing = 20;
|
||||
|
||||
public:
|
||||
SmartFilamentPanel(wxWindow *parent) : wxPanel(parent)
|
||||
{
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
wxBoxSizer *main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
main_sizer->AddSpacer(FromDIP(spacing));
|
||||
|
||||
auto *separator = new wxPanel(this);
|
||||
separator->SetBackgroundColour(wxColour("#EEEEEE"));
|
||||
main_sizer->Add(separator, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(15));
|
||||
|
||||
main_sizer->AddSpacer(FromDIP(spacing));
|
||||
|
||||
m_smart_filament_checkbox = new CheckBox(this);
|
||||
if (auto *opt = enable_filament_dynamic_map())
|
||||
m_smart_filament_checkbox->SetValue(opt->value);
|
||||
m_smart_filament_checkbox->Bind(wxEVT_TOGGLEBUTTON, &SmartFilamentPanel::on_smart_filament_checkbox, this);
|
||||
|
||||
auto *label = new Label(this, _L("Enable smart filament assign: Assign one filament to multiple nozzles to maximize savings"));
|
||||
label->SetFont(Label::Body_12);
|
||||
|
||||
// Orca: dropped the vendor "Learn more" tracking link (no Orca help page for this feature).
|
||||
|
||||
auto *smart_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
smart_sizer->Add(m_smart_filament_checkbox, 0, wxALIGN_CENTER_VERTICAL);
|
||||
smart_sizer->Add(label, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, FromDIP(3));
|
||||
|
||||
main_sizer->Add(smart_sizer, 0, wxLEFT | wxRIGHT | wxBOTTOM, FromDIP(15));
|
||||
|
||||
SetSizer(main_sizer);
|
||||
Layout();
|
||||
Fit();
|
||||
wxGetApp().UpdateDarkUIWin(this);
|
||||
}
|
||||
|
||||
private:
|
||||
static ConfigOptionBool *enable_filament_dynamic_map()
|
||||
{
|
||||
auto &config = wxGetApp().preset_bundle->project_config;
|
||||
return dynamic_cast<ConfigOptionBool *>(config.option("enable_filament_dynamic_map"));
|
||||
}
|
||||
|
||||
void on_smart_filament_checkbox(wxCommandEvent &event)
|
||||
{
|
||||
if (auto *opt = enable_filament_dynamic_map())
|
||||
opt->value = m_smart_filament_checkbox->GetValue();
|
||||
wxGetApp().plater()->update();
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
private:
|
||||
CheckBox *m_smart_filament_checkbox{nullptr};
|
||||
};
|
||||
|
||||
|
||||
bool try_pop_up_before_slice(bool is_slice_all, Plater* plater_ref, PartPlate* partplate_ref, bool force_pop_up)
|
||||
{
|
||||
@@ -61,7 +130,9 @@ bool try_pop_up_before_slice(bool is_slice_all, Plater* plater_ref, PartPlate* p
|
||||
std::vector<std::string> filament_types = full_config.option<ConfigOptionStrings>("filament_type")->values;
|
||||
FilamentMapMode applied_mode = get_applied_map_mode(full_config, plater_ref,partplate_ref, sync_plate);
|
||||
std::vector<int> applied_maps = get_applied_map(full_config, plater_ref, partplate_ref, sync_plate);
|
||||
std::vector<int> applied_volume_maps = get_applied_volume_map(full_config, plater_ref, partplate_ref, sync_plate);
|
||||
applied_maps.resize(filament_colors.size(), 1);
|
||||
applied_volume_maps.resize(filament_colors.size(), 0);
|
||||
|
||||
if (!force_pop_up && applied_mode != fmmManual)
|
||||
return true;
|
||||
@@ -79,6 +150,7 @@ bool try_pop_up_before_slice(bool is_slice_all, Plater* plater_ref, PartPlate* p
|
||||
filament_colors,
|
||||
filament_types,
|
||||
applied_maps,
|
||||
applied_volume_maps,
|
||||
filament_lists,
|
||||
applied_mode,
|
||||
plater_ref->get_machine_sync_status(),
|
||||
@@ -90,25 +162,32 @@ bool try_pop_up_before_slice(bool is_slice_all, Plater* plater_ref, PartPlate* p
|
||||
if (ret == wxID_OK) {
|
||||
FilamentMapMode new_mode = map_dlg.get_mode();
|
||||
std::vector<int> new_maps = map_dlg.get_filament_maps();
|
||||
std::vector<int> new_volume_maps = map_dlg.get_filament_volume_maps();
|
||||
if (sync_plate) {
|
||||
if (is_slice_all) {
|
||||
auto plate_list = plater_ref->get_partplate_list().get_plate_list();
|
||||
for (int i = 0; i < plate_list.size(); ++i) {
|
||||
plate_list[i]->set_filament_map_mode(new_mode);
|
||||
if(new_mode == fmmManual)
|
||||
if (new_mode == fmmManual) {
|
||||
plate_list[i]->set_filament_maps(new_maps);
|
||||
plate_list[i]->set_filament_volume_maps(new_volume_maps);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
partplate_ref->set_filament_map_mode(new_mode);
|
||||
if (new_mode == fmmManual)
|
||||
if (new_mode == fmmManual) {
|
||||
partplate_ref->set_filament_maps(new_maps);
|
||||
partplate_ref->set_filament_volume_maps(new_volume_maps);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
plater_ref->set_global_filament_map_mode(new_mode);
|
||||
if (new_mode == fmmManual)
|
||||
if (new_mode == fmmManual) {
|
||||
plater_ref->set_global_filament_map(new_maps);
|
||||
plater_ref->set_global_filament_volume_map(new_volume_maps);
|
||||
}
|
||||
}
|
||||
plater_ref->update();
|
||||
// check whether able to slice, if not, return false
|
||||
@@ -124,21 +203,35 @@ FilamentMapDialog::FilamentMapDialog(wxWindow *parent,
|
||||
const std::vector<std::string> &filament_color,
|
||||
const std::vector<std::string> &filament_type,
|
||||
const std::vector<int> &filament_map,
|
||||
const std::vector<int> &filament_volume_map,
|
||||
const std::vector<int> &filaments,
|
||||
const FilamentMapMode mode,
|
||||
bool machine_synced,
|
||||
bool show_default,
|
||||
bool with_checkbox)
|
||||
: wxDialog(parent, wxID_ANY, _L("Filament grouping"), wxDefaultPosition, wxDefaultSize,wxDEFAULT_DIALOG_STYLE), m_filament_color(filament_color), m_filament_type(filament_type), m_filament_map(filament_map)
|
||||
: wxDialog(parent, wxID_ANY, _L("Filament grouping"), wxDefaultPosition, wxDefaultSize,wxDEFAULT_DIALOG_STYLE)
|
||||
, m_filament_color(filament_color)
|
||||
, m_filament_type(filament_type)
|
||||
, m_filament_map(filament_map)
|
||||
, m_filament_volume_map(filament_volume_map)
|
||||
{
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
|
||||
SetMinSize(wxSize(FromDIP(580), -1));
|
||||
SetMaxSize(wxSize(FromDIP(580), -1));
|
||||
|
||||
// Orca: when a filament track switch is ready, every AMS filament reaches both nozzles, so the
|
||||
// Match/Convenience sub-mode is dropped and Auto is presented purely as a filament-saving mode.
|
||||
// Orca has no fmmAutoForQuality, so "only saving" collapses to "switch ready" and the remaining
|
||||
// auto mode set is { fmmAutoForFlush }.
|
||||
m_fila_switch_ready = wxGetApp().sidebar().is_fila_switch_ready();
|
||||
const bool only_saving_mode = m_fila_switch_ready;
|
||||
const bool auto_match_available = machine_synced && !m_fila_switch_ready;
|
||||
|
||||
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;
|
||||
@@ -148,7 +241,7 @@ FilamentMapDialog::FilamentMapDialog(wxWindow *parent,
|
||||
|
||||
wxBoxSizer *mode_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
m_auto_btn = new CapsuleButton(this, PageType::ptAuto, _L("Auto"), false);
|
||||
m_auto_btn = new CapsuleButton(this, PageType::ptAuto, only_saving_mode ? _L("Fila Saving") : _L("Auto"), false);
|
||||
m_manual_btn = new CapsuleButton(this, PageType::ptManual, _L("Custom"), false);
|
||||
if (show_default)
|
||||
m_default_btn = new CapsuleButton(this, PageType::ptDefault, _L("Same as Global"), true);
|
||||
@@ -167,12 +260,27 @@ FilamentMapDialog::FilamentMapDialog(wxWindow *parent,
|
||||
|
||||
auto panel_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
// Orca: fall back to the saving mode whenever Match is unavailable, which now also covers the
|
||||
// filament-track-switch-ready case (auto_match_available folds in !m_fila_switch_ready).
|
||||
FilamentMapMode default_auto_mode = mode >= fmmManual ? fmmAutoForFlush :
|
||||
mode == fmmAutoForMatch && !machine_synced ? fmmAutoForFlush :
|
||||
mode == fmmAutoForMatch && !auto_match_available ? fmmAutoForFlush :
|
||||
mode;
|
||||
|
||||
m_manual_map_panel = new FilamentMapManualPanel(this, m_filament_color, m_filament_type, filaments, filament_map);
|
||||
m_auto_map_panel = new FilamentMapAutoPanel(this, default_auto_mode, machine_synced);
|
||||
m_manual_map_panel = new FilamentMapManualPanel(this, m_filament_color, m_filament_type, filaments, filament_map, filament_volume_map);
|
||||
// A manual grouping can point filaments at a nozzle volume the extruder does not physically
|
||||
// carry; the panel's validation timer reports that here so OK is gated on a printable map.
|
||||
m_manual_map_panel->Bind(wxEVT_INVALID_MANUAL_MAP, [this](wxCommandEvent &event) {
|
||||
if (m_page_type != PageType::ptManual) {
|
||||
if (!m_ok_btn->IsEnabled()) { m_ok_btn->Enable(); }
|
||||
return;
|
||||
}
|
||||
if (event.GetInt()) {
|
||||
if (!m_ok_btn->IsEnabled()) { m_ok_btn->Enable(); }
|
||||
} else {
|
||||
if (m_ok_btn->IsEnabled()) { m_ok_btn->Disable(); }
|
||||
}
|
||||
});
|
||||
m_auto_map_panel = new FilamentMapAutoPanel(this, default_auto_mode, auto_match_available);
|
||||
if (show_default)
|
||||
m_default_map_panel = new FilamentMapDefaultPanel(this);
|
||||
else
|
||||
@@ -183,6 +291,13 @@ FilamentMapDialog::FilamentMapDialog(wxWindow *parent,
|
||||
if (show_default) panel_sizer->Add(m_default_map_panel, 0, wxEXPAND);
|
||||
main_sizer->Add(panel_sizer, 0, wxEXPAND);
|
||||
|
||||
// Smart filament section, shown only in filament-saving (flush) mode when the switch is ready.
|
||||
if (m_fila_switch_ready) {
|
||||
m_smart_filament = new SmartFilamentPanel(this);
|
||||
m_smart_filament->Show(get_mode() == fmmAutoForFlush);
|
||||
main_sizer->Add(m_smart_filament, 0, wxEXPAND);
|
||||
}
|
||||
|
||||
wxPanel* bottom_panel = new wxPanel(this);
|
||||
bottom_panel->SetBackgroundColour(*wxWHITE);
|
||||
wxBoxSizer *bottom_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
@@ -271,16 +386,8 @@ void FilamentMapDialog::on_checkbox(wxCommandEvent &event)
|
||||
void FilamentMapDialog::on_ok(wxCommandEvent &event)
|
||||
{
|
||||
if (m_page_type == PageType::ptManual) {
|
||||
std::vector<int> left_filaments = m_manual_map_panel->GetLeftFilaments();
|
||||
std::vector<int> right_filaments = m_manual_map_panel->GetRightFilaments();
|
||||
|
||||
for (int i = 0; i < m_filament_map.size(); ++i) {
|
||||
if (std::find(left_filaments.begin(), left_filaments.end(), i + 1) != left_filaments.end()) {
|
||||
m_filament_map[i] = 1;
|
||||
} else if (std::find(right_filaments.begin(), right_filaments.end(), i + 1) != right_filaments.end()) {
|
||||
m_filament_map[i] = 2;
|
||||
}
|
||||
}
|
||||
m_filament_map = m_manual_map_panel->GetFilamentMaps();
|
||||
m_filament_volume_map = m_manual_map_panel->GetFilamentVolumeMaps();
|
||||
}
|
||||
|
||||
EndModal(wxID_OK);
|
||||
@@ -318,6 +425,14 @@ void FilamentMapDialog::update_panel_status(PageType page)
|
||||
m_auto_map_panel->Show();
|
||||
}
|
||||
|
||||
// The nozzle-availability gate only constrains manual grouping; every other page must
|
||||
// leave OK usable even if the manual page had disabled it.
|
||||
if (page != PageType::ptManual && m_ok_btn && !m_ok_btn->IsEnabled())
|
||||
m_ok_btn->Enable();
|
||||
|
||||
if (m_smart_filament)
|
||||
m_smart_filament->Show(get_mode() == fmmAutoForFlush);
|
||||
|
||||
Layout();
|
||||
Fit();
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace GUI {
|
||||
class DragDropPanel;
|
||||
class Plater;
|
||||
class PartPlate;
|
||||
class SmartFilamentPanel;
|
||||
|
||||
/**
|
||||
* @brief Try to pop up the filament map dialog before slicing.
|
||||
@@ -42,6 +43,7 @@ public:
|
||||
const std::vector<std::string>& filament_color,
|
||||
const std::vector<std::string>& filament_type,
|
||||
const std::vector<int> &filament_map,
|
||||
const std::vector<int> &filament_volume_map,
|
||||
const std::vector<int> &filaments,
|
||||
const FilamentMapMode mode,
|
||||
bool machine_synced,
|
||||
@@ -56,6 +58,12 @@ public:
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<int> get_filament_volume_maps() const {
|
||||
if (m_page_type == PageType::ptManual)
|
||||
return m_filament_volume_map;
|
||||
return {};
|
||||
}
|
||||
|
||||
int ShowModal();
|
||||
void set_modal_btn_labels(const wxString& left_label, const wxString& right_label);
|
||||
private:
|
||||
@@ -78,11 +86,15 @@ private:
|
||||
Button* m_ok_btn;
|
||||
Button* m_cancel_btn;
|
||||
CheckBox* m_checkbox;
|
||||
SmartFilamentPanel* m_smart_filament{nullptr};
|
||||
|
||||
bool m_fila_switch_ready{false};
|
||||
|
||||
PageType m_page_type;
|
||||
|
||||
private:
|
||||
std::vector<int> m_filament_map;
|
||||
std::vector<int> m_filament_volume_map;
|
||||
std::vector<std::string> m_filament_color;
|
||||
std::vector<std::string> m_filament_type;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#include "FilamentMapPanel.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "Plater.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"
|
||||
@@ -17,28 +20,278 @@ static const wxColour BorderDisableColor = wxColour("#EEEEEE");
|
||||
static const wxColour TextNormalBlackColor = wxColour("#262E30");
|
||||
static const wxColour TextNormalGreyColor = wxColour("#6B6B6B");
|
||||
static const wxColour TextDisableColor = wxColour("#CECECE");
|
||||
static const wxColour TextErrorColor = wxColour("#E14747");
|
||||
|
||||
wxDEFINE_EVENT(wxEVT_INVALID_MANUAL_MAP, wxCommandEvent);
|
||||
|
||||
// Orca: whether the edited printer has an extruder that can physically carry several nozzles
|
||||
// (only such extruders track a per-volume-type nozzle inventory worth validating against).
|
||||
static bool printer_has_multi_nozzle_extruder()
|
||||
{
|
||||
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.
|
||||
return 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(); });
|
||||
}
|
||||
|
||||
void FilamentMapManualPanel::OnTimer(wxTimerEvent &)
|
||||
{
|
||||
bool valid = true;
|
||||
int invalid_eid = -1;
|
||||
NozzleVolumeType invalid_nozzle = NozzleVolumeType::nvtStandard;
|
||||
auto preset_bundle = wxGetApp().preset_bundle;
|
||||
auto proj_config = preset_bundle->project_config;
|
||||
auto nozzle_volume_values = proj_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type")->values;
|
||||
std::vector<int> filament_map = GetFilamentMaps();
|
||||
std::vector<int> filament_volume_map = GetFilamentVolumeMaps();
|
||||
// Orca: only multi-nozzle extruders carry a meaningful nozzle inventory; validating a plain
|
||||
// dual-extruder printer against it would flag every grouping whenever the stats are stale.
|
||||
if (printer_has_multi_nozzle_extruder()) {
|
||||
for (size_t eid = 0; eid < nozzle_volume_values.size(); ++eid) {
|
||||
NozzleVolumeType extruder_volume_type = NozzleVolumeType(nozzle_volume_values[eid]);
|
||||
bool extruder_used = std::find_if(m_filament_list.begin(), m_filament_list.end(),
|
||||
[&filament_map, eid](int fid) {
|
||||
return fid - 1 < (int) filament_map.size() && (filament_map[fid - 1] - 1) == (int) eid;
|
||||
}) != m_filament_list.end();
|
||||
|
||||
if (!extruder_used) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (extruder_volume_type == nvtHybrid) {
|
||||
int standard_count = getExtruderNozzleCount(preset_bundle, eid, NozzleVolumeType::nvtStandard);
|
||||
int highflow_count = getExtruderNozzleCount(preset_bundle, eid, NozzleVolumeType::nvtHighFlow);
|
||||
|
||||
auto has_material_of_type = [this, eid, &filament_map, &filament_volume_map](NozzleVolumeType volume_type) {
|
||||
return std::find_if(m_filament_list.begin(), m_filament_list.end(),
|
||||
[eid, &filament_map, &filament_volume_map, volume_type](int fid) {
|
||||
return fid - 1 < (int) filament_map.size() && (filament_map[fid - 1] - 1) == (int) eid &&
|
||||
fid - 1 < (int) filament_volume_map.size() &&
|
||||
filament_volume_map[fid - 1] == static_cast<int>(volume_type);
|
||||
}) != m_filament_list.end();
|
||||
};
|
||||
|
||||
bool has_standard = has_material_of_type(NozzleVolumeType::nvtStandard);
|
||||
bool has_highflow = has_material_of_type(NozzleVolumeType::nvtHighFlow);
|
||||
|
||||
if ((has_standard && standard_count == 0) ||
|
||||
(has_highflow && highflow_count == 0)) {
|
||||
valid = false;
|
||||
invalid_eid = eid;
|
||||
invalid_nozzle = (has_standard && standard_count == 0) ? NozzleVolumeType::nvtStandard : NozzleVolumeType::nvtHighFlow;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
int count = getExtruderNozzleCount(preset_bundle, eid, extruder_volume_type);
|
||||
if (count == 0) {
|
||||
valid = false;
|
||||
invalid_eid = eid;
|
||||
invalid_nozzle = extruder_volume_type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool update_ui = m_invalid_id != invalid_eid;
|
||||
bool send_event = update_ui || m_force_validation;
|
||||
|
||||
m_invalid_id = invalid_eid;
|
||||
|
||||
if (update_ui) {
|
||||
if (valid) {
|
||||
m_errors->Hide();
|
||||
m_suggestion_panel->Hide();
|
||||
} else {
|
||||
m_errors->SetLabel(wxString::Format(_L("Error: %s extruder has no available %s nozzle, current group result is invalid."),
|
||||
invalid_eid == 0 ? _L("Left") : _L("Right"),
|
||||
invalid_nozzle == NozzleVolumeType::nvtStandard ? _L("Standard") : _L("High Flow")));
|
||||
// Re-wrap: wrapping only applies to the label text present when Wrap is called,
|
||||
// and the label was empty at construction time.
|
||||
m_errors->Wrap(FromDIP(520));
|
||||
m_errors->Show();
|
||||
m_suggestion_panel->Show();
|
||||
}
|
||||
m_left_panel->Freeze();
|
||||
m_right_panel->Freeze();
|
||||
m_tips->Freeze();
|
||||
m_description->Freeze();
|
||||
Layout();
|
||||
Fit();
|
||||
this->GetParent()->Layout();
|
||||
this->GetParent()->Fit();
|
||||
m_left_panel->Thaw();
|
||||
m_right_panel->Thaw();
|
||||
m_tips->Thaw();
|
||||
m_description->Thaw();
|
||||
}
|
||||
|
||||
if (send_event) {
|
||||
wxCommandEvent event(wxEVT_INVALID_MANUAL_MAP);
|
||||
event.SetInt(valid);
|
||||
ProcessEvent(event);
|
||||
m_force_validation = false;
|
||||
}
|
||||
}
|
||||
|
||||
void FilamentMapManualPanel::OnSuggestionClicked(wxCommandEvent &event)
|
||||
{
|
||||
wxWindow *current = this;
|
||||
while (current && !wxDynamicCast(current, wxDialog)) {
|
||||
current = current->GetParent();
|
||||
}
|
||||
|
||||
if (current) {
|
||||
wxDialog *dlg = wxDynamicCast(current, wxDialog);
|
||||
if (dlg) {
|
||||
int invalid_eid = m_invalid_id;
|
||||
dlg->EndModal(wxID_CANCEL);
|
||||
|
||||
if (invalid_eid >= 0) {
|
||||
manuallySetNozzleCount(invalid_eid);
|
||||
}
|
||||
wxGetApp().plater()->update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<int> FilamentMapManualPanel::GetFilamentMaps() const
|
||||
{
|
||||
std::vector<int> new_filament_map = m_filament_map;
|
||||
std::vector<int> left_filaments = this->GetLeftFilaments();
|
||||
std::vector<int> right_filaments = this->GetRightFilaments();
|
||||
|
||||
for (int i = 0; i < (int) new_filament_map.size(); ++i) {
|
||||
if (std::find(left_filaments.begin(), left_filaments.end(), i + 1) != left_filaments.end()) {
|
||||
new_filament_map[i] = 1;
|
||||
} else if (std::find(right_filaments.begin(), right_filaments.end(), i + 1) != right_filaments.end()) {
|
||||
new_filament_map[i] = 2;
|
||||
}
|
||||
}
|
||||
return new_filament_map;
|
||||
}
|
||||
|
||||
std::vector<int> FilamentMapManualPanel::GetFilamentVolumeMaps() const
|
||||
{
|
||||
std::vector<int> volume_map(m_filament_map.size(), 0);
|
||||
|
||||
std::vector<int> left_filaments = this->GetLeftFilaments();
|
||||
std::vector<int> right_high_flow_filaments = this->GetRightHighFlowFilaments();
|
||||
std::vector<int> right_standard_filaments = this->GetRightStandardFilaments();
|
||||
std::vector<int> right_tpu_high_flow_filaments = this->GetRightTPUHighFlowFilaments();
|
||||
|
||||
auto preset_bundle = wxGetApp().preset_bundle;
|
||||
auto proj_config = preset_bundle->project_config;
|
||||
auto nozzle_volume_values = proj_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type")->values;
|
||||
|
||||
for (int i = 0; i < (int) volume_map.size(); ++i) {
|
||||
int filament_id = i + 1;
|
||||
|
||||
if (std::find(left_filaments.begin(), left_filaments.end(), filament_id) != left_filaments.end()) {
|
||||
if (nozzle_volume_values.size() > 0) {
|
||||
// Orca: never emit the Hybrid marker as a per-filament value; on a Hybrid
|
||||
// extruder each filament prints with a concrete flow, defaulting to Standard.
|
||||
volume_map[i] = nozzle_volume_values[0] == static_cast<int>(NozzleVolumeType::nvtHybrid) ?
|
||||
static_cast<int>(NozzleVolumeType::nvtStandard) :
|
||||
nozzle_volume_values[0];
|
||||
}
|
||||
}
|
||||
else if (std::find(right_high_flow_filaments.begin(), right_high_flow_filaments.end(), filament_id) != right_high_flow_filaments.end()) {
|
||||
volume_map[i] = static_cast<int>(NozzleVolumeType::nvtHighFlow);
|
||||
}
|
||||
else if (std::find(right_standard_filaments.begin(), right_standard_filaments.end(), filament_id) != right_standard_filaments.end()) {
|
||||
volume_map[i] = static_cast<int>(NozzleVolumeType::nvtStandard);
|
||||
}
|
||||
else if (std::find(right_tpu_high_flow_filaments.begin(), right_tpu_high_flow_filaments.end(), filament_id) != right_tpu_high_flow_filaments.end()) {
|
||||
volume_map[i] = static_cast<int>(NozzleVolumeType::nvtTPUHighFlow);
|
||||
}
|
||||
}
|
||||
|
||||
return volume_map;
|
||||
}
|
||||
|
||||
void FilamentMapManualPanel::SyncPanelHeights()
|
||||
{
|
||||
if (!m_left_panel || !m_right_panel) return;
|
||||
|
||||
auto curr_left = m_left_panel->GetMinSize();
|
||||
auto curr_right = m_right_panel->GetMinSize();
|
||||
|
||||
m_left_panel->SetMinSize(wxSize(FromDIP(260), FromDIP(110)));
|
||||
m_right_panel->SetMinSize(wxSize(FromDIP(260), FromDIP(110)));
|
||||
|
||||
m_left_panel->Layout();
|
||||
m_left_panel->Fit();
|
||||
m_right_panel->Layout();
|
||||
m_right_panel->Fit();
|
||||
|
||||
wxSize left_best_size = m_left_panel->GetBestSize();
|
||||
wxSize right_best_size = m_right_panel->GetBestSize();
|
||||
|
||||
int max_height = std::max(left_best_size.GetHeight(), right_best_size.GetHeight());
|
||||
bool height_changed = curr_left.GetHeight() != max_height || curr_right.GetHeight() != max_height;
|
||||
if (!height_changed) {
|
||||
if (curr_left.GetHeight() > 0)
|
||||
m_left_panel->SetMinSize(curr_left);
|
||||
if (curr_right.GetHeight() > 0)
|
||||
m_right_panel->SetMinSize(curr_right);
|
||||
if (GetParent()) {
|
||||
GetParent()->Layout();
|
||||
GetParent()->Fit();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
m_left_panel->SetMinSize(wxSize(FromDIP(260), max_height));
|
||||
m_right_panel->SetMinSize(wxSize(FromDIP(260), max_height));
|
||||
|
||||
Layout();
|
||||
Fit();
|
||||
|
||||
if (GetParent()) {
|
||||
GetParent()->Layout();
|
||||
GetParent()->Fit();
|
||||
}
|
||||
}
|
||||
|
||||
void FilamentMapManualPanel::OnDragDropCompleted(wxCommandEvent &event)
|
||||
{
|
||||
SyncPanelHeights();
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
FilamentMapManualPanel::FilamentMapManualPanel(wxWindow *parent,
|
||||
const std::vector<std::string> &color,
|
||||
const std::vector<std::string> &type,
|
||||
const std::vector<int> &filament_list,
|
||||
const std::vector<int> &filament_map)
|
||||
: wxPanel(parent), m_filament_map(filament_map), m_filament_color(color), m_filament_type(type), m_filament_list(filament_list)
|
||||
const std::vector<int> &filament_map,
|
||||
const std::vector<int> &filament_volume_map)
|
||||
: wxPanel(parent)
|
||||
, m_filament_map(filament_map)
|
||||
, m_filament_volume_map(filament_volume_map)
|
||||
, m_filament_list(filament_list)
|
||||
, m_filament_color(color)
|
||||
, m_filament_type(type)
|
||||
{
|
||||
SetName(wxT("FilamentMapManualPanel"));
|
||||
SetBackgroundColour(BgNormalColor);
|
||||
|
||||
auto top_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
m_description = new Label(this, _L("We will slice according to this grouping method:"));
|
||||
top_sizer->Add(m_description, 0, wxALIGN_LEFT | wxLEFT, FromDIP(15));
|
||||
m_description->Wrap(FromDIP(520));
|
||||
top_sizer->AddSpacer(FromDIP(8));
|
||||
|
||||
auto drag_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
m_left_panel = new DragDropPanel(this, _L("Left Nozzle"), false);
|
||||
m_right_panel = new DragDropPanel(this, _L("Right Nozzle"), false);
|
||||
m_right_panel = new SeparatedDragDropPanel(this, _L("Right Nozzle"), false);
|
||||
m_switch_btn = new ScalableButton(this, wxID_ANY, "switch_filament_maps");
|
||||
|
||||
UpdateNozzleVolumeType();
|
||||
|
||||
for (size_t idx = 0; idx < m_filament_map.size(); ++idx) {
|
||||
auto iter = std::find(m_filament_list.begin(), m_filament_list.end(), idx + 1);
|
||||
if (iter == m_filament_list.end()) continue;
|
||||
@@ -48,43 +301,155 @@ FilamentMapManualPanel::FilamentMapManualPanel(wxWindow *p
|
||||
m_left_panel->AddColorBlock(color, type, idx + 1);
|
||||
} else {
|
||||
assert(m_filament_map[idx] == 2);
|
||||
m_right_panel->AddColorBlock(color, type, idx + 1);
|
||||
bool is_high_flow = (idx < m_filament_volume_map.size()) && (m_filament_volume_map[idx] == static_cast<int>(NozzleVolumeType::nvtHighFlow));
|
||||
m_right_panel->AddColorBlock(color, type, idx + 1, is_high_flow);
|
||||
}
|
||||
}
|
||||
m_left_panel->SetMinSize({ FromDIP(260),-1 });
|
||||
m_right_panel->SetMinSize({ FromDIP(260),-1 });
|
||||
m_left_panel->SetMinSize({FromDIP(260), FromDIP(110)});
|
||||
m_right_panel->SetMinSize({FromDIP(260), FromDIP(110)});
|
||||
|
||||
drag_sizer->AddStretchSpacer();
|
||||
drag_sizer->Add(m_left_panel, 1, wxEXPAND);
|
||||
drag_sizer->AddSpacer(FromDIP(7));
|
||||
drag_sizer->Add(m_switch_btn, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, FromDIP(1));
|
||||
drag_sizer->AddSpacer(FromDIP(7));
|
||||
drag_sizer->Add(m_switch_btn, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, FromDIP(8));
|
||||
drag_sizer->Add(m_right_panel, 1, wxEXPAND);
|
||||
drag_sizer->AddStretchSpacer();
|
||||
|
||||
top_sizer->Add(drag_sizer, 0, wxEXPAND);
|
||||
|
||||
m_tips = new Label(this, _L("Tip: You can drag the filaments to reassign them to different nozzles."));
|
||||
m_tips->SetFont(Label::Body_14);
|
||||
m_tips->SetForegroundColour(TextNormalGreyColor);
|
||||
m_tips->Wrap(FromDIP(520));
|
||||
top_sizer->AddSpacer(FromDIP(20));
|
||||
top_sizer->Add(m_tips, 0, wxALIGN_LEFT | wxLEFT, FromDIP(15));
|
||||
|
||||
m_errors = new Label(this, "");
|
||||
m_errors->SetFont(Label::Body_13);
|
||||
m_errors->SetForegroundColour(TextErrorColor);
|
||||
m_errors->Wrap(FromDIP(520));
|
||||
top_sizer->AddSpacer(FromDIP(10));
|
||||
top_sizer->Add(m_errors, 0, wxALIGN_LEFT | wxLEFT, FromDIP(15));
|
||||
|
||||
m_errors->Hide();
|
||||
|
||||
m_suggestion_panel = new wxPanel(this, wxID_ANY);
|
||||
m_suggestion_panel->SetBackgroundColour(*wxWHITE);
|
||||
auto suggestion_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
auto suggestion_text = new Label(m_suggestion_panel, _L("Please adjust your grouping or click "));
|
||||
suggestion_text->SetFont(Label::Body_13);
|
||||
suggestion_text->SetForegroundColour(TextErrorColor);
|
||||
suggestion_text->SetBackgroundColour(*wxWHITE);
|
||||
auto suggestion_btn = new ScalableButton(m_suggestion_panel, wxID_ANY, "edit", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, true, 14);
|
||||
suggestion_btn->SetBackgroundColour(*wxWHITE);
|
||||
auto suggestion_text2 = new Label(m_suggestion_panel, _L(" to set nozzle count"));
|
||||
suggestion_text2->SetFont(Label::Body_13);
|
||||
suggestion_text2->SetForegroundColour(TextErrorColor);
|
||||
suggestion_text2->SetBackgroundColour(*wxWHITE);
|
||||
suggestion_sizer->Add(suggestion_text, 0, wxALIGN_CENTER_VERTICAL);
|
||||
suggestion_sizer->Add(suggestion_btn, 0, wxALIGN_CENTER_VERTICAL);
|
||||
suggestion_sizer->Add(suggestion_text2, 0, wxALIGN_CENTER_VERTICAL);
|
||||
m_suggestion_panel->SetSizer(suggestion_sizer);
|
||||
top_sizer->Add(m_suggestion_panel, 0, wxALIGN_LEFT | wxLEFT, FromDIP(15));
|
||||
m_suggestion_panel->Hide();
|
||||
suggestion_btn->Bind(wxEVT_BUTTON, &FilamentMapManualPanel::OnSuggestionClicked, 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.
|
||||
if (printer_has_multi_nozzle_extruder()) {
|
||||
auto *max_nozzle_counts_opt = wxGetApp().preset_bundle->printers.get_edited_preset().config.option<ConfigOptionIntsNullable>("extruder_max_nozzle_count");
|
||||
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 && max_counts[extruder_id] != ConfigOptionIntsNullable::nil_value())
|
||||
GUI::manuallySetNozzleCount(extruder_id);
|
||||
}
|
||||
evt.Skip();
|
||||
});
|
||||
}
|
||||
|
||||
m_timer = new wxTimer(this);
|
||||
Bind(wxEVT_TIMER, &FilamentMapManualPanel::OnTimer, this);
|
||||
Bind(wxEVT_DRAG_DROP_COMPLETED, &FilamentMapManualPanel::OnDragDropCompleted, this);
|
||||
|
||||
m_switch_btn->Bind(wxEVT_BUTTON, &FilamentMapManualPanel::OnSwitchFilament, this);
|
||||
|
||||
SetSizer(top_sizer);
|
||||
SetMinSize(wxSize(FromDIP(580), -1));
|
||||
Layout();
|
||||
Fit();
|
||||
GUI::wxGetApp().UpdateDarkUIWin(this);
|
||||
}
|
||||
|
||||
void FilamentMapManualPanel::UpdateNozzleVolumeType()
|
||||
{
|
||||
auto check_separation = []() {
|
||||
auto preset_bundle = wxGetApp().preset_bundle;
|
||||
auto nozzle_volume_values = preset_bundle->project_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type")->values;
|
||||
if (nozzle_volume_values.size() <= 1)
|
||||
return false;
|
||||
|
||||
return nozzle_volume_values[1] == static_cast<int>(NozzleVolumeType::nvtHybrid);
|
||||
};
|
||||
bool should_separate = check_separation();
|
||||
m_right_panel->SetUseSeparation(should_separate);
|
||||
|
||||
UpdateNozzleCountDisplay();
|
||||
|
||||
Layout();
|
||||
Fit();
|
||||
}
|
||||
|
||||
void FilamentMapManualPanel::UpdateNozzleCountDisplay()
|
||||
{
|
||||
auto preset_bundle = wxGetApp().preset_bundle;
|
||||
|
||||
// Orca: nozzle counts are only tracked (and only meaningful) for multi-nozzle extruders;
|
||||
// plain dual-extruder printers keep their unadorned zone titles.
|
||||
if (!printer_has_multi_nozzle_extruder()) {
|
||||
m_left_panel->UpdateLabel(_L("Left Nozzle"));
|
||||
m_right_panel->UpdateLabel(_L("Right Nozzle"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Format the count suffix separately so a translation containing '%' cannot
|
||||
// corrupt the wxString::Format output.
|
||||
int left_count = getExtruderNozzleCountTotal(preset_bundle, 0);
|
||||
wxString left_title = _L("Left Nozzle") + wxString::Format("(%d)", left_count);
|
||||
m_left_panel->UpdateLabel(left_title);
|
||||
|
||||
if (m_right_panel->IsUseSeparation()) {
|
||||
int standard_count = getExtruderNozzleCount(preset_bundle, 1, NozzleVolumeType::nvtStandard);
|
||||
int highflow_count = getExtruderNozzleCount(preset_bundle, 1, NozzleVolumeType::nvtHighFlow);
|
||||
wxString right_title = _L("Right Nozzle") + wxString::Format("(Std: %d, HF: %d)", standard_count, highflow_count);
|
||||
m_right_panel->UpdateLabel(right_title);
|
||||
} else {
|
||||
int right_count = getExtruderNozzleCountTotal(preset_bundle, 1);
|
||||
wxString right_title = _L("Right Nozzle") + wxString::Format("(%d)", right_count);
|
||||
m_right_panel->UpdateLabel(right_title);
|
||||
}
|
||||
}
|
||||
|
||||
FilamentMapManualPanel::~FilamentMapManualPanel()
|
||||
{
|
||||
m_timer->Stop();
|
||||
delete m_timer;
|
||||
}
|
||||
|
||||
void FilamentMapManualPanel::OnSwitchFilament(wxCommandEvent &)
|
||||
{
|
||||
auto left_blocks = m_left_panel->get_filament_blocks();
|
||||
auto right_blocks = m_right_panel->get_filament_blocks();
|
||||
|
||||
for (auto &block : left_blocks) {
|
||||
m_right_panel->AddColorBlock(block->GetColor(), block->GetType(), block->GetFilamentId(), false);
|
||||
m_right_panel->AddColorBlock(block->GetColor(), block->GetType(), block->GetFilamentId(), false, false);
|
||||
m_left_panel->RemoveColorBlock(block, false);
|
||||
}
|
||||
|
||||
@@ -94,22 +459,27 @@ void FilamentMapManualPanel::OnSwitchFilament(wxCommandEvent &)
|
||||
}
|
||||
this->GetParent()->Layout();
|
||||
this->GetParent()->Fit();
|
||||
|
||||
if (m_right_panel->IsUseSeparation()) {
|
||||
m_left_panel->Layout();
|
||||
m_left_panel->Fit();
|
||||
m_right_panel->Layout();
|
||||
m_right_panel->Fit();
|
||||
SyncPanelHeights();
|
||||
}
|
||||
}
|
||||
|
||||
void FilamentMapManualPanel::Hide()
|
||||
bool FilamentMapManualPanel::Show(bool show)
|
||||
{
|
||||
m_left_panel->Hide();
|
||||
m_right_panel->Hide();
|
||||
m_switch_btn->Hide();
|
||||
wxPanel::Hide();
|
||||
}
|
||||
m_force_validation = show;
|
||||
if (show) {
|
||||
m_timer->Start(500);
|
||||
SyncPanelHeights();
|
||||
} else {
|
||||
m_timer->Stop();
|
||||
}
|
||||
|
||||
void FilamentMapManualPanel::Show()
|
||||
{
|
||||
m_left_panel->Show();
|
||||
m_right_panel->Show();
|
||||
m_switch_btn->Show();
|
||||
wxPanel::Show();
|
||||
return wxPanel::Show(show);
|
||||
}
|
||||
|
||||
GUI::FilamentMapBtnPanel::FilamentMapBtnPanel(wxWindow *parent, const wxString &label, const wxString &detail, const std::string &icon) : wxPanel(parent)
|
||||
|
||||
@@ -9,32 +9,60 @@
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
// Fired by the manual panel's validation timer: the event int is 1 when the current manual
|
||||
// grouping is printable with the installed nozzles, 0 when some zone has no matching nozzle.
|
||||
wxDECLARE_EVENT(wxEVT_INVALID_MANUAL_MAP, wxCommandEvent);
|
||||
|
||||
class FilamentMapManualPanel : public wxPanel
|
||||
{
|
||||
public:
|
||||
FilamentMapManualPanel(wxWindow *parent, const std::vector<std::string> &color, const std::vector<std::string> &type, const std::vector<int> &filament_list, const std::vector<int> &filament_map);
|
||||
FilamentMapManualPanel(wxWindow *parent,
|
||||
const std::vector<std::string> &color,
|
||||
const std::vector<std::string> &type,
|
||||
const std::vector<int> &filament_list,
|
||||
const std::vector<int> &filament_map,
|
||||
const std::vector<int> &filament_volume_map);
|
||||
~FilamentMapManualPanel();
|
||||
|
||||
std::vector<int> GetFilamentMaps() const { return m_filament_map; }
|
||||
std::vector<int> GetFilamentMaps() const;
|
||||
std::vector<int> GetFilamentVolumeMaps() const;
|
||||
std::vector<int> GetLeftFilaments() const { return m_left_panel->GetAllFilaments(); }
|
||||
std::vector<int> GetRightFilaments() const { return m_right_panel->GetAllFilaments(); }
|
||||
|
||||
void Hide();
|
||||
void Show();
|
||||
std::vector<int> GetRightHighFlowFilaments() const { return m_right_panel->GetHighFlowFilaments(); }
|
||||
std::vector<int> GetRightStandardFilaments() const { return m_right_panel->GetStandardFilaments(); }
|
||||
std::vector<int> GetRightTPUHighFlowFilaments() const { return m_right_panel->GetTPUHighFlowFilaments(); }
|
||||
void UpdateNozzleVolumeType();
|
||||
void UpdateNozzleCountDisplay();
|
||||
|
||||
bool Show(bool show = true) override;
|
||||
|
||||
private:
|
||||
void OnSwitchFilament(wxCommandEvent &);
|
||||
DragDropPanel *m_left_panel;
|
||||
DragDropPanel *m_right_panel;
|
||||
void OnTimer(wxTimerEvent &evt);
|
||||
void OnSwitchFilament(wxCommandEvent &);
|
||||
void SyncPanelHeights();
|
||||
void OnDragDropCompleted(wxCommandEvent &evt);
|
||||
void OnSuggestionClicked(wxCommandEvent &event);
|
||||
|
||||
Label *m_description;
|
||||
Label *m_tips;
|
||||
DragDropPanel *m_left_panel;
|
||||
SeparatedDragDropPanel *m_right_panel;
|
||||
|
||||
Label *m_description;
|
||||
Label *m_tips;
|
||||
Label *m_errors;
|
||||
wxPanel *m_suggestion_panel;
|
||||
|
||||
ScalableButton *m_switch_btn;
|
||||
|
||||
std::vector<int> m_filament_map;
|
||||
std::vector<int> m_filament_volume_map;
|
||||
std::vector<int> m_filament_list;
|
||||
std::vector<std::string> m_filament_color;
|
||||
std::vector<std::string> m_filament_type;
|
||||
|
||||
wxTimer *m_timer;
|
||||
int m_invalid_id{-1};
|
||||
bool m_force_validation{false};
|
||||
};
|
||||
|
||||
class FilamentMapBtnPanel : public wxPanel
|
||||
|
||||
@@ -2915,8 +2915,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;
|
||||
|
||||
@@ -8236,6 +8236,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();
|
||||
|
||||
@@ -8297,7 +8302,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 {
|
||||
@@ -8332,7 +8337,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();
|
||||
@@ -10289,6 +10294,9 @@ void GLCanvas3D::_set_warning_notification_if_needed(EWarning warning)
|
||||
|
||||
void GLCanvas3D::_set_warning_notification(EWarning warning, bool state)
|
||||
{
|
||||
// Skip on shutdown: Plater's pImpl is already freed, so get_notification_manager() would use-after-free.
|
||||
if (wxGetApp().is_closing())
|
||||
return;
|
||||
using NotificationLevel = NotificationManager::NotificationLevel;
|
||||
enum ErrorType{
|
||||
PLATER_WARNING,
|
||||
|
||||
@@ -3633,8 +3633,24 @@ void GUI_App::switch_printer_agent()
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": printer agent switched to " << effective_agent_id;
|
||||
|
||||
// Auto-switch MachineObject
|
||||
// Auto-switch MachineObject (new agent has empty device_info, so always re-select)
|
||||
select_machine(effective_agent_id);
|
||||
} else if (effective_agent_id != BBL_PRINTER_AGENT_ID) {
|
||||
// Orca: the agent type is unchanged (e.g. switching between two Moonraker/Klipper
|
||||
// printer presets), so the selected machine and the agent's cached device_info still
|
||||
// point at the previously active printer preset. Re-select the machine when the new
|
||||
// preset targets a different host, otherwise filament sync keeps hitting the old
|
||||
// printer. (#12506)
|
||||
if (m_device_manager && preset_bundle) {
|
||||
const DynamicPrintConfig& cfg = preset_bundle->printers.get_edited_preset().config;
|
||||
const std::string print_host = cfg.opt_string("print_host");
|
||||
if (!print_host.empty()) {
|
||||
const std::string dev_id = MachineObject::dev_id_from_address(print_host, cfg.opt_string("printhost_port"));
|
||||
MachineObject* sel = m_device_manager->get_selected_machine();
|
||||
if (!sel || sel->get_dev_id() != dev_id)
|
||||
select_machine(effective_agent_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7144,7 +7160,7 @@ void GUI_App::start_sync_user_preset(bool with_progress_dlg)
|
||||
.plater()
|
||||
->get_notification_manager()
|
||||
->push_notification(NotificationType::CustomNotification,
|
||||
NotificationManager::NotificationLevel::RegularNotificationLevel, "There is an update available. Open the preset bundle dialog to update it.");
|
||||
NotificationManager::NotificationLevel::RegularNotificationLevel, _u8L("There is an update available. Open the preset bundle dialog to update it."));
|
||||
|
||||
update_available = false;
|
||||
}
|
||||
@@ -7722,14 +7738,14 @@ bool GUI_App::load_language(wxString language, bool initial)
|
||||
|
||||
if (!wxLocale::IsAvailable(locale_language_info->Language)) {
|
||||
// Loading the language dictionary failed.
|
||||
wxString message = "Switching Orca Slicer to language " + requested_language_code + " failed.";
|
||||
wxString message = wxString::Format(_L("Switching Orca Slicer to language %s failed."), requested_language_code);
|
||||
#if !defined(_WIN32) && !defined(__APPLE__)
|
||||
// likely some linux system
|
||||
message += "\nYou may need to reconfigure the missing locales, likely by running the \"locale-gen\" and \"dpkg-reconfigure locales\" commands.\n";
|
||||
message += _L("\nYou may need to reconfigure the missing locales, likely by running the \"locale-gen\" and \"dpkg-reconfigure locales\" commands.\n");
|
||||
#endif
|
||||
if (initial)
|
||||
message + "\n\nApplication will close.";
|
||||
wxMessageBox(message, "Orca Slicer - Switching language failed", wxOK | wxICON_ERROR);
|
||||
wxMessageBox(message, _L("Orca Slicer - Switching language failed"), wxOK | wxICON_ERROR);
|
||||
if (initial)
|
||||
std::exit(EXIT_FAILURE);
|
||||
else
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
|
||||
#include "slic3r/Utils/FixModelByCgal.hpp"
|
||||
#include "libslic3r/Format/bbs_3mf.hpp"
|
||||
#include "libslic3r/Orient.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
|
||||
#ifdef __WXMSW__
|
||||
@@ -1154,7 +1155,7 @@ void ObjectList::update_filament_in_config(const wxDataViewItem& item)
|
||||
|
||||
m_config = config;
|
||||
|
||||
take_snapshot("Change Filament");
|
||||
take_snapshot(_u8L("Change Filament"));
|
||||
|
||||
const int extruder = m_objects_model->GetExtruderNumber(item);
|
||||
m_config->set_key_value("extruder", new ConfigOptionInt(extruder));
|
||||
@@ -1192,7 +1193,7 @@ void ObjectList::update_name_in_model(const wxDataViewItem& item) const
|
||||
if (obj_idx < 0) return;
|
||||
const int volume_id = m_objects_model->GetVolumeIdByItem(item);
|
||||
|
||||
take_snapshot(volume_id < 0 ? "Rename Object" : "Rename Part");
|
||||
take_snapshot(volume_id < 0 ? _u8L("Rename Object") : _u8L("Rename Part"));
|
||||
|
||||
ModelObject* obj = object(obj_idx);
|
||||
if (m_objects_model->GetItemType(item) & itObject) {
|
||||
@@ -1360,7 +1361,7 @@ void ObjectList::paste_settings_into_list()
|
||||
{
|
||||
wxDataViewItemArray sels;
|
||||
GetSelections(sels);
|
||||
take_snapshot("Paste settings");
|
||||
take_snapshot(_u8L("Paste settings"));
|
||||
|
||||
std::unique_ptr<t_config_option_keys> global_keys; // need copy from global
|
||||
|
||||
@@ -1578,7 +1579,7 @@ void ObjectList::list_manipulation(const wxPoint& mouse_pos, bool evt_context_me
|
||||
int obj_idx, vol_idx;
|
||||
get_selected_item_indexes(obj_idx, vol_idx, item);
|
||||
if (obj_idx != -1) {
|
||||
Plater::TakeSnapshot(plater, "Shift objects to bed");
|
||||
Plater::TakeSnapshot(plater, _u8L("Shift objects to bed"));
|
||||
(*m_objects)[obj_idx]->ensure_on_bed();
|
||||
cnv->reload_scene(true, true);
|
||||
update_info_items(obj_idx);
|
||||
@@ -2001,7 +2002,7 @@ void ObjectList::OnDrop(wxDataViewEvent &event)
|
||||
}
|
||||
|
||||
//#if 1
|
||||
take_snapshot("Object order changed");
|
||||
take_snapshot(_u8L("Object order changed"));
|
||||
|
||||
if(m_dragged_data.type() & itObject){
|
||||
int delta = dest_obj_id < src_obj_id ? -1 : 1;
|
||||
@@ -2065,9 +2066,9 @@ void ObjectList::add_category_to_settings_from_selection(const std::vector< std:
|
||||
assert(m_config);
|
||||
auto opt_keys = m_config->keys();
|
||||
|
||||
const std::string snapshot_text = item_type & itLayer ? "Layer setting added" :
|
||||
item_type & itVolume ? "Part setting added" :
|
||||
"Object setting added";
|
||||
const std::string snapshot_text = item_type & itLayer ? _u8L("Layer setting added") :
|
||||
item_type & itVolume ? _u8L("Part setting added") :
|
||||
_u8L("Object setting added");
|
||||
take_snapshot(snapshot_text);
|
||||
|
||||
const DynamicPrintConfig& from_config = printer_technology() == ptFFF ?
|
||||
@@ -2106,9 +2107,9 @@ void ObjectList::add_category_to_settings_from_frequent(const std::vector<std::s
|
||||
assert(m_config);
|
||||
auto opt_keys = m_config->keys();
|
||||
|
||||
const std::string snapshot_text = item_type & itLayer ? "Height range settings added" :
|
||||
item_type & itVolume ? "Part settings added" :
|
||||
"Object settings added";
|
||||
const std::string snapshot_text = item_type & itLayer ? _u8L("Height range settings added") :
|
||||
item_type & itVolume ? _u8L("Part settings added") :
|
||||
_u8L("Object settings added");
|
||||
take_snapshot(snapshot_text);
|
||||
|
||||
const DynamicPrintConfig& from_config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
||||
@@ -2176,7 +2177,7 @@ void ObjectList::load_subobject(ModelVolumeType type, bool from_galery/* = false
|
||||
if (input_files.IsEmpty())
|
||||
return;
|
||||
|
||||
take_snapshot((type == ModelVolumeType::MODEL_PART) ? "Load Part" : "Load Modifier");
|
||||
take_snapshot((type == ModelVolumeType::MODEL_PART) ? _u8L("Load Part") : _u8L("Load Modifier"));
|
||||
|
||||
std::vector<ModelVolume*> volumes;
|
||||
// ! ysFIXME - delete commented code after testing and rename "load_modifier" to something common
|
||||
@@ -2463,7 +2464,7 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode
|
||||
if (instance_idx == -1)
|
||||
return;
|
||||
|
||||
take_snapshot("Add primitive");
|
||||
take_snapshot(_u8L("Add primitive"));
|
||||
|
||||
// Selected object
|
||||
ModelObject &model_object = *(*m_objects)[obj_idx];
|
||||
@@ -2555,11 +2556,20 @@ void ObjectList::load_shape_object(const std::string &type_name)
|
||||
if (obj_idx < 0)
|
||||
return;
|
||||
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Add Primitive");
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _u8L("Add Primitive"));
|
||||
|
||||
// Create mesh
|
||||
BoundingBoxf3 bb;
|
||||
TriangleMesh mesh = create_mesh(type_name, bb);
|
||||
// Rotate the largest overhang area toward the part cooling fan so new shapes start in a
|
||||
// cooling friendly orientation. Skip the plain cube: the pressure advance pattern
|
||||
// calibration reuses it as an axis-aligned anchor and scales it by its bounding box,
|
||||
// so its orientation must stay fixed.
|
||||
const Slic3r::DynamicPrintConfig& full_config = wxGetApp().preset_bundle->full_config();
|
||||
if (type_name != "Cube" && full_config.has("fan_direction") && full_config.has("auxiliary_fan")) {
|
||||
FanDirection config_dir = full_config.option<ConfigOptionEnum<FanDirection>>("fan_direction")->value;
|
||||
orientation::orient_for_cooling(mesh, config_dir);
|
||||
}
|
||||
// BBS: remove "Shape" prefix
|
||||
load_mesh_object(mesh, _(type_name));
|
||||
wxGetApp().mainframe->update_title();
|
||||
@@ -2716,7 +2726,7 @@ void ObjectList::del_settings_from_config(const wxDataViewItem& parent_item)
|
||||
(is_layer_settings && opt_cnt == 2 && m_config->has("extruder") && m_config->has("layer_height")))
|
||||
return;
|
||||
|
||||
take_snapshot("Delete Settings");
|
||||
take_snapshot(_u8L("Delete Settings"));
|
||||
|
||||
int extruder = m_config->has("extruder") ? m_config->extruder() : -1;
|
||||
|
||||
@@ -2757,7 +2767,7 @@ void ObjectList::del_layer_from_object(const int obj_idx, const t_layer_height_r
|
||||
if (del_range == object(obj_idx)->layer_config_ranges.end())
|
||||
return;
|
||||
|
||||
take_snapshot("Remove height range");
|
||||
take_snapshot(_u8L("Remove height range"));
|
||||
|
||||
object(obj_idx)->layer_config_ranges.erase(del_range);
|
||||
|
||||
@@ -2830,7 +2840,7 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con
|
||||
return false;
|
||||
}
|
||||
|
||||
take_snapshot("Delete part");
|
||||
take_snapshot(_u8L("Delete part"));
|
||||
|
||||
object->delete_volume(idx);
|
||||
|
||||
@@ -2895,7 +2905,7 @@ void ObjectList::split()
|
||||
return;
|
||||
}
|
||||
|
||||
take_snapshot("Split to parts");
|
||||
take_snapshot(_u8L("Split to parts"));
|
||||
|
||||
volume->split(filament_cnt, wxGetApp().app_config->get_bool("keep_painting"));
|
||||
|
||||
@@ -3028,7 +3038,7 @@ void ObjectList::merge(bool to_multipart_object)
|
||||
GetSelections(sels);
|
||||
assert(!sels.IsEmpty());
|
||||
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Assemble");
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _u8L("Assemble"));
|
||||
|
||||
get_object_idxs(obj_idxs, sels);
|
||||
|
||||
@@ -3176,7 +3186,7 @@ void ObjectList::merge(bool to_multipart_object)
|
||||
if (obj_idx == -1)
|
||||
return;
|
||||
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Merge parts to an object");
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _u8L("Merge parts to an object"));
|
||||
|
||||
ModelObject* model_object = (*m_objects)[obj_idx];
|
||||
model_object->merge();
|
||||
@@ -3243,7 +3253,7 @@ void ObjectList::layers_editing()
|
||||
// set some default value
|
||||
if (ranges.empty()) {
|
||||
// BBS: remove snapshot name "Add layers"
|
||||
take_snapshot("Add layers");
|
||||
take_snapshot(_u8L("Add layers"));
|
||||
ranges[{ 0.0f, 2.0f }].assign_config(get_default_layer_config(obj_idx));
|
||||
}
|
||||
|
||||
@@ -4234,7 +4244,7 @@ void ObjectList::delete_from_model_and_list(const ItemType type, const int obj_i
|
||||
if (!(type&(itObject|itVolume|itInstance)))
|
||||
return;
|
||||
|
||||
take_snapshot("Delete selected");
|
||||
take_snapshot(_u8L("Delete selected"));
|
||||
|
||||
if (type&itObject) {
|
||||
bool was_cut = object(obj_idx)->is_cut();
|
||||
@@ -4467,7 +4477,7 @@ void ObjectList::remove()
|
||||
UnselectAll();
|
||||
update_selection(sels, sels_mode, m_objects_model);
|
||||
|
||||
Plater::TakeSnapshot snapshot = Plater::TakeSnapshot(wxGetApp().plater(), "Delete selected");
|
||||
Plater::TakeSnapshot snapshot = Plater::TakeSnapshot(wxGetApp().plater(), _u8L("Delete selected"));
|
||||
|
||||
for (auto& item : sels)
|
||||
{
|
||||
@@ -5525,7 +5535,7 @@ void ObjectList::change_part_type()
|
||||
return;
|
||||
}
|
||||
|
||||
take_snapshot("Change part type");
|
||||
take_snapshot(_u8L("Change part type"));
|
||||
|
||||
volume->set_type(new_type);
|
||||
wxDataViewItemArray sel = reorder_volumes_and_get_selection(obj_idx, [volume](const ModelVolume* vol) { return vol == volume; });
|
||||
@@ -5799,7 +5809,7 @@ void ObjectList::set_volume_type(ModelVolumeType new_type)
|
||||
}
|
||||
}
|
||||
|
||||
take_snapshot("Change part type");
|
||||
take_snapshot(_u8L("Change part type"));
|
||||
|
||||
std::set<const ModelVolume*> changed_volumes;
|
||||
std::set<int> touched_objects;
|
||||
@@ -6033,7 +6043,7 @@ void ObjectList::split_instances()
|
||||
if (obj_idx == -1)
|
||||
return;
|
||||
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Instances to Separated Objects");
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _u8L("Instances to Separated Objects"));
|
||||
|
||||
if (selection.is_single_full_object())
|
||||
{
|
||||
@@ -6164,7 +6174,7 @@ void ObjectList::fix_through_cgal()
|
||||
return true;
|
||||
};
|
||||
|
||||
Plater::TakeSnapshot snapshot(plater, "Repairing model object");
|
||||
Plater::TakeSnapshot snapshot(plater, _u8L("Repairing model object"));
|
||||
|
||||
// Open a progress dialog.
|
||||
ProgressDialog progress_dlg(_L("Repairing model object"), "", 100, find_toplevel_parent(plater),
|
||||
@@ -6495,7 +6505,7 @@ void ObjectList::OnEditingStarted(wxDataViewEvent &event)
|
||||
else if (col == colSinking) {
|
||||
Plater * plater = wxGetApp().plater();
|
||||
GLCanvas3D *cnv = plater->canvas3D();
|
||||
Plater::TakeSnapshot(plater, "Shift objects to bed");
|
||||
Plater::TakeSnapshot(plater, _u8L("Shift objects to bed"));
|
||||
int obj_idx, vol_idx;
|
||||
get_selected_item_indexes(obj_idx, vol_idx, item);
|
||||
(*m_objects)[obj_idx]->ensure_on_bed();
|
||||
@@ -6582,7 +6592,7 @@ void ObjectList::set_extruder_for_selected_items(const int extruder)
|
||||
if (sels.empty())
|
||||
return;
|
||||
|
||||
take_snapshot("Change Filaments");
|
||||
take_snapshot(_u8L("Change Filaments"));
|
||||
|
||||
for (const wxDataViewItem& sel_item : sels)
|
||||
{
|
||||
|
||||
@@ -2975,7 +2975,7 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors, floa
|
||||
ImGui::SameLine();
|
||||
m_imgui->disabled_begin(is_cut_plane_init && !has_connectors);
|
||||
if (m_imgui->button(_L("Reset"), _L("Reset cutting plane and remove connectors"))) {
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Reset Cut", UndoRedo::SnapshotType::GizmoAction);
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _u8L("Reset Cut"), UndoRedo::SnapshotType::GizmoAction);
|
||||
reset_cut_plane();
|
||||
reset_connectors();
|
||||
}
|
||||
@@ -3500,10 +3500,10 @@ static void check_objects_after_cut(const ModelObjectPtrs& objects)
|
||||
wxString names = from_u8(err_objects_names[0]);
|
||||
for (size_t i = 1; i < err_objects_names.size(); i++)
|
||||
names += ", " + from_u8(err_objects_names[i]);
|
||||
WarningDialog(wxGetApp().plater(), format_wxstr("Objects(%1%) have duplicated connectors. "
|
||||
WarningDialog(wxGetApp().plater(), format_wxstr(_L("Objects(%1%) have duplicated connectors. "
|
||||
"Some connectors may be missing in slicing result.\n"
|
||||
"Please report to PrusaSlicer team in which scenario this issue happened.\n"
|
||||
"Thank you.", names)).ShowModal();
|
||||
"Thank you."), names)).ShowModal();
|
||||
}
|
||||
|
||||
void synchronize_model_after_cut(Model& model, const CutObjectBase& cut_id)
|
||||
|
||||
@@ -229,15 +229,32 @@ orientation::OrientMesh OrientJob::get_orient_mesh(ModelInstance* instance)
|
||||
auto obj = instance->get_object();
|
||||
om.name = obj->name;
|
||||
om.mesh = obj->mesh(); // don't know the difference to obj->raw_mesh(). Both seem OK
|
||||
const Slic3r::DynamicPrintConfig& config = wxGetApp().preset_bundle->full_config();
|
||||
if (obj->config.has("support_threshold_angle"))
|
||||
om.overhang_angle = obj->config.opt_int("support_threshold_angle");
|
||||
else {
|
||||
const Slic3r::DynamicPrintConfig& config = wxGetApp().preset_bundle->full_config();
|
||||
om.overhang_angle = config.opt_int("support_threshold_angle");
|
||||
}
|
||||
|
||||
if (config.has("fan_direction") && config.has("auxiliary_fan")) {
|
||||
FanDirection config_dir = config.option<ConfigOptionEnum<FanDirection>>("fan_direction")->value;
|
||||
if (config_dir == FanDirection::fdUndefine || !config.opt_bool("auxiliary_fan")) {
|
||||
// no part cooling airflow to face, keep the orientation around the z axis unchanged
|
||||
om.cooling_direction = {0, 0, 0};
|
||||
} else if (config_dir == FanDirection::fdRight) {
|
||||
// the part cooling airflow comes from the right side
|
||||
om.cooling_direction = {1, 0, 0};
|
||||
om.has_cooling_fan = true;
|
||||
} else {
|
||||
// the part cooling airflow comes from the left side, or from both sides
|
||||
om.cooling_direction = {-1, 0, 0};
|
||||
om.has_cooling_fan = true;
|
||||
}
|
||||
}
|
||||
|
||||
om.setter = [instance](const OrientMesh& p) {
|
||||
instance->rotate(p.rotation_matrix);
|
||||
instance->rotate(p.rotation_matrix_vertical);
|
||||
instance->get_object()->invalidate_bounding_box();
|
||||
instance->get_object()->ensure_on_bed();
|
||||
};
|
||||
|
||||
@@ -3735,7 +3735,7 @@ void MainFrame::load_config_file()
|
||||
// return;
|
||||
wxFileDialog dlg(this, _L("Select profile to load:"),
|
||||
!m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(),
|
||||
"config.json", "Config files (*.json;*.zip;*.orca_printer;*.orca_bundle;*.orca_filament)|*.json;*.zip;*.orca_printer;*.orca_bundle;*.orca_filament", wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST);
|
||||
"config.json", _L("Config files (*.json;*.zip;*.orca_printer;*.orca_bundle;*.orca_filament)|*.json;*.zip;*.orca_printer;*.orca_bundle;*.orca_filament"), wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST);
|
||||
wxArrayString files;
|
||||
if (dlg.ShowModal() != wxID_OK)
|
||||
return;
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -156,6 +156,7 @@ public:
|
||||
|
||||
|
||||
void jump_to_HMS();
|
||||
void jump_to_Upgrade();
|
||||
void jump_to_LiveView();
|
||||
void update_network_version_footer();
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -315,6 +319,18 @@ std::vector<int> PartPlate::get_real_filament_maps(const DynamicConfig& g_config
|
||||
return g_maps;
|
||||
}
|
||||
|
||||
std::vector<int> PartPlate::get_real_filament_volume_maps(const DynamicConfig& g_config, bool* use_global_param) const
|
||||
{
|
||||
auto maps = get_filament_volume_maps();
|
||||
if (!maps.empty()) {
|
||||
if (use_global_param) { *use_global_param = false; }
|
||||
return maps;
|
||||
}
|
||||
auto g_maps = g_config.option<ConfigOptionInts>("filament_volume_map")->values;
|
||||
if (use_global_param) { *use_global_param = true; }
|
||||
return g_maps;
|
||||
}
|
||||
|
||||
FilamentMapMode PartPlate::get_real_filament_map_mode(const DynamicConfig& g_config, bool* use_global_param) const
|
||||
{
|
||||
auto mode = get_filament_map_mode();
|
||||
@@ -1903,6 +1919,17 @@ int PartPlate::get_physical_extruder_by_filament_id(const DynamicConfig& g_confi
|
||||
return the_map->values[zero_base_logical_idx];
|
||||
}
|
||||
|
||||
int PartPlate::get_logical_extruder_by_filament_id(const DynamicConfig& g_config, int idx) const
|
||||
{
|
||||
const std::vector<int>& filament_map = get_real_filament_maps(g_config);
|
||||
if (idx <= 0 || idx > (int)filament_map.size())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return filament_map[idx - 1] - 1;
|
||||
}
|
||||
|
||||
std::vector<int> PartPlate::get_used_filaments()
|
||||
{
|
||||
std::vector<int> used_filaments;
|
||||
@@ -3506,8 +3533,16 @@ int PartPlate::load_gcode_from_file(const std::string& filename)
|
||||
int ret = 0;
|
||||
|
||||
// process gcode
|
||||
auto& preset_bundle = wxGetApp().preset_bundle;
|
||||
std::vector<int> filament_maps = this->get_filament_maps();
|
||||
DynamicPrintConfig full_config = wxGetApp().preset_bundle->full_config(false, filament_maps);
|
||||
// Inject the plate's volume map (or the per-extruder defaults) exactly like the apply-time
|
||||
// composition, so the config applied over the loaded slice result matches the next
|
||||
// background-process apply and does not invalidate the embedded g-code.
|
||||
std::vector<int> f_volume_maps = this->get_filament_volume_maps();
|
||||
if (f_volume_maps.empty()) {
|
||||
f_volume_maps = preset_bundle->get_default_nozzle_volume_types_for_filaments(filament_maps);
|
||||
}
|
||||
DynamicPrintConfig full_config = preset_bundle->full_config(false, filament_maps, f_volume_maps);
|
||||
full_config.apply(m_config, true);
|
||||
m_print->apply(*m_model, full_config, false);
|
||||
//BBS: need to apply two times, for after the first apply, the m_print got its object,
|
||||
@@ -3814,6 +3849,40 @@ void PartPlate::clear_filament_map()
|
||||
m_config.erase("filament_map");
|
||||
}
|
||||
|
||||
std::vector<int> PartPlate::get_filament_volume_maps() const
|
||||
{
|
||||
std::string key = "filament_volume_map";
|
||||
if (m_config.has(key))
|
||||
return m_config.option<ConfigOptionInts>(key)->values;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void PartPlate::set_filament_volume_maps(const std::vector<int>& f_maps)
|
||||
{
|
||||
m_config.option<ConfigOptionInts>("filament_volume_map", true)->values = f_maps;
|
||||
}
|
||||
|
||||
void PartPlate::clear_filament_volume_map()
|
||||
{
|
||||
if (m_config.has("filament_volume_map"))
|
||||
m_config.erase("filament_volume_map");
|
||||
}
|
||||
|
||||
std::vector<int> PartPlate::get_filament_nozzle_maps() const
|
||||
{
|
||||
std::string key = "filament_nozzle_map";
|
||||
if (m_config.has(key))
|
||||
return m_config.option<ConfigOptionInts>(key)->values;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void PartPlate::set_filament_nozzle_maps(const std::vector<int>& f_maps)
|
||||
{
|
||||
m_config.option<ConfigOptionInts>("filament_nozzle_map", true)->values = f_maps;
|
||||
}
|
||||
|
||||
void PartPlate::clear_filament_map_mode()
|
||||
{
|
||||
if (m_config.has("filament_map_mode"))
|
||||
@@ -3840,6 +3909,16 @@ void PartPlate::set_filament_count(int filament_count)
|
||||
std::vector<int>& filament_maps = m_config.option<ConfigOptionInts>("filament_map")->values;
|
||||
filament_maps.resize(filament_count, 1);
|
||||
}
|
||||
|
||||
if (m_config.has("filament_nozzle_map")) {
|
||||
std::vector<int>& filament_nozzle_map = m_config.option<ConfigOptionInts>("filament_nozzle_map")->values;
|
||||
filament_nozzle_map.resize(filament_count, 0);
|
||||
}
|
||||
|
||||
if (m_config.has("filament_volume_map")) {
|
||||
std::vector<int>& filament_volume_map = m_config.option<ConfigOptionInts>("filament_volume_map")->values;
|
||||
filament_volume_map.resize(filament_count, static_cast<int>(NozzleVolumeType::nvtStandard));
|
||||
}
|
||||
}
|
||||
|
||||
void PartPlate::on_filament_added()
|
||||
@@ -3848,6 +3927,27 @@ void PartPlate::on_filament_added()
|
||||
std::vector<int>& filament_maps = m_config.option<ConfigOptionInts>("filament_map")->values;
|
||||
filament_maps.push_back(1);
|
||||
}
|
||||
|
||||
if (m_config.has("filament_nozzle_map")) {
|
||||
std::vector<int>& filament_nozzle_map = m_config.option<ConfigOptionInts>("filament_nozzle_map")->values;
|
||||
filament_nozzle_map.push_back(0);
|
||||
}
|
||||
|
||||
if (m_config.has("filament_volume_map")) {
|
||||
std::vector<int>& filament_volume_map = m_config.option<ConfigOptionInts>("filament_volume_map")->values;
|
||||
// A new filament defaults onto the first extruder, so seed its volume value from
|
||||
// that extruder's flow type.
|
||||
int volume_type = static_cast<int>(NozzleVolumeType::nvtStandard);
|
||||
auto nozzle_volumes = wxGetApp().preset_bundle->project_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type");
|
||||
if (nozzle_volumes && !nozzle_volumes->values.empty())
|
||||
volume_type = nozzle_volumes->values[0];
|
||||
// Orca: never store the Hybrid marker as a per-filament value; on a Hybrid extruder
|
||||
// each filament still prints with a concrete flow, defaulting to Standard.
|
||||
if (volume_type == static_cast<int>(NozzleVolumeType::nvtHybrid))
|
||||
volume_type = static_cast<int>(NozzleVolumeType::nvtStandard);
|
||||
|
||||
filament_volume_map.push_back(volume_type);
|
||||
}
|
||||
}
|
||||
|
||||
void PartPlate::on_filament_deleted(int filament_count, int filament_id)
|
||||
@@ -3860,6 +3960,19 @@ void PartPlate::on_filament_deleted(int filament_count, int filament_id)
|
||||
if (filament_id >= 0 && filament_id < (int) filament_maps.size())
|
||||
filament_maps.erase(filament_maps.begin() + filament_id);
|
||||
}
|
||||
|
||||
if (m_config.has("filament_nozzle_map")) {
|
||||
std::vector<int>& filament_nozzle_map = m_config.option<ConfigOptionInts>("filament_nozzle_map")->values;
|
||||
if (filament_id >= 0 && filament_id < (int) filament_nozzle_map.size())
|
||||
filament_nozzle_map.erase(filament_nozzle_map.begin() + filament_id);
|
||||
}
|
||||
|
||||
if (m_config.has("filament_volume_map")) {
|
||||
std::vector<int>& filament_volume_map = m_config.option<ConfigOptionInts>("filament_volume_map")->values;
|
||||
if (filament_id >= 0 && filament_id < (int) filament_volume_map.size())
|
||||
filament_volume_map.erase(filament_volume_map.begin() + filament_id);
|
||||
}
|
||||
|
||||
update_first_layer_print_sequence_when_delete_filament(filament_id);
|
||||
}
|
||||
|
||||
@@ -4225,6 +4338,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();
|
||||
@@ -4241,7 +4360,14 @@ void PartPlateList::set_default_wipe_tower_pos_for_plate(int plate_idx, bool ini
|
||||
coordf_t plate_bbox_y_max_local_coord = plate_bbox_2d.max(1) - plate_origin(1);
|
||||
|
||||
std::vector<int> filament_maps = part_plate->get_real_filament_maps(proj_cfg);
|
||||
DynamicPrintConfig full_config = wxGetApp().preset_bundle->full_config(false, filament_maps);
|
||||
// Keep this composition consistent with the apply-time injection (plate volume map, else
|
||||
// per-extruder defaults); the config below currently only feeds scalar reads, but a
|
||||
// divergent volume map would silently mis-resolve any future per-filament read here.
|
||||
std::vector<int> f_volume_maps = part_plate->get_filament_volume_maps();
|
||||
if (f_volume_maps.empty()) {
|
||||
f_volume_maps = wxGetApp().preset_bundle->get_default_nozzle_volume_types_for_filaments(filament_maps);
|
||||
}
|
||||
DynamicPrintConfig full_config = wxGetApp().preset_bundle->full_config(false, filament_maps, f_volume_maps);
|
||||
const DynamicPrintConfig &print_cfg = wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
||||
float w = dynamic_cast<const ConfigOptionFloat *>(print_cfg.option("prime_tower_width"))->value;
|
||||
float v = dynamic_cast<const ConfigOptionFloat *>(full_config.option("prime_volume"))->value;
|
||||
@@ -6283,6 +6409,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)) {
|
||||
@@ -6442,9 +6620,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, " ");
|
||||
@@ -6455,6 +6634,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, " ");
|
||||
@@ -6475,6 +6664,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);
|
||||
@@ -6498,6 +6688,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");
|
||||
@@ -6508,6 +6701,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");
|
||||
|
||||
@@ -253,6 +253,7 @@ public:
|
||||
PrintSequence get_real_print_seq(bool* plate_same_as_global=nullptr) const;
|
||||
|
||||
std::vector<int> get_real_filament_maps(const DynamicConfig& g_config, bool* use_global_param = nullptr)const;
|
||||
std::vector<int> get_real_filament_volume_maps(const DynamicConfig& g_config, bool* use_global_param = nullptr) const;
|
||||
FilamentMapMode get_real_filament_map_mode(const DynamicConfig& g_config,bool * use_global_param = nullptr) const;
|
||||
|
||||
FilamentMapMode get_filament_map_mode() const;
|
||||
@@ -262,6 +263,15 @@ public:
|
||||
std::vector<int> get_filament_maps() const;
|
||||
void set_filament_maps(const std::vector<int>& f_maps);
|
||||
|
||||
// per-filament nozzle-volume choice (NozzleVolumeType values, 0 based filament ids)
|
||||
std::vector<int> get_filament_volume_maps() const;
|
||||
void set_filament_volume_maps(const std::vector<int>& f_maps);
|
||||
void clear_filament_volume_map();
|
||||
|
||||
// per-filament nozzle-group choice (0 based filament and nozzle ids)
|
||||
std::vector<int> get_filament_nozzle_maps() const;
|
||||
void set_filament_nozzle_maps(const std::vector<int>& f_maps);
|
||||
|
||||
void clear_filament_map();
|
||||
void clear_filament_map_mode();
|
||||
|
||||
@@ -340,6 +350,7 @@ public:
|
||||
std::vector<int> get_used_filaments();
|
||||
const std::vector<FilamentInfo>& get_slice_filaments_info() const { return slice_filaments_info; }
|
||||
int get_physical_extruder_by_filament_id(const DynamicConfig& g_config, int idx) const;
|
||||
int get_logical_extruder_by_filament_id(const DynamicConfig& g_config, int idx) const;
|
||||
bool check_filament_printable(const DynamicPrintConfig & config, wxString& error_message);
|
||||
bool check_tpu_printable_status(const DynamicPrintConfig & config, const std::vector<int> &tpu_filaments);
|
||||
bool check_mixture_of_pla_and_petg(const DynamicPrintConfig & config);
|
||||
@@ -668,6 +679,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;
|
||||
}
|
||||
|
||||
+918
-67
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
@@ -195,6 +196,8 @@ public:
|
||||
std::map<int, DynamicPrintConfig> build_filament_ams_list(MachineObject* obj);
|
||||
void sync_ams_list(bool is_from_big_sync_btn = false);
|
||||
bool sync_extruder_list();
|
||||
bool is_fila_switch_ready();
|
||||
void reset_fila_switch();
|
||||
bool need_auto_sync_extruder_list_after_connect_priner(const MachineObject* obj);
|
||||
void update_sync_status(const MachineObject* obj);
|
||||
int get_sidebar_pos_right_x();
|
||||
@@ -205,6 +208,10 @@ public:
|
||||
// Orca
|
||||
static bool should_show_SEMM_buttons();
|
||||
void show_SEMM_buttons();
|
||||
void enable_purge_mode_btn(bool enable);
|
||||
// Sidebar nozzle-count badge on the extruder cards (multi-nozzle printers only).
|
||||
void set_extruder_nozzle_count(int extruder_id, int nozzle_count);
|
||||
void enable_nozzle_count_edit(bool enable);
|
||||
void update_dynamic_filament_list();
|
||||
|
||||
PlaterPresetComboBox * printer_combox();
|
||||
@@ -519,6 +526,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);
|
||||
@@ -576,7 +586,9 @@ public:
|
||||
|
||||
void set_global_filament_map_mode(FilamentMapMode mode);
|
||||
void set_global_filament_map(const std::vector<int>& filament_map);
|
||||
void set_global_filament_volume_map(const std::vector<int>& filament_volume_map);
|
||||
std::vector<int> get_global_filament_map() const;
|
||||
std::vector<int> get_global_filament_volume_map() const;
|
||||
FilamentMapMode get_global_filament_map_mode() const;
|
||||
|
||||
void update_menus();
|
||||
@@ -738,6 +750,11 @@ public:
|
||||
|
||||
void update_machine_sync_status();
|
||||
|
||||
// Rewrite every plate's per-filament volume choice for the filaments grouped onto this
|
||||
// extruder after its Flow type changed (Hybrid resets them to Standard so the user
|
||||
// re-assigns concrete volumes in the grouping dialog).
|
||||
void update_filament_volume_map(int extruder_id, int volume_type);
|
||||
|
||||
#if ENABLE_ENVIRONMENT_MAP
|
||||
void init_environment_texture();
|
||||
unsigned int get_environment_texture_id() const;
|
||||
|
||||
@@ -36,6 +36,9 @@ 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 PrintStatusFilaSwitcherError: return "PrintStatusFilaSwitcherError";
|
||||
case PrintStatusColorQuantityExceed: return "PrintStatusColorQuantityExceed";
|
||||
// Handle filament errors
|
||||
case PrintStatusAmsOnSettingup: return "PrintStatusAmsOnSettingup";
|
||||
@@ -47,13 +50,21 @@ 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 PrintStatusFilaSwitcherSlicingNotMatch: return "PrintStatusFilaSwitcherSlicingNotMatch";
|
||||
case PrintStatusWarningKvalueNotUsed: return "PrintStatusWarningKvalueNotUsed";
|
||||
case PrintStatusHasFilamentInBlackListWarning: return "PrintStatusHasFilamentInBlackListWarning";
|
||||
case PrintStatusFilamentWarningHighChamberTemp: return "PrintStatusFilamentWarningHighChamberTemp";
|
||||
case PrintStatusFilamentWarningHighChamberTempCloseDoor: return "PrintStatusFilamentWarningHighChamberTempCloseDoor";
|
||||
case PrintStatusFilamentWarningHighChamberTempSoft: return "PrintStatusFilamentWarningHighChamberTempSoft";
|
||||
case PrintStatusFilamentWarningUnknownHighChamberTempSoft: return "PrintStatusFilamentWarningUnknownHighChamberTempSoft";
|
||||
case PrintStatusNozzleNoMatchedHotends: return "PrintStatusNozzleNoMatchedHotends";
|
||||
case PrintStatusNozzleRackMaximumInstalled: return "PrintStatusNozzleRackMaximumInstalled";
|
||||
case PrintStatusRackReading: return "PrintStatusRackReading";
|
||||
case PrintStatusRackNozzleNumUnmeetWarning: return "PrintStatusRackNozzleNumUnmeetWarning";
|
||||
case PrintStatusHasUnreliableNozzleWarning: return "PrintStatusHasUnreliableNozzleWarning";
|
||||
case PrintStatusWarningExtFilamentNotMatch: return "PrintStatusWarningExtFilamentNotMatch";
|
||||
case PrintStatusFilamentWarningNozzleHRC: return "PrintStatusFilamentWarningNozzleHRC";
|
||||
case PrintStatusReadingFinished: return "PrintStatusReadingFinished";
|
||||
case PrintStatusSendingCanceled: return "PrintStatusSendingCanceled";
|
||||
case PrintStatusAmsMappingSuccess: return "PrintStatusAmsMappingSuccess";
|
||||
|
||||
@@ -55,6 +55,8 @@ enum PrintDialogStatus : unsigned int {
|
||||
PrintStatusNozzleDataInvalid,
|
||||
PrintStatusNozzleDiameterMismatch,
|
||||
PrintStatusNozzleTypeMismatch,
|
||||
PrintStatusNozzleNoMatchedHotends,
|
||||
PrintStatusNozzleRackMaximumInstalled,
|
||||
PrintStatusRefreshingMachineList,
|
||||
PrintStatusSending,
|
||||
PrintStatusLanModeNoSdcard,
|
||||
@@ -65,6 +67,10 @@ enum PrintDialogStatus : unsigned int {
|
||||
PrintStatusNotSupportedPrintAll,
|
||||
PrintStatusBlankPlate,
|
||||
PrintStatusUnsupportedPrinter,
|
||||
PrintStatusRackNozzleMappingWaiting,
|
||||
PrintStatusRackNozzleMappingError,
|
||||
PrintStatusRackReading,
|
||||
PrintStatusFilaSwitcherError,
|
||||
PrintStatusPrinterErrorEnd,
|
||||
|
||||
// Errors for filament, Block Print
|
||||
@@ -89,6 +95,10 @@ enum PrintDialogStatus : unsigned int {
|
||||
PrintStatusTimelapseWarning,
|
||||
PrintStatusMixAmsAndVtSlotWarning,
|
||||
PrintStatusToolHeadCoolingFanWarning,
|
||||
PrintStatusRackNozzleMappingWarning,
|
||||
PrintStatusFilaSwitcherSlicingNotMatch,
|
||||
PrintStatusRackNozzleNumUnmeetWarning,
|
||||
PrintStatusHasUnreliableNozzleWarning,
|
||||
PrintStatusPrinterWarningEnd,
|
||||
|
||||
// Warnings for filament
|
||||
@@ -100,6 +110,7 @@ enum PrintDialogStatus : unsigned int {
|
||||
PrintStatusFilamentWarningHighChamberTempSoft,
|
||||
PrintStatusFilamentWarningUnknownHighChamberTempSoft,
|
||||
PrintStatusWarningExtFilamentNotMatch,
|
||||
PrintStatusFilamentWarningNozzleHRC,
|
||||
PrintStatusFilamentWarningEnd,
|
||||
|
||||
PrintStatusWarningEnd,//->end error<-
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <set>
|
||||
#include <utility>
|
||||
#include <algorithm>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
@@ -510,8 +512,14 @@ bool PresetComboBox::add_ams_filaments(std::string selected, bool alias_name)
|
||||
bool selected_in_ams = false;
|
||||
bool is_bbl_vendor_preset = m_preset_bundle->is_bbl_vendor();
|
||||
if (is_bbl_vendor_preset && !m_preset_bundle->filament_ams_list.empty()) {
|
||||
// When a filament track switch is installed and calibrated, every AMS filament is reachable
|
||||
// from both extruders, so present one deduplicated group instead of the Left/Right split.
|
||||
bool fila_switch_ready = wxGetApp().sidebar().is_fila_switch_ready();
|
||||
bool dual_extruder = (m_preset_bundle->filament_ams_list.begin()->first & 0x10000) == 0;
|
||||
set_label_marker(Append(dual_extruder ? _L("Left filaments") : _L("AMS filament"), wxNullBitmap, DD_ITEM_STYLE_SPLIT_ITEM));
|
||||
if (fila_switch_ready)
|
||||
set_label_marker(Append(_L("AMS filaments"), wxNullBitmap, DD_ITEM_STYLE_SPLIT_ITEM));
|
||||
else
|
||||
set_label_marker(Append(dual_extruder ? _L("Left filaments") : _L("AMS filament"), wxNullBitmap, DD_ITEM_STYLE_SPLIT_ITEM));
|
||||
m_first_ams_filament = GetCount();
|
||||
auto &filaments = m_collection->get_presets();
|
||||
|
||||
@@ -523,8 +531,12 @@ bool PresetComboBox::add_ams_filaments(std::string selected, bool alias_name)
|
||||
icon_width = 32;
|
||||
}
|
||||
|
||||
// Deduplicate by (tray_name, filament_id) so a filament shared by both extruders is
|
||||
// listed once when the switch is ready. Uses Orca's tray naming/lookup, not BBS's.
|
||||
std::set<std::pair<std::string, std::string>> added_filaments;
|
||||
|
||||
for (auto &entry : m_preset_bundle->filament_ams_list) {
|
||||
if (dual_extruder && (entry.first & 0x10000)) {
|
||||
if (!fila_switch_ready && dual_extruder && (entry.first & 0x10000)) {
|
||||
dual_extruder = false;
|
||||
set_label_marker(Append(_L("Right filaments"), wxNullBitmap, DD_ITEM_STYLE_SPLIT_ITEM));
|
||||
}
|
||||
@@ -535,6 +547,13 @@ bool PresetComboBox::add_ams_filaments(std::string selected, bool alias_name)
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1% 's filament_id is empty.") % name;
|
||||
continue;
|
||||
}
|
||||
if (fila_switch_ready) {
|
||||
// skip the external spool and collapse duplicates shared across both extruders
|
||||
if (name == "Ext")
|
||||
continue;
|
||||
if (!added_filaments.insert(std::make_pair(name, filament_id)).second)
|
||||
continue;
|
||||
}
|
||||
auto iter = std::find_if(filaments.begin(), filaments.end(),
|
||||
[&filament_id, this](auto &f) { return f.is_compatible && m_collection->get_preset_base(f) == &f && f.filament_id == filament_id; });
|
||||
if (iter == filaments.end()) {
|
||||
@@ -1080,7 +1099,7 @@ void PlaterPresetComboBox::show_edit_menu()
|
||||
#ifdef __linux__
|
||||
// To edit extruder color from the sidebar
|
||||
if (m_type == Preset::TYPE_FILAMENT) {
|
||||
append_menu_item(menu, wxID_ANY, _devL("Change extruder color"), "",
|
||||
append_menu_item(menu, wxID_ANY, _L("Change extruder color"), "",
|
||||
[this](wxCommandEvent&) { this->change_extruder_color(); }, "blank_14", menu, []() { return true; }, wxGetApp().plater());
|
||||
wxGetApp().plater()->PopupMenu(menu);
|
||||
return;
|
||||
@@ -1307,7 +1326,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<wxString> filament_orders = {"Bambu PLA Basic", "Bambu PLA Matte", "Bambu PETG HF", "Bambu ABS", "Bambu PLA Silk", "Bambu PLA-CF",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ void PrinterCloudAuthDialog::OnScriptMessage(wxWebViewEvent& evt)
|
||||
}
|
||||
Close();
|
||||
} catch (std::exception& e) {
|
||||
wxMessageBox(e.what(), "parse json failed", wxICON_WARNING);
|
||||
wxMessageBox(e.what(), _L("parse json failed"), wxICON_WARNING);
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
#include "PurgeModeDialog.hpp"
|
||||
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/statbmp.h>
|
||||
#include <wx/dcbuffer.h>
|
||||
#include <wx/graphics.h>
|
||||
|
||||
#include "I18N.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
#include "Widgets/Button.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
static const wxColour BgNormalColor = wxColour("#FFFFFF");
|
||||
static const wxColour BgSelectColor = wxColour("#EBF9F0");
|
||||
|
||||
static const wxColour BorderNormalColor = wxColour("#CECECE");
|
||||
static const wxColour BorderSelectedColor = wxColour("#00AE42");
|
||||
|
||||
static const wxColour TextNormalBlackColor = wxColour("#262E30");
|
||||
static const wxColour TextNormalGreyColor = wxColour("#6B6B6B");
|
||||
|
||||
PurgeModeDialog::PurgeModeDialog(wxWindow *parent, PurgeModeDialogType dialog_type)
|
||||
: DPIDialog(parent, wxID_ANY, _L("Purge Mode Settings"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE), m_dialog_type(dialog_type)
|
||||
{
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
SetMinSize(wxSize(FromDIP(520), FromDIP(320)));
|
||||
SetMaxSize(wxSize(FromDIP(520), FromDIP(320)));
|
||||
std::string icon_path = (boost::format("%1%/images/OrcaSlicerTitle.ico") % resources_dir()).str();
|
||||
SetIcon(wxIcon(encode_path(icon_path.c_str()), wxBITMAP_TYPE_ICO));
|
||||
|
||||
auto main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
auto options_panel = new wxPanel(this);
|
||||
auto options_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
auto panels_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
auto &preset_bundle = *wxGetApp().preset_bundle;
|
||||
auto current_mode = preset_bundle.project_config.option<ConfigOptionEnum<PrimeVolumeMode>>("prime_volume_mode");
|
||||
PrimeVolumeMode mode = current_mode ? current_mode->value : pvmDefault;
|
||||
m_selected_mode = mode;
|
||||
|
||||
bool is_fast_mode = (m_dialog_type == PurgeModeDialogType::FastMode);
|
||||
|
||||
m_standard_panel = new PurgeModeBtnPanel(options_panel, _L("Standard"), _L("Perform full purging with speed and temperature transition for the best print quality."),
|
||||
"shield");
|
||||
m_standard_panel->SetMinSize(wxSize(FromDIP(200), FromDIP(150)));
|
||||
m_standard_panel->Select(mode == pvmDefault);
|
||||
m_standard_panel->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &) { select_option(pvmDefault); });
|
||||
panels_sizer->Add(m_standard_panel, 1, wxEXPAND | wxRIGHT, FromDIP(12));
|
||||
if (is_fast_mode) {
|
||||
m_saving_panel = new PurgeModeBtnPanel(options_panel, _L("Fast"),
|
||||
_L("Uses optimized purge temperature, multiplier and stronger extrusion for faster purging. May cause slight color mixing in some extreme cases."), "leaf");
|
||||
m_saving_panel->SetMinSize(wxSize(FromDIP(200), FromDIP(150)));
|
||||
m_saving_panel->Select(mode == pvmFast);
|
||||
m_saving_panel->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &) { select_option(pvmFast); });
|
||||
} else {
|
||||
m_saving_panel = new PurgeModeBtnPanel(options_panel, _L("Prime Saving"),
|
||||
_L("Reduces prime waste and prints faster. May cause slight color mixing or small surface defects."), "leaf");
|
||||
m_saving_panel->SetMinSize(wxSize(FromDIP(200), FromDIP(150)));
|
||||
m_saving_panel->Select(mode == pvmSaving);
|
||||
m_saving_panel->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &) { select_option(pvmSaving); });
|
||||
}
|
||||
|
||||
panels_sizer->Add(m_saving_panel, 1, wxEXPAND | wxLEFT, FromDIP(12));
|
||||
|
||||
options_sizer->Add(panels_sizer, 0, wxEXPAND | wxALL, FromDIP(20));
|
||||
|
||||
options_panel->SetSizer(options_sizer);
|
||||
main_sizer->Add(options_panel, 1, wxEXPAND);
|
||||
|
||||
auto btn_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
btn_sizer->AddStretchSpacer();
|
||||
|
||||
auto ok_btn = new Button(this, _L("Confirm"));
|
||||
ok_btn->SetStyle(ButtonStyle::Confirm, ButtonType::Compact);
|
||||
ok_btn->SetId(wxID_OK);
|
||||
|
||||
auto cancel_btn = new Button(this, _L("Cancel"));
|
||||
cancel_btn->SetStyle(ButtonStyle::Regular, ButtonType::Compact);
|
||||
cancel_btn->SetId(wxID_CANCEL);
|
||||
|
||||
btn_sizer->Add(ok_btn, 0, wxRIGHT, FromDIP(12));
|
||||
btn_sizer->Add(cancel_btn, 0);
|
||||
|
||||
main_sizer->Add(btn_sizer, 0, wxEXPAND | wxALL, FromDIP(20));
|
||||
|
||||
SetSizer(main_sizer);
|
||||
Fit();
|
||||
CenterOnParent();
|
||||
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
}
|
||||
|
||||
void PurgeModeDialog::select_option(PrimeVolumeMode mode)
|
||||
{
|
||||
m_selected_mode = mode;
|
||||
update_panel_selection();
|
||||
}
|
||||
|
||||
void PurgeModeDialog::update_panel_selection()
|
||||
{
|
||||
if (m_selected_mode == pvmDefault) {
|
||||
m_standard_panel->Select(true);
|
||||
m_saving_panel->Select(false);
|
||||
} else {
|
||||
m_standard_panel->Select(false);
|
||||
m_saving_panel->Select(true);
|
||||
}
|
||||
}
|
||||
|
||||
void PurgeModeDialog::on_dpi_changed(const wxRect &suggested_rect)
|
||||
{
|
||||
const int &em = em_unit();
|
||||
|
||||
msw_buttons_rescale(this, em, {wxID_OK, wxID_CANCEL});
|
||||
|
||||
const wxSize &size = wxSize(70 * em, 32 * em);
|
||||
SetMinSize(size);
|
||||
|
||||
Fit();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
PurgeModeBtnPanel::PurgeModeBtnPanel(wxWindow *parent, const wxString &label, const wxString &detail, const std::string &icon_path) : wxPanel(parent)
|
||||
{
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
|
||||
const int horizontal_margin = FromDIP(12);
|
||||
|
||||
auto sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
icon = create_scaled_bitmap(icon_path, nullptr, 20);
|
||||
m_btn = new wxStaticBitmap(this, wxID_ANY, icon, wxDefaultPosition, wxDefaultSize, wxNO_BORDER);
|
||||
|
||||
check_icon = create_scaled_bitmap("completed_2", nullptr, 20);
|
||||
m_check_btn = new wxStaticBitmap(this, wxID_ANY, check_icon, wxDefaultPosition, wxDefaultSize, wxNO_BORDER);
|
||||
m_check_btn->Hide();
|
||||
|
||||
auto icon_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
icon_sizer->Add(m_btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, horizontal_margin);
|
||||
icon_sizer->AddStretchSpacer();
|
||||
icon_sizer->Add(m_check_btn, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, horizontal_margin);
|
||||
|
||||
m_label = new wxStaticText(this, wxID_ANY, label);
|
||||
m_label->SetFont(Label::Head_14);
|
||||
m_label->SetForegroundColour(TextNormalBlackColor);
|
||||
|
||||
auto label_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
label_sizer->Add(m_label, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, horizontal_margin);
|
||||
|
||||
auto detail_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
m_detail = new Label(this, detail);
|
||||
m_detail->SetFont(Label::Body_12);
|
||||
m_detail->SetForegroundColour(TextNormalGreyColor);
|
||||
m_detail->Wrap(FromDIP(200));
|
||||
detail_sizer->Add(m_detail, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, horizontal_margin);
|
||||
|
||||
sizer->AddSpacer(FromDIP(15));
|
||||
sizer->Add(icon_sizer, 0, wxEXPAND);
|
||||
sizer->AddSpacer(FromDIP(10));
|
||||
sizer->Add(label_sizer, 0, wxEXPAND);
|
||||
sizer->AddSpacer(FromDIP(6));
|
||||
sizer->Add(detail_sizer, 0, wxEXPAND);
|
||||
sizer->AddSpacer(FromDIP(10));
|
||||
|
||||
SetSizer(sizer);
|
||||
Layout();
|
||||
Fit();
|
||||
|
||||
wxGetApp().UpdateDarkUIWin(this);
|
||||
|
||||
// Clicks on child widgets must select the whole card, so re-dispatch them on the panel.
|
||||
auto forward_click_to_parent = [this](wxMouseEvent &) {
|
||||
wxCommandEvent click_event(wxEVT_LEFT_DOWN, GetId());
|
||||
click_event.SetEventObject(this);
|
||||
this->ProcessEvent(click_event);
|
||||
};
|
||||
|
||||
m_btn->Bind(wxEVT_LEFT_DOWN, forward_click_to_parent);
|
||||
m_label->Bind(wxEVT_LEFT_DOWN, forward_click_to_parent);
|
||||
m_detail->Bind(wxEVT_LEFT_DOWN, forward_click_to_parent);
|
||||
|
||||
Bind(wxEVT_PAINT, &PurgeModeBtnPanel::OnPaint, this);
|
||||
Bind(wxEVT_ENTER_WINDOW, &PurgeModeBtnPanel::OnEnterWindow, this);
|
||||
Bind(wxEVT_LEAVE_WINDOW, &PurgeModeBtnPanel::OnLeaveWindow, this);
|
||||
}
|
||||
|
||||
void PurgeModeBtnPanel::OnPaint(wxPaintEvent &event)
|
||||
{
|
||||
wxAutoBufferedPaintDC dc(this);
|
||||
wxGraphicsContext *gc = wxGraphicsContext::Create(dc);
|
||||
|
||||
if (gc) {
|
||||
dc.Clear();
|
||||
wxRect rect = GetClientRect();
|
||||
gc->SetBrush(wxTransparentColour);
|
||||
gc->DrawRoundedRectangle(0, 0, rect.width, rect.height, 0);
|
||||
wxColour bg_color = m_selected ? BgSelectColor : BgNormalColor;
|
||||
|
||||
wxColour border_color = m_hover || m_selected ? BorderSelectedColor : BorderNormalColor;
|
||||
|
||||
bg_color = StateColor::darkModeColorFor(bg_color);
|
||||
border_color = StateColor::darkModeColorFor(border_color);
|
||||
gc->SetBrush(wxBrush(bg_color));
|
||||
gc->SetPen(wxPen(border_color, 1));
|
||||
gc->DrawRoundedRectangle(1, 1, rect.width - 2, rect.height - 2, 8);
|
||||
delete gc;
|
||||
}
|
||||
}
|
||||
|
||||
void PurgeModeBtnPanel::UpdateStatus()
|
||||
{
|
||||
if (m_selected) {
|
||||
m_btn->SetBackgroundColour(BgSelectColor);
|
||||
m_label->SetBackgroundColour(BgSelectColor);
|
||||
m_detail->SetBackgroundColour(BgSelectColor);
|
||||
if (m_check_btn) {
|
||||
m_check_btn->SetBackgroundColour(BgSelectColor);
|
||||
m_check_btn->Show();
|
||||
}
|
||||
} else {
|
||||
m_btn->SetBackgroundColour(BgNormalColor);
|
||||
m_label->SetBackgroundColour(BgNormalColor);
|
||||
m_detail->SetBackgroundColour(BgNormalColor);
|
||||
if (m_check_btn) {
|
||||
m_check_btn->SetBackgroundColour(BgNormalColor);
|
||||
m_check_btn->Hide();
|
||||
}
|
||||
}
|
||||
Layout();
|
||||
Refresh();
|
||||
wxGetApp().UpdateDarkUIWin(this);
|
||||
}
|
||||
|
||||
void PurgeModeBtnPanel::OnEnterWindow(wxMouseEvent &event)
|
||||
{
|
||||
if (!m_hover) {
|
||||
m_hover = true;
|
||||
UpdateStatus();
|
||||
Refresh();
|
||||
event.Skip();
|
||||
}
|
||||
}
|
||||
|
||||
void PurgeModeBtnPanel::OnLeaveWindow(wxMouseEvent &event)
|
||||
{
|
||||
if (m_hover) {
|
||||
wxPoint pos = this->ScreenToClient(wxGetMousePosition());
|
||||
if (this->GetClientRect().Contains(pos)) return;
|
||||
m_hover = false;
|
||||
UpdateStatus();
|
||||
Refresh();
|
||||
event.Skip();
|
||||
}
|
||||
}
|
||||
|
||||
void PurgeModeBtnPanel::Select(bool selected)
|
||||
{
|
||||
m_selected = selected;
|
||||
UpdateStatus();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
@@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
|
||||
#include <wx/dialog.h>
|
||||
#include <wx/panel.h>
|
||||
|
||||
#include "GUI_Utils.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "Widgets/Label.hpp"
|
||||
|
||||
class wxStaticText;
|
||||
class wxStaticBitmap;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace GUI {
|
||||
|
||||
class PurgeModeBtnPanel : public wxPanel
|
||||
{
|
||||
public:
|
||||
PurgeModeBtnPanel(wxWindow *parent, const wxString &label, const wxString &detail, const std::string &icon_path);
|
||||
void Select(bool selected);
|
||||
|
||||
protected:
|
||||
void OnPaint(wxPaintEvent &event);
|
||||
|
||||
private:
|
||||
void OnEnterWindow(wxMouseEvent &event);
|
||||
void OnLeaveWindow(wxMouseEvent &event);
|
||||
|
||||
void UpdateStatus();
|
||||
|
||||
wxBitmap icon;
|
||||
wxBitmap check_icon;
|
||||
|
||||
wxStaticBitmap *m_btn;
|
||||
wxStaticBitmap *m_check_btn;
|
||||
wxStaticText *m_label;
|
||||
Label *m_detail;
|
||||
bool m_hover{false};
|
||||
bool m_selected{false};
|
||||
};
|
||||
|
||||
enum class PurgeModeDialogType {
|
||||
MultiNozzle,
|
||||
FastMode
|
||||
};
|
||||
|
||||
class PurgeModeDialog : public DPIDialog
|
||||
{
|
||||
public:
|
||||
PurgeModeDialog(wxWindow *parent, PurgeModeDialogType dialog_type = PurgeModeDialogType::MultiNozzle);
|
||||
|
||||
PrimeVolumeMode get_selected_mode() const { return m_selected_mode; }
|
||||
|
||||
protected:
|
||||
void on_dpi_changed(const wxRect &suggested_rect) override;
|
||||
|
||||
private:
|
||||
void select_option(PrimeVolumeMode mode);
|
||||
void update_panel_selection();
|
||||
|
||||
PurgeModeBtnPanel *m_standard_panel;
|
||||
PurgeModeBtnPanel *m_saving_panel;
|
||||
PrimeVolumeMode m_selected_mode;
|
||||
PurgeModeDialogType m_dialog_type;
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
@@ -1524,7 +1524,7 @@ InputIpAddressDialog::InputIpAddressDialog(wxWindow *parent)
|
||||
m_tip4->SetMaxSize(wxSize(FromDIP(355), -1));
|
||||
|
||||
// ORCA standardized HyperLink
|
||||
m_trouble_shoot = new HyperLink(this, "How to trouble shooting");
|
||||
m_trouble_shoot = new HyperLink(this, _L("How to trouble shooting"));
|
||||
|
||||
m_img_help = new wxStaticBitmap(this, wxID_ANY, create_scaled_bitmap("input_access_code_x1_en", this, 198), wxDefaultPosition, wxSize(FromDIP(355), -1), 0);
|
||||
|
||||
|
||||
+851
-221
File diff suppressed because it is too large
Load Diff
@@ -33,6 +33,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"
|
||||
@@ -62,10 +63,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,
|
||||
@@ -325,6 +324,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;
|
||||
@@ -512,9 +512,40 @@ public:
|
||||
bool build_nozzles_info(std::string& nozzles_info);
|
||||
bool can_hybrid_mapping(DevExtderSystem data);
|
||||
void auto_supply_with_ext(std::vector<DevAmsTray> slots);
|
||||
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;
|
||||
|
||||
// Block Send while a rack printer is still reading its hotend information.
|
||||
bool CheckErrorRackStatus(MachineObject* obj_);
|
||||
// Warn (without blocking) when the rack inventory looks insufficient for the sliced plate:
|
||||
// fewer matching hotends than the plate needs, or matches relying on unreliable nozzle info.
|
||||
void CheckWarningRackStatus(MachineObject* obj_);
|
||||
// Compare the slicing file's nozzle requirements (validity, flow, diameter) against the
|
||||
// printer; the rack extruder is checked against its whole inventory (mounted + rack).
|
||||
bool CheckErrorExtruderNozzleWithSlicing(MachineObject* obj_);//return true if no errors
|
||||
|
||||
// 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;
|
||||
|
||||
// Filament Track Switch: warn when the sliced switch state doesn't match the installed
|
||||
// hardware, and (for dynamic nozzle mapping) block Send when the switch is missing or not
|
||||
// set up. slicing_with_fila_switch() reports whether the file was sliced assuming a switch.
|
||||
bool slicing_with_fila_switch() const;
|
||||
bool CheckErrorDynamicSwitchNozzle(MachineObject* obj_);
|
||||
void on_flow_cali_option_changed();
|
||||
|
||||
bool is_ams_drying(MachineObject* obj);
|
||||
bool is_selected_ams_drying(MachineObject* obj);
|
||||
|
||||
|
||||
@@ -760,6 +760,9 @@ void Selection::clear()
|
||||
#endif
|
||||
|
||||
// #et_FIXME fake KillFocus from sidebar
|
||||
// Skip on shutdown: Plater's pImpl is already freed, so plater()->canvas3D() would use-after-free.
|
||||
if (wxGetApp().is_closing())
|
||||
return;
|
||||
wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false);
|
||||
}
|
||||
|
||||
|
||||
@@ -412,7 +412,7 @@ SendToPrinterDialog::SendToPrinterDialog(Plater *plater)
|
||||
sizer_error_code->Add(st_title_error_code_doc, 0, wxALL, 0);
|
||||
sizer_error_code->Add(m_st_txt_error_code, 0, wxALL, 0);
|
||||
|
||||
auto st_title_error_desc = new wxStaticText(m_sw_print_failed_info, wxID_ANY, wxT("Error desc"));
|
||||
auto st_title_error_desc = new wxStaticText(m_sw_print_failed_info, wxID_ANY, _L("Error desc"));
|
||||
auto st_title_error_desc_doc = new wxStaticText(m_sw_print_failed_info, wxID_ANY, ": ");
|
||||
m_st_txt_error_desc = new Label(m_sw_print_failed_info, wxEmptyString);
|
||||
st_title_error_desc->SetForegroundColour(0x909090);
|
||||
@@ -429,7 +429,7 @@ SendToPrinterDialog::SendToPrinterDialog(Plater *plater)
|
||||
sizer_error_desc->Add(st_title_error_desc_doc, 0, wxALL, 0);
|
||||
sizer_error_desc->Add(m_st_txt_error_desc, 0, wxALL, 0);
|
||||
|
||||
auto st_title_extra_info = new wxStaticText(m_sw_print_failed_info, wxID_ANY, wxT("Extra info"));
|
||||
auto st_title_extra_info = new wxStaticText(m_sw_print_failed_info, wxID_ANY, _L("Extra info"));
|
||||
auto st_title_extra_info_doc = new wxStaticText(m_sw_print_failed_info, wxID_ANY, ": ");
|
||||
m_st_txt_extra_info = new Label(m_sw_print_failed_info, wxEmptyString);
|
||||
st_title_extra_info->SetForegroundColour(0x909090);
|
||||
|
||||
+140
-10
@@ -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();
|
||||
@@ -2227,7 +2258,7 @@ void StatusBasePanel::show_filament_load_group(bool show)
|
||||
}
|
||||
|
||||
auto cur_ext = obj->GetExtderSystem()->GetCurrentExtder();
|
||||
m_filament_step->SetupSteps(cur_ext ? cur_ext->HasFilamentInExt() : false);
|
||||
m_filament_step->SetupSteps(obj, cur_ext ? cur_ext->HasFilamentInExt() : false);
|
||||
|
||||
Layout();
|
||||
Fit();
|
||||
@@ -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();
|
||||
@@ -3413,7 +3471,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);
|
||||
@@ -3462,6 +3527,14 @@ void StatusPanel::update_ams_control_state(std::string ams_id, std::string slot_
|
||||
if (ams_id.empty() || slot_id.empty()) {
|
||||
load_error_info = _L("Choose an AMS slot then press \"Load\" or \"Unload\" button to automatically load or unload filament.");
|
||||
unload_error_info = _L("Choose an AMS slot then press \"Load\" or \"Unload\" button to automatically load or unload filament.");
|
||||
} else if (obj->GetFilaSwitch()->IsInstalled() && devPrinterUtil::IsVirtualSlot(ams_id)) {
|
||||
// Orca: with a Filament Track Switch installed the external spool cannot be routed, so both actions are blocked.
|
||||
load_error_info = _L("\"Load\" or \"Unload\" is not supported for external spool while using Filament Track Switch.");
|
||||
unload_error_info = load_error_info;
|
||||
} else if (obj->GetFilaSwitch()->IsInstalled() && !obj->GetFilaSwitch()->IsReady()) {
|
||||
// Orca: an un-calibrated Filament Track Switch cannot route filament to either extruder, so block both actions.
|
||||
load_error_info = _L("The Filament Track Switch has not been setup. Please setup on printer.");
|
||||
unload_error_info = load_error_info;
|
||||
} else if (ams_id == std::to_string(VIRTUAL_TRAY_MAIN_ID) || ams_id == std::to_string(VIRTUAL_TRAY_DEPUTY_ID)) {
|
||||
for (auto ext : obj->GetExtderSystem()->GetExtruders()) {
|
||||
if (ext.GetSlotNow().ams_id == ams_id && ext.GetSlotNow().slot_id == slot_id)
|
||||
@@ -3471,6 +3544,8 @@ void StatusPanel::update_ams_control_state(std::string ams_id, std::string slot_
|
||||
}
|
||||
} else {
|
||||
for (auto ext : obj->GetExtderSystem()->GetExtruders()) {
|
||||
// Orca: with a Filament Track Switch installed a slot is not bound to a single extruder, so skip the "already loaded" identity check.
|
||||
if (obj->GetFilaSwitch()->IsInstalled()) { continue; }
|
||||
if (ext.GetSlotNow().ams_id == ams_id && ext.GetSlotNow().slot_id == slot_id)
|
||||
{
|
||||
load_error_info = _L("Current slot has already been loaded.");
|
||||
@@ -4167,6 +4242,43 @@ void StatusPanel::on_ams_load_curr()
|
||||
std::string curr_ams_id = m_ams_control->GetCurentAms();
|
||||
std::string curr_can_id = m_ams_control->GetCurrentCan(curr_ams_id);
|
||||
|
||||
std::optional<int> extruder_id = std::nullopt;
|
||||
if (obj->GetFilaSwitch() && obj->GetFilaSwitch()->IsInstalled()) {
|
||||
if (!obj->GetFilaSwitch()->IsReady()) {
|
||||
MessageDialog msg_dlg(nullptr, _L("The Filament Track Switch has not been setup. Please setup on printer."), wxEmptyString, wxICON_WARNING | wxOK);
|
||||
msg_dlg.ShowModal();
|
||||
return;
|
||||
}
|
||||
|
||||
// Filament Track Switch is calibrated: the load can go to either extruder, so ask the
|
||||
// user which. Record each extruder's currently-loaded slot so the dialog can grey out a
|
||||
// side that already holds this filament.
|
||||
std::vector<std::pair<std::string, std::string>> extruderSlots(2, {"", ""});
|
||||
if (auto ext = obj->GetExtderSystem()->GetExtderById(MAIN_EXTRUDER_ID); ext.has_value())
|
||||
{
|
||||
if (ext->HasFilamentInExt())
|
||||
{
|
||||
extruderSlots[MAIN_EXTRUDER_ID] = {ext->GetSlotNow().ams_id, ext->GetSlotNow().slot_id};
|
||||
}
|
||||
}
|
||||
if (auto ext = obj->GetExtderSystem()->GetExtderById(DEPUTY_EXTRUDER_ID); ext.has_value())
|
||||
{
|
||||
if (ext->HasFilamentInExt())
|
||||
{
|
||||
extruderSlots[DEPUTY_EXTRUDER_ID] = {ext->GetSlotNow().ams_id, ext->GetSlotNow().slot_id};
|
||||
}
|
||||
}
|
||||
|
||||
FeedDirectionDialog dialog(nullptr, 2, obj->printer_type);
|
||||
dialog.SetExtruderMapping(obj, curr_ams_id, curr_can_id, extruderSlots);
|
||||
auto rtn = dialog.ShowModal();
|
||||
|
||||
if (rtn != wxID_OK)
|
||||
{
|
||||
return;
|
||||
}
|
||||
extruder_id = dialog.GetExtruderID();
|
||||
}
|
||||
|
||||
update_load_with_temp();
|
||||
//virtual tray
|
||||
@@ -4198,11 +4310,11 @@ void StatusPanel::on_ams_load_curr()
|
||||
if (obj->is_enable_np || obj->is_enable_ams_np) {
|
||||
try {
|
||||
if (!curr_ams_id.empty() && !curr_can_id.empty()) {
|
||||
obj->command_ams_change_filament(true, curr_ams_id, "0", old_temp, new_temp);
|
||||
obj->command_ams_change_filament(true, curr_ams_id, "0", old_temp, new_temp, extruder_id);
|
||||
}
|
||||
} catch (...) {}
|
||||
} else {
|
||||
obj->command_ams_change_filament(true, "254", "0", old_temp, new_temp);
|
||||
obj->command_ams_change_filament(true, "254", "0", old_temp, new_temp, extruder_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4238,12 +4350,12 @@ void StatusPanel::on_ams_load_curr()
|
||||
if (obj->is_enable_np) {
|
||||
try {
|
||||
if (!curr_ams_id.empty() && !curr_can_id.empty()) {
|
||||
obj->command_ams_change_filament(true, curr_ams_id, curr_can_id, old_temp, new_temp);
|
||||
obj->command_ams_change_filament(true, curr_ams_id, curr_can_id, old_temp, new_temp, extruder_id);
|
||||
}
|
||||
}
|
||||
catch (...){}
|
||||
} else {
|
||||
obj->command_ams_change_filament(true, curr_ams_id, curr_can_id, old_temp, new_temp);
|
||||
obj->command_ams_change_filament(true, curr_ams_id, curr_can_id, old_temp, new_temp, extruder_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5086,6 +5198,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();
|
||||
@@ -5263,6 +5378,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();
|
||||
|
||||
|
||||
@@ -5284,6 +5400,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) {
|
||||
@@ -5723,7 +5853,7 @@ wxBoxSizer *ScoreDialog::get_photo_btn_sizer() {
|
||||
|
||||
m_add_photo->Bind(wxEVT_LEFT_DOWN, [this](auto &e) {
|
||||
// add photo logic
|
||||
wxFileDialog openFileDialog(this, "Select Images", "", "", "Image files (*.png;*.jpg;*jpeg)|*.png;*.jpg;*.jpeg", wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_MULTIPLE);
|
||||
wxFileDialog openFileDialog(this, _L("Select Images"), "", "", _L("Image files (*.png;*.jpg;*jpeg)|*.png;*.jpg;*.jpeg"), wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_MULTIPLE);
|
||||
|
||||
if (openFileDialog.ShowModal() == wxID_CANCEL) return;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1527,6 +1527,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";
|
||||
}
|
||||
@@ -1542,6 +1544,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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+139
-56
@@ -46,6 +46,7 @@
|
||||
|
||||
#include "Widgets/ComboBox.hpp"
|
||||
#include "Widgets/Label.hpp"
|
||||
#include "Widgets/MultiNozzleSync.hpp"
|
||||
#include "Widgets/SwitchButton.hpp"
|
||||
#include "Widgets/TabCtrl.hpp"
|
||||
#include "Widgets/ComboBox.hpp"
|
||||
@@ -616,26 +617,25 @@ void Tab::parse_extruder_selection(int selection, int &extruder_id, NozzleVolume
|
||||
for (int i = 0; i < extruder_nums; ++i) {
|
||||
NozzleVolumeType volume_type = NozzleVolumeType(nozzle_volumes->values[i]);
|
||||
|
||||
// TODO: Orca: Support hybrid
|
||||
//if (volume_type == NozzleVolumeType::nvtHybrid) {
|
||||
// if (selection == current_index) {
|
||||
// extruder_id = i;
|
||||
// nozzle_type = NozzleVolumeType::nvtStandard;
|
||||
// return;
|
||||
// } else if (selection == current_index + 1) {
|
||||
// extruder_id = i;
|
||||
// nozzle_type = NozzleVolumeType::nvtHighFlow;
|
||||
// return;
|
||||
// }
|
||||
// current_index += 2;
|
||||
//} else {
|
||||
if (volume_type == NozzleVolumeType::nvtHybrid) {
|
||||
if (selection == current_index) {
|
||||
extruder_id = i;
|
||||
nozzle_type = NozzleVolumeType::nvtStandard;
|
||||
return;
|
||||
} else if (selection == current_index + 1) {
|
||||
extruder_id = i;
|
||||
nozzle_type = NozzleVolumeType::nvtHighFlow;
|
||||
return;
|
||||
}
|
||||
current_index += 2;
|
||||
} else {
|
||||
if (selection == current_index) {
|
||||
extruder_id = i;
|
||||
nozzle_type = volume_type;
|
||||
return;
|
||||
}
|
||||
current_index += 1;
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
extruder_id = 0;
|
||||
@@ -651,17 +651,16 @@ int Tab::calculate_selection_index_for_extruder(int extruder_id, NozzleVolumeTyp
|
||||
|
||||
for (int i = 0; i < extruder_nums; ++i) {
|
||||
if (i == extruder_id) {
|
||||
// TODO: Orca: Support hybrid
|
||||
NozzleVolumeType volume_type = NozzleVolumeType(nozzle_volumes->values[i]);
|
||||
/*if (volume_type == NozzleVolumeType::nvtHybrid) {
|
||||
if (volume_type == NozzleVolumeType::nvtHybrid) {
|
||||
return nozzle_type == NozzleVolumeType::nvtHighFlow ? index + 1 : index;
|
||||
} else*/ {
|
||||
} else {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
NozzleVolumeType volume_type = NozzleVolumeType(nozzle_volumes->values[i]);
|
||||
index += /*(volume_type == NozzleVolumeType::nvtHybrid) ? 2 :*/ 1;
|
||||
index += (volume_type == NozzleVolumeType::nvtHybrid) ? 2 : 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -1847,6 +1846,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\?"),
|
||||
@@ -4935,8 +4955,10 @@ void TabPrinter::build_fff()
|
||||
optgroup->append_single_option_line("nozzle_type", "printer_basic_information_accessory#nozzle-type");
|
||||
optgroup->append_single_option_line("nozzle_hrc", "printer_basic_information_accessory#nozzle-hrc");
|
||||
optgroup->append_single_option_line("auxiliary_fan", "printer_basic_information_accessory#auxiliary-part-cooling-fan");
|
||||
optgroup->append_single_option_line("fan_direction");
|
||||
optgroup->append_single_option_line("support_chamber_temp_control", "printer_basic_information_accessory#support-controlling-chamber-temperature");
|
||||
optgroup->append_single_option_line("support_air_filtration", "printer_basic_information_accessory#support-air-filtration");
|
||||
optgroup->append_single_option_line("cooling_filter_enabled");
|
||||
|
||||
auto edit_custom_gcode_fn = [this](const t_config_option_key& opt_key) { edit_custom_gcode(opt_key); };
|
||||
|
||||
@@ -5622,7 +5644,53 @@ void TabPrinter::on_preset_loaded()
|
||||
if (use_default_nozzle_volume_type) {
|
||||
m_preset_bundle->project_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type")->values = current_printer.config.option<ConfigOptionEnumsGeneric>("default_nozzle_volume_type")->values;
|
||||
}
|
||||
|
||||
// Changing printer model drops any Filament Track Switch context from the previous
|
||||
// printer; clear both device-derived flags so a switch-less printer never inherits a
|
||||
// stale installed/active state (re-derived by device sync when a printer is connected).
|
||||
if (auto* has_switcher = m_preset_bundle->project_config.opt<ConfigOptionBool>("has_filament_switcher"))
|
||||
has_switcher->value = false;
|
||||
if (auto* dynamic_map = m_preset_bundle->project_config.opt<ConfigOptionBool>("enable_filament_dynamic_map"))
|
||||
dynamic_map->value = false;
|
||||
// Orca: also clear the sidebar switcher status icon on printer-model change (mirrors BBS's
|
||||
// reset_fila_switch on machine change); it re-derives from device sync once a printer connects.
|
||||
if (wxGetApp().plater())
|
||||
wxGetApp().plater()->sidebar().reset_fila_switch();
|
||||
}
|
||||
|
||||
// Purge mode selection is only meaningful for printers with multiple sub-nozzles per
|
||||
// extruder (prime saving) or fast-purge support; reset stale project values that the
|
||||
// current printer cannot honor and toggle the sidebar button accordingly.
|
||||
auto extruder_max_nozzle_count = current_printer.config.option<ConfigOptionIntsNullable>("extruder_max_nozzle_count");
|
||||
bool has_multiple_nozzle = extruder_max_nozzle_count &&
|
||||
std::any_of(extruder_max_nozzle_count->values.begin(), extruder_max_nozzle_count->values.end(),
|
||||
[](int v) { return v > 1 && v != ConfigOptionIntsNullable::nil_value(); });
|
||||
auto support_fast_purge_opt = current_printer.config.option<ConfigOptionBool>("support_fast_purge_mode");
|
||||
bool support_fast_purge = support_fast_purge_opt ? support_fast_purge_opt->value : false;
|
||||
bool show_purge_mode = has_multiple_nozzle || support_fast_purge;
|
||||
if (auto *prime_volume_mode = m_preset_bundle->project_config.option<ConfigOptionEnum<PrimeVolumeMode>>("prime_volume_mode")) {
|
||||
if (!show_purge_mode)
|
||||
prime_volume_mode->value = PrimeVolumeMode::pvmDefault;
|
||||
else if (!has_multiple_nozzle && prime_volume_mode->value == PrimeVolumeMode::pvmSaving)
|
||||
prime_volume_mode->value = PrimeVolumeMode::pvmDefault;
|
||||
else if (!support_fast_purge && prime_volume_mode->value == PrimeVolumeMode::pvmFast)
|
||||
prime_volume_mode->value = PrimeVolumeMode::pvmDefault;
|
||||
}
|
||||
if (wxGetApp().plater())
|
||||
wxGetApp().plater()->sidebar().enable_purge_mode_btn(show_purge_mode);
|
||||
|
||||
// Sidebar nozzle-count badge on the extruder cards. The `extruder_nozzle_stats` key is session-only —
|
||||
// saved presets never carry it, so any preset switch rebuilds the edited config without it — so
|
||||
// re-baseline it whenever it is missing (each extruder gets extruder_max_nozzle_count nozzles of its
|
||||
// selected volume type), then refresh the badges. Idempotent, so run on every preset load.
|
||||
if (wxGetApp().plater())
|
||||
wxGetApp().plater()->sidebar().enable_nozzle_count_edit(has_multiple_nozzle);
|
||||
const auto *nozzle_stats = m_preset_bundle->printers.get_edited_preset().config.option<ConfigOptionStrings>("extruder_nozzle_stats");
|
||||
if (nozzle_stats == nullptr || nozzle_stats->values.empty())
|
||||
seedExtruderNozzleStats(m_preset_bundle);
|
||||
if (auto *nozzle_volume_type = m_preset_bundle->project_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type"))
|
||||
for (size_t idx = 0; idx < nozzle_volume_type->values.size(); ++idx)
|
||||
updateNozzleCountDisplay(m_preset_bundle, idx, NozzleVolumeType(nozzle_volume_type->values[idx]));
|
||||
}
|
||||
|
||||
void TabPrinter::update_pages()
|
||||
@@ -5838,6 +5906,12 @@ void TabPrinter::toggle_options()
|
||||
|
||||
const bool support_parallel_printheads = printer_cfg.opt_bool("support_parallel_printheads");
|
||||
toggle_line("parallel_printheads_count", support_parallel_printheads);
|
||||
|
||||
toggle_line("fan_direction", m_config->opt_bool("auxiliary_fan"));
|
||||
|
||||
// The cooling filter and air filtration are alternative accessories: show only the one the printer supports.
|
||||
toggle_line("support_air_filtration", !m_config->opt_bool("support_cooling_filter"));
|
||||
toggle_line("cooling_filter_enabled", m_config->opt_bool("support_cooling_filter"));
|
||||
}
|
||||
|
||||
|
||||
@@ -7440,11 +7514,11 @@ wxSizer* Tab::compatible_widget_create(wxWindow* parent, PresetDependencies &dep
|
||||
}
|
||||
|
||||
if(deps.type == Preset::TYPE_PRINTER){
|
||||
deps.dialog_title = "Compatible printers";
|
||||
deps.dialog_label = "Select printers";
|
||||
deps.dialog_title = _L("Compatible printers");
|
||||
deps.dialog_label = _L("Select printers");
|
||||
}else{
|
||||
deps.dialog_title = "Compatible process profiles";
|
||||
deps.dialog_label = "Select profiles";
|
||||
deps.dialog_title = _L("Compatible process profiles");
|
||||
deps.dialog_label = _L("Select profiles");
|
||||
}
|
||||
|
||||
MultiChoiceDialog dlg(parent, deps.dialog_label, deps.dialog_title, presets);
|
||||
@@ -7491,12 +7565,23 @@ void TabPrinter::set_extruder_volume_type(int extruder_id, NozzleVolumeType type
|
||||
auto nozzle_volumes = m_preset_bundle->project_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type");
|
||||
assert(nozzle_volumes->values.size() > (size_t)extruder_id);
|
||||
nozzle_volumes->values[extruder_id] = type;
|
||||
|
||||
// Carry the extruder's nozzle count over to the new flow type and refresh the sidebar badge.
|
||||
onNozzleVolumeTypeSwitch(m_preset_bundle, extruder_id, type);
|
||||
updateNozzleCountDisplay(m_preset_bundle, extruder_id, type);
|
||||
|
||||
on_value_change((boost::format("nozzle_volume_type#%1%") % extruder_id).str(), int(type));
|
||||
|
||||
//save to app config
|
||||
if (!m_base_preset_name.empty()) {
|
||||
ConfigOptionEnumsGeneric* nozzle_volume_type_option = m_preset_bundle->project_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type");
|
||||
std::string nozzle_volume_type_str = nozzle_volume_type_option->serialize();
|
||||
// do not save hybrid flow status to config: it depends on the nozzles currently installed
|
||||
// on the connected printer, so it must not be restored in a later session
|
||||
ConfigOptionEnumsGeneric nozzle_volume_type_option = *m_preset_bundle->project_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type");
|
||||
for (size_t i = 0; i < nozzle_volume_type_option.values.size(); i++) {
|
||||
if (nozzle_volume_type_option.values[i] == (int) nvtHybrid)
|
||||
nozzle_volume_type_option.values[i] = (int) nvtStandard;
|
||||
}
|
||||
std::string nozzle_volume_type_str = nozzle_volume_type_option.serialize();
|
||||
wxGetApp().app_config->save_nozzle_volume_types_to_config(m_base_preset_name, nozzle_volume_type_str);
|
||||
}
|
||||
|
||||
@@ -7831,12 +7916,11 @@ std::vector<wxString> Tab::generate_extruder_options()
|
||||
wxString extruder_name = _L(DevPrinterConfigUtil::get_toolhead_display_name(
|
||||
pt, ext_id, ToolHeadComponent::Nozzle, ToolHeadNameCase::TitleCase, true));
|
||||
NozzleVolumeType volume_type = NozzleVolumeType(nozzle_volumes->values[i]);
|
||||
|
||||
// TODO: Orca: Support hybrid
|
||||
/*if (volume_type == NozzleVolumeType::nvtHybrid) {
|
||||
|
||||
if (volume_type == NozzleVolumeType::nvtHybrid) {
|
||||
options.push_back(wxString::Format(_L("%s: %s"), extruder_name, _L("Standard")));
|
||||
options.push_back(wxString::Format(_L("%s: %s"), extruder_name, _L("High Flow")));
|
||||
} else*/ {
|
||||
} else {
|
||||
wxString volume_name = get_nozzle_volume_type_name(volume_type);
|
||||
options.push_back(wxString::Format(_L("%s: %s"), extruder_name, volume_name));
|
||||
}
|
||||
@@ -7885,36 +7969,35 @@ bool Tab::get_extruder_sync_enable_state(int extruder_id)
|
||||
if (left_nozzle == right_nozzle) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: Orca: Support hybrid
|
||||
//if (left_nozzle != NozzleVolumeType::nvtHybrid && right_nozzle != NozzleVolumeType::nvtHybrid) {
|
||||
// return false;
|
||||
//}
|
||||
|
||||
//if (left_nozzle == NozzleVolumeType::nvtHybrid && right_nozzle == NozzleVolumeType::nvtHybrid) {
|
||||
// return true;
|
||||
//}
|
||||
if (left_nozzle != NozzleVolumeType::nvtHybrid && right_nozzle != NozzleVolumeType::nvtHybrid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//// Hybrid rules
|
||||
//auto current_nozzle = get_actual_nozzle_volume_type(extruder_id);
|
||||
//if (left_nozzle != NozzleVolumeType::nvtHybrid) {
|
||||
// if (extruder_id == 0) {
|
||||
// return true;
|
||||
// }
|
||||
// if (extruder_id == 1 && current_nozzle == left_nozzle) {
|
||||
// return true;
|
||||
// }
|
||||
// return false;
|
||||
//}
|
||||
//if (right_nozzle != NozzleVolumeType::nvtHybrid) {
|
||||
// if (extruder_id == 1) {
|
||||
// return true;
|
||||
// }
|
||||
// if (extruder_id == 0 && current_nozzle == right_nozzle) {
|
||||
// return true;
|
||||
// }
|
||||
// return false;
|
||||
//}
|
||||
if (left_nozzle == NozzleVolumeType::nvtHybrid && right_nozzle == NozzleVolumeType::nvtHybrid) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Hybrid rules
|
||||
auto current_nozzle = get_actual_nozzle_volume_type(extruder_id);
|
||||
if (left_nozzle != NozzleVolumeType::nvtHybrid) {
|
||||
if (extruder_id == 0) {
|
||||
return true;
|
||||
}
|
||||
if (extruder_id == 1 && current_nozzle == left_nozzle) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (right_nozzle != NozzleVolumeType::nvtHybrid) {
|
||||
if (extruder_id == 1) {
|
||||
return true;
|
||||
}
|
||||
if (extruder_id == 0 && current_nozzle == right_nozzle) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -2281,7 +2281,7 @@ void DiffPresetDialog::update_tree()
|
||||
const Preset* left_preset = presets->find_preset(get_selection(preset_combos.presets_left));
|
||||
const Preset* right_preset = presets->find_preset(get_selection(preset_combos.presets_right));
|
||||
if (!left_preset || !right_preset) {
|
||||
bottom_info = "One of the presets does not exist";
|
||||
bottom_info = _L("One of the presets does not exist");
|
||||
preset_combos.equal_bmp->SetBitmap_(ScalableBitmap(this, "question"));
|
||||
preset_combos.equal_bmp->SetToolTip(bottom_info);
|
||||
continue;
|
||||
@@ -2292,7 +2292,7 @@ void DiffPresetDialog::update_tree()
|
||||
const DynamicPrintConfig& right_congig = right_preset->config;
|
||||
|
||||
if (left_pt != right_preset->printer_technology()) {
|
||||
bottom_info = "Compared presets has different printer technology";
|
||||
bottom_info = _L("Compared presets has different printer technology");
|
||||
preset_combos.equal_bmp->SetBitmap_(ScalableBitmap(this, "question"));
|
||||
preset_combos.equal_bmp->SetToolTip(bottom_info);
|
||||
continue;
|
||||
@@ -2336,7 +2336,7 @@ void DiffPresetDialog::update_tree()
|
||||
wxString left_val = from_u8((boost::format("%1%") % left_config.opt<ConfigOptionStrings>("extruder_colour")->values.size()).str());
|
||||
wxString right_val = from_u8((boost::format("%1%") % right_congig.opt<ConfigOptionStrings>("extruder_colour")->values.size()).str());
|
||||
|
||||
m_tree->Append("extruders_count", type, "General", "Capabilities", local_label, left_val, right_val,
|
||||
m_tree->Append("extruders_count", type, _L("General"), _L("Capabilities"), local_label, left_val, right_val,
|
||||
get_category_icon("Basic information"));
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
@@ -27,6 +28,7 @@ static const std::unordered_map<wxString, wxString> ACCESSORY_DISPLAY_STR = {
|
||||
{"O2L_ACM", "Active Cutting Module"},
|
||||
{"O2L_UCM", "Ultrasonic Cutting Module"},
|
||||
{"O2L-AFP", L("Auto Fire Extinguishing System")},
|
||||
{"O2L-FTS", L("Filament Track Switch")},
|
||||
};
|
||||
|
||||
enum FIRMWARE_STASUS
|
||||
@@ -216,6 +218,10 @@ MachineInfoPanel::MachineInfoPanel(wxWindow* parent, wxWindowID id, const wxPoin
|
||||
createLaserWidgets(m_main_left_sizer);
|
||||
createAirPumpWidgets(m_main_left_sizer);
|
||||
createExtinguishWidgets(m_main_left_sizer);
|
||||
createFilaTrackSwitchWidgets(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);
|
||||
|
||||
@@ -402,6 +408,27 @@ void MachineInfoPanel::createExtinguishWidgets(wxBoxSizer* main_left_sizer)
|
||||
main_left_sizer->Add(m_extinguish_sizer, 0, wxEXPAND, 0);
|
||||
}
|
||||
|
||||
void MachineInfoPanel::createFilaTrackSwitchWidgets(wxBoxSizer* main_left_sizer)
|
||||
{
|
||||
m_filatrack_line_above = new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL);
|
||||
m_filatrack_line_above->SetBackgroundColour(wxColour(206, 206, 206));
|
||||
main_left_sizer->Add(m_filatrack_line_above, 0, wxEXPAND | wxLEFT, FromDIP(40));
|
||||
|
||||
m_filatrack_img = new wxStaticBitmap(this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize(FromDIP(200), FromDIP(200)));
|
||||
m_filatrack_img->SetBitmap(m_img_filatrack.bmp());
|
||||
|
||||
wxBoxSizer* content_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
content_sizer->Add(0, 40, 0, wxEXPAND, FromDIP(5));
|
||||
m_filatrack_version = new uiDeviceUpdateVersion(this, wxID_ANY);
|
||||
content_sizer->Add(m_filatrack_version, 0, wxEXPAND, 0);
|
||||
|
||||
m_filatrack_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
m_filatrack_sizer->Add(m_filatrack_img, 0, wxALIGN_TOP | wxALL, FromDIP(5));
|
||||
m_filatrack_sizer->Add(content_sizer, 1, wxEXPAND, 0);
|
||||
|
||||
main_left_sizer->Add(m_filatrack_sizer, 0, wxEXPAND, 0);
|
||||
}
|
||||
|
||||
void MachineInfoPanel::msw_rescale()
|
||||
{
|
||||
rescale_bitmaps();
|
||||
@@ -431,6 +458,8 @@ 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_filatrack = ScalableBitmap(this, "filament_track_switch", 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 +568,8 @@ void MachineInfoPanel::update(MachineObject* obj)
|
||||
update_cut(obj);
|
||||
update_laszer(obj);
|
||||
update_extinguish(obj);
|
||||
update_filatrack(obj);
|
||||
update_nozzle_rack(obj);
|
||||
|
||||
//update progress
|
||||
int upgrade_percent = obj->get_upgrade_percent();
|
||||
@@ -1123,6 +1154,19 @@ void MachineInfoPanel::update_extinguish(MachineObject* obj)
|
||||
}
|
||||
}
|
||||
|
||||
void MachineInfoPanel::update_filatrack(MachineObject* obj)
|
||||
{
|
||||
if (obj && obj->filatrack_version_info.isValid())
|
||||
{
|
||||
m_filatrack_version->UpdateInfo(obj->filatrack_version_info);
|
||||
show_filatrack(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
show_filatrack(false);
|
||||
}
|
||||
}
|
||||
|
||||
void MachineInfoPanel::show_status(int status, std::string upgrade_status_str)
|
||||
{
|
||||
if (last_status == status && last_status_str == upgrade_status_str) return;
|
||||
@@ -1257,6 +1301,90 @@ void MachineInfoPanel::show_extinguish(bool show)
|
||||
}
|
||||
}
|
||||
|
||||
void MachineInfoPanel::show_filatrack(bool show)
|
||||
{
|
||||
if (m_filatrack_version->IsShown() != show)
|
||||
{
|
||||
m_filatrack_img->Show(show);
|
||||
m_filatrack_line_above->Show(show);
|
||||
m_filatrack_version->Show(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()
|
||||
{
|
||||
|
||||
@@ -134,6 +134,19 @@ protected:
|
||||
wxStaticLine* m_extinguish_line_above = nullptr;;
|
||||
uiDeviceUpdateVersion* m_extinguish_version = nullptr;
|
||||
|
||||
/* filament track switch info*/
|
||||
wxBoxSizer* m_filatrack_sizer = nullptr;
|
||||
wxStaticBitmap* m_filatrack_img = nullptr;
|
||||
wxStaticLine* m_filatrack_line_above = nullptr;
|
||||
uiDeviceUpdateVersion* m_filatrack_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 +168,8 @@ protected:
|
||||
ScalableBitmap m_img_cutting;
|
||||
ScalableBitmap m_img_laser;
|
||||
ScalableBitmap m_img_extinguish;
|
||||
ScalableBitmap m_img_filatrack;
|
||||
ScalableBitmap m_img_nozzle_rack;
|
||||
ScalableBitmap upgrade_gray_icon;
|
||||
ScalableBitmap upgrade_green_icon;
|
||||
ScalableBitmap upgrade_yellow_icon;
|
||||
@@ -212,16 +227,24 @@ private:
|
||||
void createCuttingWidgets(wxBoxSizer* main_left_sizer);
|
||||
void createLaserWidgets(wxBoxSizer* main_left_sizer);
|
||||
void createExtinguishWidgets(wxBoxSizer* main_left_sizer);
|
||||
void createFilaTrackSwitchWidgets(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_filatrack(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_filatrack(bool show = true);
|
||||
void show_nozzle_rack(bool show = true);
|
||||
|
||||
void on_nozzle_rack_update(wxCommandEvent& event);
|
||||
};
|
||||
|
||||
//enum UpgradeMode {
|
||||
|
||||
@@ -506,7 +506,7 @@ void ZUserLogin::OnScriptMessage(wxWebViewEvent &evt)
|
||||
return;
|
||||
}
|
||||
} catch (std::exception &e) {
|
||||
wxMessageBox(e.what(), "parse json failed", wxICON_WARNING);
|
||||
wxMessageBox(e.what(), _L("parse json failed"), wxICON_WARNING);
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,11 @@
|
||||
|
||||
#include "slic3r/GUI/DeviceCore/DevManager.h"
|
||||
#include "slic3r/GUI/DeviceCore/DevFilaSystem.h"
|
||||
#include "slic3r/GUI/DeviceCore/DevFilaSwitch.h"
|
||||
|
||||
#include <wx/simplebook.h>
|
||||
#include <wx/dcgraph.h>
|
||||
#include <wx/artprov.h>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
@@ -40,7 +42,7 @@ AMSControl::AMSControl(wxWindow *parent, wxWindowID id, const wxPoint &pos, cons
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
// normal mode
|
||||
//Freeze();
|
||||
wxBoxSizer *m_sizer_body = new wxBoxSizer(wxVERTICAL);
|
||||
m_sizer_body = new wxBoxSizer(wxVERTICAL);
|
||||
m_amswin = new wxWindow(this, wxID_ANY);
|
||||
m_amswin->SetBackgroundColour(*wxWHITE);
|
||||
m_amswin->SetSize(wxSize(FromDIP(578), -1));
|
||||
@@ -152,6 +154,12 @@ AMSControl::AMSControl(wxWindow *parent, wxWindowID id, const wxPoint &pos, cons
|
||||
m_extruder = new AMSextruder(m_amswin, wxID_ANY, m_total_ext_count, wxDefaultPosition, AMS_EXTRUDER_SIZE);
|
||||
m_sizer_option_mid->Add( m_extruder, 0, wxALIGN_CENTER, 0 );
|
||||
|
||||
// Orca: filament-switch routing glyph; hidden by default so it stays inert (zero layout impact)
|
||||
// on any printer without a Filament Track Switch. Shown from UpdateAms only when installed.
|
||||
m_switcher = new SwitcherImage(m_amswin, wxID_ANY, "fila_switch", wxSize(FromDIP(29), FromDIP(16)), wxDefaultPosition);
|
||||
m_switcher->Hide();
|
||||
m_sizer_option_mid->Add(m_switcher, 0, wxALIGN_CENTER | wxLEFT, FromDIP(6));
|
||||
|
||||
|
||||
/*option right*/
|
||||
m_button_extruder_feed = new Button(m_panel_option_right, _L("Load"));
|
||||
@@ -1017,6 +1025,32 @@ void AMSControl::UpdateAms(const std::string &series_name,
|
||||
{
|
||||
m_amswin->Layout();
|
||||
}
|
||||
|
||||
/*update switch status*/
|
||||
// Orca: inert on any printer without a Filament Track Switch — install is false, so the banner is
|
||||
// never materialized, the glyph stays hidden, and every ShowRoad(true) below is a no-op.
|
||||
const auto [install, ready] = isFilaSwitchReady();
|
||||
show_switcher_status(install && (!ready));
|
||||
bool isShow = install && m_total_ext_count >= 2;
|
||||
if (m_switcher->IsShown() != isShow)
|
||||
{
|
||||
m_switcher->Show(isShow);
|
||||
m_sizer_body->Layout();
|
||||
m_sizer_body->Fit(this);
|
||||
this->Layout();
|
||||
this->Refresh(true);
|
||||
this->Update();
|
||||
}
|
||||
|
||||
/*update ext road visibility when fila switch installed*/
|
||||
bool road_visibility_changed = false;
|
||||
for (auto& [ams_id, ams_item] : m_ams_item_list) {
|
||||
if (!ams_item) continue;
|
||||
// Orca: drop the external-spool road (AMSModel::EXT_AMS) while the switch routes all filament.
|
||||
const bool should_show_road = !(install && ams_item->get_ams_model() == AMSModel::EXT_AMS);
|
||||
if (ams_item->ShowRoad(should_show_road)) { road_visibility_changed = true; }
|
||||
}
|
||||
if (road_visibility_changed) { m_amswin->Layout(); }
|
||||
}
|
||||
|
||||
void AMSControl::AddAmsPreview(AMSinfo info, AMSModel type)
|
||||
@@ -1638,6 +1672,54 @@ void AMSControl::on_ams_setting_click(wxMouseEvent &event)
|
||||
post_event(SimpleEvent(EVT_AMS_SETTINGS));
|
||||
}
|
||||
|
||||
std::tuple<bool, bool> AMSControl::isFilaSwitchReady()
|
||||
{
|
||||
DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
|
||||
if (!dev) return {false, false};
|
||||
MachineObject* obj = dev->get_selected_machine();
|
||||
if (!obj) return {false, false};
|
||||
// Orca: GetFilaSwitch() is a raw non-null accessor (BBS returns a shared_ptr).
|
||||
DevFilaSwitch* fila_switch = obj->GetFilaSwitch();
|
||||
if (fila_switch)
|
||||
{
|
||||
return {fila_switch->IsInstalled(), fila_switch->IsReady()};
|
||||
}
|
||||
return {false, false};
|
||||
}
|
||||
|
||||
void AMSControl::show_switcher_status(bool show)
|
||||
{
|
||||
// Orca: never materialize the banner on printers that never request it, so the AMS control is
|
||||
// byte-identical without a Filament Track Switch (UpdateAms calls this with show=false every refresh).
|
||||
if (!show && tipPanel == nullptr) { return; }
|
||||
|
||||
if (tipPanel == nullptr)
|
||||
{
|
||||
m_sizer_body->Add(0, 0, 1, wxEXPAND | wxTOP, FromDIP(5));
|
||||
tipPanel = new wxPanel(m_amswin);
|
||||
tipPanel->SetBackgroundColour(wxColour(255, 153, 0));
|
||||
tipSizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
tipPanel->SetSizer(tipSizer);
|
||||
icon = new wxStaticBitmap(tipPanel, wxID_ANY,
|
||||
wxArtProvider::GetBitmap(wxART_INFORMATION, wxART_MESSAGE_BOX, wxSize(FromDIP(16), FromDIP(16))));
|
||||
tipSizer->Add(icon, 0, wxALL, FromDIP(8));
|
||||
tipText = new wxStaticText(tipPanel, wxID_ANY, _L("AMS has not been initialized. Please initialize it before use."));
|
||||
tipText->SetForegroundColour(wxColour(255, 255, 255));
|
||||
tipText->SetFont(wxFont(10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD));
|
||||
tipText->Wrap(-1);
|
||||
tipText->SetMinSize(wxSize(-1, -1));
|
||||
tipSizer->Add(tipText, 0, wxALL | wxALIGN_CENTER_VERTICAL | wxEXPAND, FromDIP(8));
|
||||
m_sizer_body->Add(tipPanel, 1, wxEXPAND, 0);
|
||||
}
|
||||
if (tipPanel->IsShown() == show)
|
||||
{
|
||||
return;
|
||||
}
|
||||
tipPanel->Show(show);
|
||||
m_amswin->Layout();
|
||||
m_amswin->Fit();
|
||||
}
|
||||
|
||||
void AMSControl::parse_object(MachineObject* obj) {
|
||||
if (!obj || obj->GetFilaSystem()->GetAmsList().size() == 0)
|
||||
{
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <wx/hyperlink.h>
|
||||
#include <wx/animate.h>
|
||||
#include <wx/dynarray.h>
|
||||
#include <tuple>
|
||||
|
||||
#include "slic3r/GUI/DeviceCore/DevExtruderSystem.h"
|
||||
#include "slic3r/GUI/AMSDryControl.hpp"
|
||||
@@ -52,8 +53,16 @@ protected:
|
||||
|
||||
int m_total_ext_count = 1;
|
||||
AMSextruder *m_extruder{nullptr};
|
||||
SwitcherImage *m_switcher{nullptr}; // Orca: filament-switch routing glyph (hidden unless a switch is installed)
|
||||
AMSRoadDownPart* m_down_road{ nullptr };
|
||||
|
||||
// Orca: "AMS not initialized" warning banner, materialized lazily only when a switch needs it (see show_switcher_status)
|
||||
wxBoxSizer* m_sizer_body{nullptr};
|
||||
wxPanel* tipPanel{nullptr};
|
||||
wxBoxSizer* tipSizer{nullptr};
|
||||
wxStaticBitmap* icon{nullptr};
|
||||
wxStaticText* tipText{nullptr};
|
||||
|
||||
/*items*/
|
||||
wxBoxSizer* m_sizer_ams_items{nullptr};
|
||||
wxScrolledWindow* m_panel_prv_left {nullptr};
|
||||
@@ -153,6 +162,11 @@ public:
|
||||
void StopRridLoading(wxString amsid, wxString canid);
|
||||
void ShowFilamentTip(bool hasams = true);
|
||||
|
||||
// Orca: Filament Track Switch awareness. isFilaSwitchReady() resolves the selected machine and
|
||||
// returns {installed, ready}; show_switcher_status() toggles the "AMS not initialized" banner.
|
||||
std::tuple<bool, bool> isFilaSwitchReady();
|
||||
void show_switcher_status(bool show);
|
||||
|
||||
void UpdatePassRoad(string ams_id, AMSPassRoadType type, AMSPassRoadSTEP step);
|
||||
void CreateAms();
|
||||
void CreateAmsDoubleNozzle(const std::string &series_name, const std::string& printer_type);
|
||||
|
||||
@@ -742,6 +742,63 @@ void AMSExtImage::doRender(wxDC& dc)
|
||||
}
|
||||
|
||||
|
||||
// Orca: SwitcherImage — routing glyph drawn when a Filament Track Switch is installed.
|
||||
void SwitcherImage::paintEvent(wxPaintEvent &evt)
|
||||
{
|
||||
wxPaintDC dc(this);
|
||||
render(dc);
|
||||
}
|
||||
|
||||
void SwitcherImage::render(wxDC &dc)
|
||||
{
|
||||
#ifdef __WXMSW__
|
||||
wxSize size = GetSize();
|
||||
wxMemoryDC memdc;
|
||||
wxBitmap bmp(size.x, size.y);
|
||||
memdc.SelectObject(bmp);
|
||||
memdc.Blit({0, 0}, size, &dc, {0, 0});
|
||||
|
||||
{
|
||||
wxGCDC dc2(memdc);
|
||||
doRender(dc2);
|
||||
}
|
||||
|
||||
memdc.SelectObject(wxNullBitmap);
|
||||
dc.DrawBitmap(bmp, 0, 0);
|
||||
#else
|
||||
doRender(dc);
|
||||
#endif
|
||||
}
|
||||
|
||||
void SwitcherImage::doRender(wxDC &dc)
|
||||
{
|
||||
auto size = GetSize();
|
||||
if (m_show_state){
|
||||
dc.SetPen(*wxTRANSPARENT_PEN);
|
||||
dc.SetBrush(*wxWHITE);
|
||||
dc.DrawBitmap(m_switcher.bmp(), wxPoint((size.x - m_switcher.GetBmpSize().x) / 2, 0));
|
||||
}
|
||||
Layout();
|
||||
}
|
||||
|
||||
SwitcherImage::SwitcherImage(wxWindow *parent, wxWindowID id, string file_name, const wxSize& size, const wxPoint &pos)
|
||||
{
|
||||
wxWindow::Create(parent, id, pos, size);
|
||||
SetBackgroundColour(StateColor::darkModeColorFor(*wxWHITE));
|
||||
m_show_state = true;
|
||||
m_switcher = ScalableBitmap(this, file_name, 16);
|
||||
m_file_name = file_name;
|
||||
SetSize(size);
|
||||
SetMinSize(size);
|
||||
SetMaxSize(size);
|
||||
|
||||
|
||||
Bind(wxEVT_PAINT, &SwitcherImage::paintEvent, this);
|
||||
}
|
||||
|
||||
SwitcherImage::~SwitcherImage() {}
|
||||
|
||||
|
||||
//DevAms Extruder
|
||||
AMSextruder::AMSextruder(wxWindow *parent, wxWindowID id, int nozzle_num, const wxPoint &pos, const wxSize &size)
|
||||
{
|
||||
@@ -3577,6 +3634,18 @@ void AmsItem::doRender(wxDC& dc)
|
||||
}
|
||||
}
|
||||
|
||||
// Orca: toggle the road segment below the item (m_panel_road). Used to drop the external-spool
|
||||
// road when a Filament Track Switch is installed. No-op (returns false) when already in state.
|
||||
bool AmsItem::ShowRoad(bool show)
|
||||
{
|
||||
if (!m_panel_road) return false;
|
||||
if (m_panel_road->IsShown() == show) return false;
|
||||
m_panel_road->Show(show);
|
||||
Layout();
|
||||
Refresh();
|
||||
return true;
|
||||
}
|
||||
|
||||
void AmsItem::RenderLiteRoad(wxDC& dc, wxSize size) {
|
||||
auto end_top = size.x - FromDIP(3);
|
||||
if (m_panel_pos == AMSPanelPos::RIGHT_PANEL){
|
||||
@@ -3706,4 +3775,342 @@ void AmsItem::show_sn_value(bool show)
|
||||
}
|
||||
}
|
||||
|
||||
DevExtruderImage::DevExtruderImage(wxWindow *parent, wxWindowID id, int extruder_num, const wxPoint &pos, const wxSize &size) : wxWindow(parent, id, pos, wxDefaultSize), m_extruder_num(extruder_num)
|
||||
{
|
||||
// wxWindow::Create(parent, id, pos, wxSize(FromDIP(45), FromDIP(112)));
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
SetSize(wxSize(FromDIP(48), FromDIP(112)));
|
||||
SetMinSize(wxSize(FromDIP(48), FromDIP(112)));
|
||||
SetMaxSize(wxSize(FromDIP(48), FromDIP(112)));
|
||||
|
||||
|
||||
m_left_extruder_active_filled = new ScalableBitmap(this, "left_extruder_active_filled", 62);
|
||||
m_left_extruder_active_empty = new ScalableBitmap(this, "left_extruder_active_empty", 62);
|
||||
m_left_extruder_unactive_filled = new ScalableBitmap(this, "left_extruder_unactive_filled", 62);
|
||||
m_left_extruder_unactive_empty = new ScalableBitmap(this, "left_extruder_unactive_empty", 62);
|
||||
m_right_extruder_active_filled = new ScalableBitmap(this, "right_extruder_active_filled", 62);
|
||||
m_right_extruder_active_empty = new ScalableBitmap(this, "right_extruder_active_empty", 62);
|
||||
m_right_extruder_unactive_filled = new ScalableBitmap(this, "right_extruder_unactive_filled", 62);
|
||||
m_right_extruder_unactive_empty = new ScalableBitmap(this, "right_extruder_unactive_empty", 62);
|
||||
|
||||
m_extruder_single_nozzle_empty_load = new ScalableBitmap(this, "monitor_extruder_empty_load", 106);
|
||||
m_extruder_single_nozzle_empty_unload = new ScalableBitmap(this, "monitor_extruder_empty_unload", 106);
|
||||
m_extruder_single_nozzle_filled_load = new ScalableBitmap(this, "monitor_extruder_filled_load", 106);
|
||||
m_extruder_single_nozzle_filled_unload = new ScalableBitmap(this, "monitor_extruder_filled_unload", 106);
|
||||
|
||||
Bind(wxEVT_PAINT, &DevExtruderImage::paintEvent, this);
|
||||
}
|
||||
|
||||
void DevExtruderImage::msw_rescale()
|
||||
{
|
||||
m_left_extruder_active_filled->msw_rescale();
|
||||
m_left_extruder_active_empty->msw_rescale();
|
||||
m_left_extruder_unactive_filled->msw_rescale();
|
||||
m_left_extruder_unactive_empty->msw_rescale();
|
||||
m_right_extruder_active_filled->msw_rescale();
|
||||
m_right_extruder_active_empty->msw_rescale();
|
||||
m_right_extruder_unactive_filled->msw_rescale();
|
||||
m_right_extruder_unactive_empty->msw_rescale();
|
||||
|
||||
m_extruder_single_nozzle_empty_load->msw_rescale();
|
||||
m_extruder_single_nozzle_empty_unload->msw_rescale();
|
||||
m_extruder_single_nozzle_filled_load->msw_rescale();
|
||||
m_extruder_single_nozzle_filled_unload->msw_rescale();
|
||||
Layout();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void DevExtruderImage::render(wxDC &dc)
|
||||
{
|
||||
#ifdef __WXMSW__
|
||||
wxSize size = GetSize();
|
||||
wxMemoryDC memdc;
|
||||
wxBitmap bmp(size.x, size.y);
|
||||
memdc.SelectObject(bmp);
|
||||
memdc.Blit({0, 0}, size, &dc, {0, 0});
|
||||
|
||||
{
|
||||
wxGCDC dc2(memdc);
|
||||
doRender(dc2);
|
||||
}
|
||||
|
||||
memdc.SelectObject(wxNullBitmap);
|
||||
dc.DrawBitmap(bmp, 0, 0);
|
||||
#else
|
||||
doRender(dc);
|
||||
#endif
|
||||
}
|
||||
|
||||
void DevExtruderImage::doRender(wxDC &dc)
|
||||
{
|
||||
auto size = GetSize();
|
||||
auto pot = wxPoint(size.x / 2, (size.y - m_left_extruder_active_filled->GetBmpSize().y) / 2);
|
||||
|
||||
if (m_extruder_num >= 2)
|
||||
{
|
||||
ScalableBitmap *left_extruder_bmp{nullptr};
|
||||
ScalableBitmap *right_extruder_bmp{nullptr};
|
||||
|
||||
switch (m_right_ext_state)
|
||||
{
|
||||
case DevExtruderState::FILLED_LOAD:
|
||||
right_extruder_bmp = current_extruder_loc == "right" ? m_right_extruder_active_filled : m_right_extruder_unactive_filled;
|
||||
break;
|
||||
case DevExtruderState::FILLED_UNLOAD:
|
||||
right_extruder_bmp = current_extruder_loc == "right" ? m_right_extruder_active_filled : m_right_extruder_unactive_filled;
|
||||
break;
|
||||
case DevExtruderState::EMPTY_LOAD:
|
||||
if (current_extruder_loc.empty())
|
||||
{
|
||||
right_extruder_bmp = m_right_extruder_active_empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
right_extruder_bmp = current_extruder_loc == "right" ? m_right_extruder_active_empty : m_right_extruder_unactive_empty;
|
||||
}
|
||||
break;
|
||||
case DevExtruderState::EMPTY_UNLOAD:
|
||||
right_extruder_bmp = current_extruder_loc == "right" ? m_right_extruder_active_empty : m_right_extruder_unactive_empty;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
switch (m_left_ext_state)
|
||||
{
|
||||
case DevExtruderState::FILLED_LOAD:
|
||||
left_extruder_bmp = current_extruder_loc == "left" ? m_left_extruder_active_filled : m_left_extruder_unactive_filled;
|
||||
break;
|
||||
case DevExtruderState::FILLED_UNLOAD:
|
||||
left_extruder_bmp = current_extruder_loc == "left" ? m_left_extruder_active_filled : m_left_extruder_unactive_filled;
|
||||
break;
|
||||
case DevExtruderState::EMPTY_LOAD:
|
||||
if (current_extruder_loc.empty())
|
||||
{
|
||||
left_extruder_bmp = m_left_extruder_active_empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
left_extruder_bmp = current_extruder_loc == "left" ? m_left_extruder_active_empty : m_left_extruder_unactive_empty;
|
||||
}
|
||||
break;
|
||||
case DevExtruderState::EMPTY_UNLOAD:
|
||||
left_extruder_bmp = current_extruder_loc == "left" ? m_left_extruder_active_empty : m_left_extruder_unactive_empty;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (left_extruder_bmp) { dc.DrawBitmap(left_extruder_bmp->bmp(), pot.x - left_extruder_bmp->GetBmpWidth(), pot.y); }
|
||||
if (right_extruder_bmp) { dc.DrawBitmap(right_extruder_bmp->bmp(), pot.x, pot.y); }
|
||||
}
|
||||
else
|
||||
{
|
||||
ScalableBitmap *extruder_bmp = nullptr;
|
||||
switch (m_single_ext_state)
|
||||
{
|
||||
case DevExtruderState::FILLED_LOAD:
|
||||
extruder_bmp = m_extruder_single_nozzle_filled_load;
|
||||
break;
|
||||
case DevExtruderState::FILLED_UNLOAD:
|
||||
extruder_bmp = m_extruder_single_nozzle_filled_unload;
|
||||
break;
|
||||
case DevExtruderState::EMPTY_LOAD:
|
||||
extruder_bmp = m_extruder_single_nozzle_empty_load;
|
||||
break;
|
||||
case DevExtruderState::EMPTY_UNLOAD:
|
||||
extruder_bmp = m_extruder_single_nozzle_empty_unload;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (extruder_bmp) { dc.DrawBitmap(extruder_bmp->bmp(), pot.x - extruder_bmp->GetBmpWidth() / 2, (size.y - extruder_bmp->GetBmpHeight()) / 2); }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
FeedDirectionDialog::FeedDirectionDialog(wxWindow* parent,
|
||||
const int extruderNum,
|
||||
const std::string& printer_type)
|
||||
: wxDialog(parent, wxID_ANY, "", wxDefaultPosition, wxDefaultSize),
|
||||
m_extruder_num(extruderNum),
|
||||
m_printer_type(printer_type)
|
||||
{
|
||||
SetBackgroundColour(wxColour("#FFFFFF"));
|
||||
SetMaxSize(wxSize(FromDIP(360), FromDIP(207)));
|
||||
SetMinSize(wxSize(FromDIP(360), FromDIP(207)));
|
||||
SetSize(wxSize(FromDIP(360), FromDIP(207)));
|
||||
|
||||
wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
wxGridSizer* topSizer = new wxGridSizer (1, 3, FromDIP(5), 0);
|
||||
|
||||
m_radioHelper = new wxRadioButton(this, wxID_ANY, wxT(""), wxDefaultPosition, wxDefaultSize, wxRB_GROUP);
|
||||
m_leftRadio = new wxRadioButton(this, wxID_ANY,
|
||||
_L(DevPrinterConfigUtil::get_toolhead_display_name(m_printer_type, DEPUTY_EXTRUDER_ID, ToolHeadComponent::Extruder, ToolHeadNameCase::TitleCase, true)));
|
||||
m_rightRadio = new wxRadioButton(this, wxID_ANY,
|
||||
_L(DevPrinterConfigUtil::get_toolhead_display_name(m_printer_type, MAIN_EXTRUDER_ID, ToolHeadComponent::Extruder, ToolHeadNameCase::TitleCase, true)));
|
||||
m_radioHelper->Show(false);
|
||||
m_radioHelper->SetCanFocus(false);
|
||||
|
||||
m_leftRadio->SetForegroundColour(*wxBLACK);
|
||||
m_rightRadio->SetForegroundColour(*wxBLACK);
|
||||
|
||||
topSizer->Add(m_leftRadio, 0, wxALIGN_CENTER | wxALL, FromDIP(20));
|
||||
m_extruderImage = new DevExtruderImage(this, wxID_ANY, m_extruder_num);
|
||||
topSizer->Add(m_extruderImage, 0, wxALIGN_CENTER | wxALL, FromDIP(5));
|
||||
topSizer->Add(m_rightRadio, 0, wxALIGN_CENTER | wxALL, FromDIP(20));
|
||||
|
||||
mainSizer->AddStretchSpacer(1);
|
||||
mainSizer->Add(topSizer, 1, wxEXPAND);
|
||||
mainSizer->AddStretchSpacer(1);
|
||||
|
||||
wxBoxSizer* bottomSizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
m_confirmBtn = new Button(this, _L("Confirm"));
|
||||
m_confirmBtn->SetSize(wxSize(FromDIP(80), FromDIP(32)));
|
||||
m_confirmBtn->Enable(false);
|
||||
m_confirmBtn->SetFont(::Label::Body_14);
|
||||
bottomSizer->Add(m_confirmBtn, 0, wxALIGN_RIGHT);
|
||||
|
||||
mainSizer->Add(bottomSizer, 0, wxALIGN_RIGHT | wxBOTTOM | wxRIGHT, FromDIP(10));
|
||||
|
||||
SetSizer(mainSizer);
|
||||
Layout();
|
||||
Centre(wxBOTH);
|
||||
|
||||
m_lastChecked = m_radioHelper;
|
||||
m_confirmBtn->Bind(wxEVT_BUTTON, &FeedDirectionDialog::OnConfirm, this);
|
||||
m_leftRadio->Bind(wxEVT_RADIOBUTTON, &FeedDirectionDialog::OnRadioClicked, this);
|
||||
m_rightRadio->Bind(wxEVT_RADIOBUTTON, &FeedDirectionDialog::OnRadioClicked, this);
|
||||
}
|
||||
|
||||
void FeedDirectionDialog::OnConfirm(wxCommandEvent& event)
|
||||
{
|
||||
EndModal(wxID_OK);
|
||||
}
|
||||
|
||||
void FeedDirectionDialog::OnRadioClicked(wxCommandEvent& evt)
|
||||
{
|
||||
auto clicked = static_cast<wxRadioButton*>(evt.GetEventObject());
|
||||
m_load_extruder_id = std::nullopt;
|
||||
if (clicked == m_lastChecked)
|
||||
{
|
||||
m_radioHelper->SetValue(true);
|
||||
m_lastChecked = m_radioHelper;
|
||||
m_confirmBtn->Enable(false);
|
||||
m_extruderImage->update(DevExtruderState::EMPTY_LOAD, DevExtruderState::EMPTY_LOAD);
|
||||
m_extruderImage->setExtruderUsed("");
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
clicked->SetValue(true);
|
||||
m_lastChecked = clicked;
|
||||
m_confirmBtn->Enable(true);
|
||||
|
||||
if (clicked == m_leftRadio)
|
||||
{
|
||||
m_extruderImage->update(DevExtruderState::FILLED_LOAD, DevExtruderState::EMPTY_LOAD);
|
||||
m_extruderImage->setExtruderUsed("left");
|
||||
m_load_extruder_id = 1;
|
||||
{
|
||||
SetTitle(wxString::Format(_L("Load %s to ") + _L(DevPrinterConfigUtil::get_toolhead_display_name(m_printer_type, DEPUTY_EXTRUDER_ID, ToolHeadComponent::Extruder, ToolHeadNameCase::LowerCase)), m_filament_id));
|
||||
}
|
||||
}
|
||||
else if (clicked == m_rightRadio)
|
||||
{
|
||||
m_extruderImage->update(DevExtruderState::EMPTY_LOAD, DevExtruderState::FILLED_LOAD);
|
||||
m_extruderImage->setExtruderUsed("right");
|
||||
m_load_extruder_id = 0;
|
||||
{
|
||||
SetTitle(wxString::Format(_L("Load %s to ") + _L(DevPrinterConfigUtil::get_toolhead_display_name(m_printer_type, MAIN_EXTRUDER_ID, ToolHeadComponent::Extruder, ToolHeadNameCase::LowerCase)), m_filament_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
m_leftRadio->Refresh();
|
||||
m_rightRadio->Refresh();
|
||||
Fit();
|
||||
Update();
|
||||
}
|
||||
|
||||
std::optional<int> FeedDirectionDialog::GetExtruderID()
|
||||
{
|
||||
if (m_extruderImage)
|
||||
{
|
||||
return m_load_extruder_id;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
wxString FeedDirectionDialog::calcTrayName(MachineObject* obj, const std::string& amsID, const std::string& slotID)
|
||||
{
|
||||
if (amsID.empty() || slotID.empty() || !obj)
|
||||
return wxString();
|
||||
|
||||
auto filaSys = obj->GetFilaSystem();
|
||||
if (!filaSys)
|
||||
return wxString();
|
||||
|
||||
DevAms* ams = filaSys->GetAmsById(amsID);
|
||||
if (!ams)
|
||||
return wxString();
|
||||
|
||||
int ams_id_int = std::stoi(amsID);
|
||||
int slot_id_int = std::stoi(slotID);
|
||||
int tray_id = 0;
|
||||
|
||||
// Orca: DevAms::AmsType is the local enum (AMS / AMS_LITE / N3F / N3S); it has no EXT_SPOOL
|
||||
// member, and external/virtual spools are not real DevAms objects (GetAmsById returns nullptr
|
||||
// above), so the EXT_SPOOL branch from upstream is unnecessary here.
|
||||
if (ams->GetAmsType() == DevAms::AMS || ams->GetAmsType() == DevAms::AMS_LITE || ams->GetAmsType() == DevAms::N3F) {
|
||||
tray_id = ams_id_int * 4 + slot_id_int;
|
||||
} else if (ams->GetAmsType() == DevAms::N3S) {
|
||||
tray_id = ams_id_int + slot_id_int;
|
||||
} else {
|
||||
return wxString();
|
||||
}
|
||||
|
||||
return wxGetApp().transition_tridid(tray_id);
|
||||
}
|
||||
|
||||
void FeedDirectionDialog::SetExtruderMapping(MachineObject* obj,
|
||||
const std::string& currAmsId,
|
||||
const std::string& currSlotId,
|
||||
const std::vector<std::pair<std::string, std::string>>& extruderSlots)
|
||||
{
|
||||
wxString filamentID = calcTrayName(obj, currAmsId, currSlotId);
|
||||
if (filamentID.empty())
|
||||
return;
|
||||
|
||||
m_filament_id = filamentID;
|
||||
SetTitle(wxString::Format(_L("Load %s to "), filamentID));
|
||||
|
||||
std::vector<wxString> extruderMapping(extruderSlots.size());
|
||||
for (size_t i = 0; i < extruderSlots.size(); ++i) {
|
||||
if (!extruderSlots[i].first.empty())
|
||||
extruderMapping[i] = calcTrayName(obj, extruderSlots[i].first, extruderSlots[i].second);
|
||||
}
|
||||
|
||||
if (extruderMapping.size() > 1 && extruderMapping[1] == filamentID) //left extruder
|
||||
{
|
||||
m_leftRadio->Enable(false);
|
||||
m_confirmBtn->Enable(false);
|
||||
m_extruderImage->update(DevExtruderState::EMPTY_LOAD, DevExtruderState::EMPTY_LOAD);
|
||||
m_extruderImage->setExtruderUsed("right");
|
||||
}
|
||||
else if (!extruderMapping.empty() && extruderMapping[0] == filamentID) //right extruder
|
||||
{
|
||||
m_rightRadio->Enable(false);
|
||||
m_confirmBtn->Enable(false);
|
||||
m_extruderImage->update(DevExtruderState::EMPTY_LOAD, DevExtruderState::EMPTY_LOAD);
|
||||
m_extruderImage->setExtruderUsed("left");
|
||||
}
|
||||
else
|
||||
{
|
||||
m_radioHelper->SetValue(true);
|
||||
m_lastChecked = m_radioHelper;
|
||||
m_confirmBtn->Enable(false);
|
||||
m_extruderImage->update(DevExtruderState::EMPTY_LOAD, DevExtruderState::EMPTY_LOAD);
|
||||
m_extruderImage->setExtruderUsed("");
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <wx/hyperlink.h>
|
||||
#include <wx/animate.h>
|
||||
#include <wx/dynarray.h>
|
||||
#include <optional>
|
||||
|
||||
|
||||
#define AMS_CONTROL_BRAND_COLOUR wxColour(0, 150, 136)
|
||||
@@ -400,6 +401,26 @@ private:
|
||||
};
|
||||
|
||||
|
||||
// Orca: routing glyph shown on the AMS control when a Filament Track Switch is installed.
|
||||
class SwitcherImage: public wxWindow
|
||||
{
|
||||
public:
|
||||
void setShowState(bool show_state) { m_show_state = show_state; };
|
||||
// void msw_rescale();
|
||||
void paintEvent(wxPaintEvent &evt);
|
||||
|
||||
void render(wxDC &dc);
|
||||
bool m_show_state = {false};
|
||||
wxColour m_colour;
|
||||
ScalableBitmap m_switcher;
|
||||
string m_file_name;
|
||||
// bool m_ams_loading{ false };
|
||||
void doRender(wxDC &dc);
|
||||
SwitcherImage(wxWindow *parent, wxWindowID id, string file_name, const wxSize& size, const wxPoint &pos = wxDefaultPosition);
|
||||
~SwitcherImage();
|
||||
};
|
||||
|
||||
|
||||
class AMSextruder : public wxWindow
|
||||
{
|
||||
private:
|
||||
@@ -771,6 +792,9 @@ public:
|
||||
void PlayRridLoading(wxString canid);
|
||||
void StopRridLoading(wxString canid);
|
||||
void msw_rescale();
|
||||
// Orca: hide/show the road segment below the item; used to drop the external-spool road
|
||||
// when a Filament Track Switch is installed. Returns true if the visibility actually changed.
|
||||
bool ShowRoad(bool show);
|
||||
void show_sn_value(bool show);
|
||||
void SetAmsStepExtra(wxString canid, AMSPassRoadType type, AMSPassRoadSTEP step);
|
||||
void SetAmsStep(std::string amsid, std::string canid, AMSPassRoadType type, AMSPassRoadSTEP step);
|
||||
@@ -849,6 +873,111 @@ wxDECLARE_EVENT(EVT_AMS_UNSELETED_AMS, wxCommandEvent);
|
||||
wxDECLARE_EVENT(EVT_VAMS_ON_FILAMENT_EDIT, wxCommandEvent);
|
||||
wxDECLARE_EVENT(EVT_AMS_SWITCH, SimpleEvent);
|
||||
|
||||
enum class DevExtruderState {
|
||||
FILLED_LOAD,
|
||||
FILLED_UNLOAD,
|
||||
EMPTY_LOAD,
|
||||
EMPTY_UNLOAD
|
||||
};
|
||||
|
||||
class DevExtruderImage : public wxWindow
|
||||
{
|
||||
ScalableBitmap *m_left_extruder_active_filled;
|
||||
ScalableBitmap *m_left_extruder_active_empty;
|
||||
ScalableBitmap *m_left_extruder_unactive_filled;
|
||||
ScalableBitmap *m_left_extruder_unactive_empty;
|
||||
ScalableBitmap *m_right_extruder_active_filled;
|
||||
ScalableBitmap *m_right_extruder_active_empty;
|
||||
ScalableBitmap *m_right_extruder_unactive_filled;
|
||||
ScalableBitmap *m_right_extruder_unactive_empty;
|
||||
|
||||
ScalableBitmap *m_extruder_single_nozzle_empty_load;
|
||||
ScalableBitmap *m_extruder_single_nozzle_empty_unload;
|
||||
ScalableBitmap *m_extruder_single_nozzle_filled_load;
|
||||
ScalableBitmap *m_extruder_single_nozzle_filled_unload;
|
||||
|
||||
DevExtruderState m_left_ext_state = {DevExtruderState::EMPTY_LOAD};
|
||||
DevExtruderState m_right_ext_state = {DevExtruderState::EMPTY_LOAD};
|
||||
DevExtruderState m_single_ext_state = {DevExtruderState::EMPTY_LOAD};
|
||||
|
||||
public:
|
||||
DevExtruderImage(wxWindow *parent, wxWindowID id,
|
||||
int extruder_num,
|
||||
const wxPoint &pos = wxDefaultPosition,
|
||||
const wxSize &size = wxDefaultSize);
|
||||
~DevExtruderImage()
|
||||
{
|
||||
|
||||
}
|
||||
void update(DevExtruderState single_state)
|
||||
{
|
||||
m_single_ext_state = single_state;
|
||||
}
|
||||
void update(DevExtruderState left_state, DevExtruderState right_state)
|
||||
{
|
||||
m_left_ext_state = left_state;
|
||||
m_right_ext_state = right_state;
|
||||
}
|
||||
|
||||
void msw_rescale();
|
||||
void setExtruderCount(int extruder_num)
|
||||
{
|
||||
m_extruder_num = extruder_num;
|
||||
}
|
||||
void setExtruderUsed(const std::string& loc)
|
||||
{
|
||||
if (current_extruder_loc == loc) { return; }
|
||||
current_extruder_loc = loc;
|
||||
Refresh();
|
||||
}
|
||||
private:
|
||||
void paintEvent(wxPaintEvent &evt)
|
||||
{
|
||||
wxPaintDC dc(this);
|
||||
render(dc);
|
||||
}
|
||||
void render(wxDC &dc);
|
||||
void doRender(wxDC &dc);
|
||||
int m_extruder_num = 1;
|
||||
std::string current_extruder_loc = "";
|
||||
|
||||
};
|
||||
|
||||
// Filament Track Switch: when the switch is installed and calibrated a filament can be routed to
|
||||
// either extruder, so this dialog lets the user pick. GetExtruderID() returns the chosen extruder
|
||||
// (1 = deputy/left, 0 = main/right) or nullopt if none was picked; that value is passed to
|
||||
// MachineObject::command_ams_change_filament. Only constructed on the FTS-ready path.
|
||||
class FeedDirectionDialog : public wxDialog
|
||||
{
|
||||
public:
|
||||
FeedDirectionDialog(wxWindow* parent, const int extruderNum, const std::string& printer_type = "");
|
||||
|
||||
std::optional<int> GetExtruderID();
|
||||
|
||||
void SetExtruderMapping(MachineObject* obj,
|
||||
const std::string& currAmsId,
|
||||
const std::string& currSlotId,
|
||||
const std::vector<std::pair<std::string, std::string>>& extruderSlots);
|
||||
|
||||
private:
|
||||
static wxString calcTrayName(MachineObject* obj, const std::string& amsID, const std::string& slotID);
|
||||
|
||||
int m_extruder_num{};
|
||||
std::string m_printer_type;
|
||||
wxString m_filament_id{};
|
||||
wxRadioButton* m_radioHelper{nullptr};
|
||||
wxRadioButton* m_leftRadio{nullptr};
|
||||
wxRadioButton* m_rightRadio{nullptr};
|
||||
wxRadioButton* m_lastChecked{nullptr};
|
||||
DevExtruderImage* m_extruderImage{nullptr};
|
||||
Button* m_confirmBtn{nullptr};
|
||||
std::optional<int> m_load_extruder_id = std::nullopt;
|
||||
|
||||
void OnConfirm(wxCommandEvent& event);
|
||||
void OnRadioClicked(wxCommandEvent& evt);
|
||||
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
#endif // !slic3r_GUI_amscontrol_hpp_
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "../BitmapCache.hpp"
|
||||
#include "../I18N.hpp"
|
||||
#include "../GUI_App.hpp"
|
||||
#include "../DeviceCore/DevFilaSystem.h"
|
||||
|
||||
#include <wx/simplebook.h>
|
||||
#include <wx/dcgraph.h>
|
||||
@@ -37,6 +38,28 @@ FilamentLoad::FilamentLoad(wxWindow* parent, wxWindowID id, const wxPoint& pos,
|
||||
//FILAMENT_CHANGE_STEP_STRING[FilamentStep::STEP_FEED_FILAMENT] = _L("Feed Filament");
|
||||
FILAMENT_CHANGE_STEP_STRING[FilamentStep::STEP_CONFIRM_EXTRUDED] = _L("Confirm extruded");
|
||||
FILAMENT_CHANGE_STEP_STRING[FilamentStep::STEP_CHECK_POSITION] = _L("Check filament location");
|
||||
|
||||
// Orca: labels for the device-numbered steps carried by the AMS (ams.cfs). Overlapping steps
|
||||
// reuse the wording above so the current-step highlight (driven by the legacy ams_status_sub
|
||||
// path) still text-matches; the switch/hotend/cooling and Filament Track Switch steps are new.
|
||||
// STEP_CHECK_POSITION and STEP_CONFIRM_EXTRUDED share code 0x08, so CONFIRM is assigned first
|
||||
// and CHECK_POSITION last, leaving code 0x08 mapped to "Check filament location" as on device.
|
||||
DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_IDLE] = _L("Idling...");
|
||||
DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_PAUSE] = _L("Pause");
|
||||
DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_HEAT_NOZZLE] = _L("Heat the nozzle");
|
||||
DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_CUT_FILAMENT] = _L("Cut filament");
|
||||
DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_PULL_CURR_FILAMENT] = _L("Pull back the current filament");
|
||||
DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_PUSH_NEW_FILAMENT] = _L("Push new filament into extruder");
|
||||
DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_GRAB_NEW_FILAMENT] = _L("Grab new filament");
|
||||
DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_PURGE_OLD_FILAMENT] = _L("Purge old filament");
|
||||
DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_SWITCH_EXTRUDER] = _L("Switch") + " " + _L("extruder");
|
||||
DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_SWITCH_HOTEND] = _L("Switch") + " " + _L("hotend");
|
||||
DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_AMS_FILA_COOLING] = _L("Wait for AMS cooling");
|
||||
DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_PUSH_SWITCHER_FILA] = _L("Switch current filament at Filament Track Switch");
|
||||
DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_PULL_SWITCHER_FILA] = _L("Pull back current filament at Filament Track Switch");
|
||||
DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_SWITCHER_SWITCH] = _L("Switch track at Filament Track Switch");
|
||||
DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_CONFIRM_EXTRUDED] = _L("Confirm extruded");
|
||||
DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_CHECK_POSITION] = _L("Check filament location");
|
||||
}
|
||||
|
||||
void FilamentLoad::SetFilamentStep(FilamentStep item_idx, FilamentStepType f_type)
|
||||
@@ -124,11 +147,34 @@ void FilamentLoad::SetFilamentStep(FilamentStep item_idx, FilamentStepType f_typ
|
||||
step_control->SetSlotInformation(slot_info);
|
||||
}
|
||||
|
||||
void FilamentLoad::SetupSteps(bool has_fila_to_switch) {
|
||||
void FilamentLoad::SetupSteps(MachineObject* obj_, bool has_fila_to_switch) {
|
||||
m_filament_load_steps->DeleteAllItems();
|
||||
m_filament_unload_steps->DeleteAllItems();
|
||||
m_filament_vt_load_steps->DeleteAllItems();
|
||||
|
||||
// Orca: newer firmware sends the exact change-step sequence through the AMS (ams.cfs). When
|
||||
// present, build the visible steps straight from that list and skip the hardcoded per-model
|
||||
// sequences below. Inert on current firmware: the list is empty, so this branch is skipped and
|
||||
// the legacy logic runs unchanged.
|
||||
if (obj_ && obj_->GetFilaSystem() && !obj_->GetFilaSystem()->GetFilamentChangeSteps().empty()) {
|
||||
const auto& steps = obj_->GetFilaSystem()->GetFilamentChangeSteps();
|
||||
for (auto step : steps) {
|
||||
auto iter = DEV_FILAMENT_CHANGE_STEP_STRING.find(step);
|
||||
if (iter == DEV_FILAMENT_CHANGE_STEP_STRING.end()) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Unknown filament change step: " << static_cast<int>(step);
|
||||
continue;
|
||||
}
|
||||
|
||||
m_filament_load_steps->AppendItem(iter->second);
|
||||
m_filament_unload_steps->AppendItem(iter->second);
|
||||
m_filament_vt_load_steps->AppendItem(iter->second);
|
||||
}
|
||||
|
||||
Layout();
|
||||
Fit();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_ams_model == AMSModel::GENERIC_AMS || m_ext_model == AMSModel::N3F_AMS || m_ext_model == AMSModel::N3S_AMS) {
|
||||
if (has_fila_to_switch) {
|
||||
m_filament_load_steps->AppendItem(FILAMENT_CHANGE_STEP_STRING[FilamentStep::STEP_HEAT_NOZZLE]);
|
||||
|
||||
@@ -35,6 +35,10 @@ protected:
|
||||
|
||||
public:
|
||||
std::map<FilamentStep, wxString> FILAMENT_CHANGE_STEP_STRING;
|
||||
// Labels for the device-numbered steps reported by the AMS (ams.cfs). Keyed by the device
|
||||
// enum, separate from the legacy FilamentStep map above. Only used when the AMS provides a
|
||||
// step list; empty AMS list falls back to the legacy sequences below.
|
||||
std::map<DevFilamentStep, wxString> DEV_FILAMENT_CHANGE_STEP_STRING;
|
||||
AMSModel m_ams_model{ AMSModel::GENERIC_AMS };
|
||||
AMSModel m_ext_model{ AMSModel::AMS_LITE };
|
||||
AMSModel m_is_none_ams_mode{ AMSModel::AMS_LITE };
|
||||
@@ -44,7 +48,7 @@ public:
|
||||
void SetFilamentStep(FilamentStep item_idx, FilamentStepType f_type);
|
||||
void ShowFilamentTip(bool hasams = true);
|
||||
|
||||
void SetupSteps(bool is_extrusion_exist);
|
||||
void SetupSteps(MachineObject* obj_, bool is_extrusion_exist);
|
||||
|
||||
void show_nofilament_mode(bool show);
|
||||
void updateID(int ams_id, int slot_id) { m_ams_id = ams_id; m_slot_id = slot_id; };
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user