mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-28 03:11:47 +00:00
Merge branch 'main' into feat/plugin-feature
Resolve five conflicts, all of which needed both sides rather than a pick: - BackgroundSlicingProcess: ours was a pure tabs->spaces reformat of base, so keep main's per-filament volume/nozzle map read-back (its only change here). - GUI_App: main's #12506 else-if attached to an `if` this branch deleted; re-expressed onto the same-agent early-return path (the agent factory caches per id, so pointer equality is the same predicate). - MainFrame: both sides relocated Sync Presets independently; keep main's push_notification plus the branch's Plugins menu items. - Tab: the "TODO: Orca: Support hybrid" blocks were unchanged base, not a branch decision; take main's enabled Hybrid to match the already auto-merged siblings. - test_config: union of both sides' cases (6 plugin + 9 multi-nozzle).
This commit is contained in:
@@ -428,6 +428,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
|
||||
@@ -511,6 +513,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();
|
||||
|
||||
@@ -324,10 +324,12 @@ AboutDialog::AboutDialog()
|
||||
m_html->SetFonts(font.GetFaceName(), font.GetFaceName(), size);
|
||||
m_html->SetMinSize(wxSize(FromDIP(-1), FromDIP(16)));
|
||||
m_html->SetBorders(2);
|
||||
wxColour bgr_clr = GetBackgroundColour();
|
||||
const auto bgr_clr_str = encode_color(ColorRGB(bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue()));
|
||||
const auto text = from_u8(
|
||||
(boost::format(
|
||||
"<html>"
|
||||
"<body>"
|
||||
"<body bgcolor= \"" + bgr_clr_str + "\" >"
|
||||
"<p style=\"text-align:left\"><a style=\"color:#009789\" href=\"https://www.orcaslicer.com\">https://www.orcaslicer.com</ a></p>"
|
||||
"</body>"
|
||||
"</html>")
|
||||
|
||||
@@ -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() {
|
||||
@@ -569,7 +673,7 @@ void MaterialSyncItem::doRender(wxDC &dc)
|
||||
dc.DrawRoundedRectangle(1, 1, MATERIAL_ITEM_SIZE.x - 1, MATERIAL_ITEM_SIZE.y - 1, 5);
|
||||
|
||||
if (m_selected) {
|
||||
dc.SetPen(wxColour(0x00, 0xAE, 0x42));
|
||||
dc.SetPen(wxColour("#009688")); // ORCA selected item border color
|
||||
dc.SetBrush(*wxTRANSPARENT_BRUSH);
|
||||
dc.DrawRoundedRectangle(1, 1, MATERIAL_ITEM_SIZE.x - 1, MATERIAL_ITEM_SIZE.y - 1, 5);
|
||||
}
|
||||
@@ -580,7 +684,7 @@ void MaterialSyncItem::doRender(wxDC &dc)
|
||||
dc.DrawRoundedRectangle(0, 0, MATERIAL_ITEM_SIZE.x, MATERIAL_ITEM_SIZE.y, 5);
|
||||
|
||||
if (m_selected) {
|
||||
dc.SetPen(wxPen(wxColour(0x00, 0xAE, 0x42), FromDIP(2)));
|
||||
dc.SetPen(wxPen(wxColour("#009688"), FromDIP(2))); // ORCA selected item border color
|
||||
dc.SetBrush(*wxTRANSPARENT_BRUSH);
|
||||
dc.DrawRoundedRectangle(FromDIP(1), FromDIP(1), MATERIAL_ITEM_SIZE.x - FromDIP(1), MATERIAL_ITEM_SIZE.y - FromDIP(1), 5);
|
||||
}
|
||||
@@ -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();
|
||||
@@ -813,7 +936,7 @@ void AmsMapingPopup::msw_rescale()
|
||||
m_split_left_line->SetMaxSize(wxSize(-1, 1));
|
||||
sizer_split_ams->Add(0, 0, 0, wxEXPAND, 0);
|
||||
sizer_split_ams->Add(ams_title_text, 0, wxALIGN_CENTER, 0);
|
||||
sizer_split_ams->Add(m_split_left_line, 1, wxEXPAND, 0);
|
||||
sizer_split_ams->Add(m_split_left_line, 1, wxALIGN_CENTER, 0); // ORCA align line vertically
|
||||
return sizer_split_ams;
|
||||
}
|
||||
|
||||
@@ -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)) {
|
||||
@@ -1420,7 +1621,7 @@ bool AmsMapingPopup::ProcessLeftDown(wxMouseEvent &event)
|
||||
void AmsMapingPopup::paintEvent(wxPaintEvent &evt)
|
||||
{
|
||||
wxPaintDC dc(this);
|
||||
dc.SetPen(wxColour(0xAC, 0xAC, 0xAC));
|
||||
dc.SetPen(wxPen(StateColor::darkModeColorFor(wxColour("#009688")),FromDIP(2))); // ORCA use colorful border for separation
|
||||
dc.SetBrush(*wxTRANSPARENT_BRUSH);
|
||||
dc.DrawRoundedRectangle(0, 0, GetSize().x, GetSize().y, 0);
|
||||
}
|
||||
@@ -2281,7 +2482,7 @@ AmsRMGroup* AmsReplaceMaterialDialog::create_backup_group(wxString gname, std::m
|
||||
void AmsReplaceMaterialDialog::paintEvent(wxPaintEvent& evt)
|
||||
{
|
||||
wxPaintDC dc(this);
|
||||
dc.SetPen(wxColour(0xAC, 0xAC, 0xAC));
|
||||
dc.SetPen(StateColor::darkModeColorFor(wxColour("#DBDBDB"))); // ORCA fix popup border color for dark mode
|
||||
dc.SetBrush(*wxTRANSPARENT_BRUSH);
|
||||
dc.DrawRoundedRectangle(0, 0, GetSize().x, GetSize().y, 0);
|
||||
}
|
||||
@@ -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};
|
||||
|
||||
@@ -233,6 +233,19 @@ 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.
|
||||
@@ -244,6 +257,9 @@ void BackgroundSlicingProcess::process_fff()
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -30,6 +30,19 @@ BaseTransparentDPIFrame::BaseTransparentDPIFrame(
|
||||
// SetBackgroundStyle(wxBackgroundStyle::wxBG_STYLE_TRANSPARENT);
|
||||
SetTransparent(m_init_transparent);
|
||||
SetBackgroundColour(wxColour(23, 25, 22, 128));
|
||||
|
||||
// ORCA add border
|
||||
Bind(wxEVT_PAINT, [this](wxPaintEvent& evt) {
|
||||
wxPaintDC dc(this);
|
||||
dc.SetPen(wxPen(StateColor::darkModeColorFor(wxColour("#009688")), FromDIP(2)));
|
||||
dc.SetBrush(*wxTRANSPARENT_BRUSH);
|
||||
dc.DrawRoundedRectangle(0, 0, GetSize().x, GetSize().y, 0);
|
||||
});
|
||||
|
||||
int window_padding = 15;
|
||||
auto imgsize = 32;
|
||||
auto imgright = 10;
|
||||
|
||||
//Adaptive Frame Width
|
||||
wxClientDC dc(parent);
|
||||
wxSize msg_sz = dc.GetMultiLineTextExtent(ok_text);
|
||||
@@ -44,21 +57,16 @@ BaseTransparentDPIFrame::BaseTransparentDPIFrame(
|
||||
|
||||
m_sizer_main = new wxBoxSizer(wxVERTICAL);
|
||||
wxBoxSizer *text_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
text_sizer->AddSpacer(FromDIP(20));
|
||||
auto image_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
auto imgsize = FromDIP(25);
|
||||
auto completedimg = new wxStaticBitmap(this, wxID_ANY, create_scaled_bitmap("completed", this, 25), wxDefaultPosition, wxSize(imgsize, imgsize), 0);
|
||||
image_sizer->Add(completedimg, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(0));
|
||||
image_sizer->AddStretchSpacer();
|
||||
text_sizer->Add(image_sizer);
|
||||
text_sizer->AddSpacer(FromDIP(5));
|
||||
|
||||
auto completedimg = new wxStaticBitmap(this, wxID_ANY, create_scaled_bitmap("completed", this, imgsize), wxDefaultPosition, FromDIP(wxSize(imgsize, imgsize)), 0);
|
||||
|
||||
text_sizer->Add(completedimg, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(imgright));
|
||||
m_finish_text = new Label(this, win_text, LB_AUTO_WRAP);
|
||||
m_finish_text->SetMinSize(wxSize(FromDIP(win_width - 64), -1));
|
||||
m_finish_text->SetMaxSize(wxSize(FromDIP(win_width - 64), -1));
|
||||
m_finish_text->SetMinSize(wxSize(FromDIP(win_width - (window_padding * 2 + imgright + imgsize)), -1));
|
||||
m_finish_text->SetMaxSize(wxSize(FromDIP(win_width - (window_padding * 2 + imgright + imgsize)), -1));
|
||||
m_finish_text->SetForegroundColour(wxColour(255, 255, 255, 255));
|
||||
text_sizer->Add(m_finish_text, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxALL, 0);
|
||||
text_sizer->AddSpacer(FromDIP(20));
|
||||
m_sizer_main->Add(text_sizer, FromDIP(0), wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxTOP, FromDIP(15));
|
||||
text_sizer->Add(m_finish_text, 0, wxALIGN_CENTER_VERTICAL);
|
||||
m_sizer_main->Add(text_sizer, 0, wxALL, FromDIP(15));
|
||||
|
||||
wxBoxSizer *bSizer_button = new wxBoxSizer(wxHORIZONTAL);
|
||||
bSizer_button->SetMinSize(wxSize(FromDIP(100), -1));
|
||||
@@ -66,22 +74,18 @@ BaseTransparentDPIFrame::BaseTransparentDPIFrame(
|
||||
bSizer_button->Add(m_checkbox, 0, wxALIGN_LEFT);*/
|
||||
bSizer_button->AddStretchSpacer(1);
|
||||
m_button_ok = new Button(this, ok_text);
|
||||
m_button_ok->SetStyle(ButtonStyle::Confirm, ButtonType::Window);
|
||||
m_button_ok->SetSize(wxSize(FromDIP(60), FromDIP(30)));
|
||||
m_button_ok->SetMinSize(wxSize(FromDIP(90), FromDIP(30)));
|
||||
bSizer_button->Add(m_button_ok, 0, wxALIGN_RIGHT | wxLEFT | wxTOP, FromDIP(10));
|
||||
m_button_ok->SetStyle(ButtonStyle::Confirm, ButtonType::Choice);
|
||||
bSizer_button->Add(m_button_ok, 0, wxALIGN_RIGHT | wxLEFT, FromDIP(10));
|
||||
|
||||
m_button_ok->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](wxCommandEvent &e) { deal_ok(); });
|
||||
|
||||
m_button_cancel = new Button(this, cancel_text);
|
||||
m_button_cancel->SetStyle(ButtonStyle::Regular, ButtonType::Window);
|
||||
m_button_cancel->SetSize(wxSize(FromDIP(65), FromDIP(30)));
|
||||
m_button_cancel->SetMinSize(wxSize(FromDIP(65), FromDIP(30)));
|
||||
bSizer_button->Add(m_button_cancel, 0, wxALIGN_RIGHT | wxLEFT | wxTOP, FromDIP(10));
|
||||
m_button_cancel->SetStyle(ButtonStyle::Regular, ButtonType::Choice);
|
||||
bSizer_button->Add(m_button_cancel, 0, wxALIGN_RIGHT | wxLEFT, FromDIP(10));
|
||||
|
||||
m_button_cancel->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](wxCommandEvent &e) { deal_cancel(); });
|
||||
|
||||
m_sizer_main->Add(bSizer_button, 1, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, FromDIP(20));
|
||||
m_sizer_main->Add(bSizer_button, 1, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, FromDIP(window_padding));
|
||||
|
||||
Bind(wxEVT_CLOSE_WINDOW, [this](auto &e) {
|
||||
on_hide();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <boost/nowide/convert.hpp>
|
||||
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/button.h>
|
||||
#include <wx/listctrl.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/timer.h>
|
||||
@@ -20,6 +19,8 @@
|
||||
#include "slic3r/GUI/format.hpp"
|
||||
#include "slic3r/Utils/Bonjour.hpp"
|
||||
|
||||
#include "slic3r/GUI/Widgets/DialogButtons.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
@@ -63,30 +64,30 @@ BonjourDialog::BonjourDialog(wxWindow *parent, Slic3r::PrinterTechnology tech)
|
||||
, timer_state(0)
|
||||
, tech(tech)
|
||||
{
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
|
||||
const int em = GUI::wxGetApp().em_unit();
|
||||
list->SetMinSize(wxSize(80 * em, 30 * em));
|
||||
|
||||
wxBoxSizer *vsizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
label->SetFont(Label::Body_14);
|
||||
vsizer->Add(label, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, em);
|
||||
|
||||
list->SetSingleStyle(wxLC_SINGLE_SEL);
|
||||
list->SetSingleStyle(wxLC_SORT_DESCENDING);
|
||||
list->AppendColumn(_(L("Address")), wxLIST_FORMAT_LEFT, 5 * em);
|
||||
list->AppendColumn(_(L("Address")), wxLIST_FORMAT_LEFT, 20 * em);
|
||||
list->AppendColumn(_(L("Hostname")), wxLIST_FORMAT_LEFT, 10 * em);
|
||||
list->AppendColumn(_(L("Service name")), wxLIST_FORMAT_LEFT, 20 * em);
|
||||
if (tech == ptFFF) {
|
||||
list->AppendColumn(_(L("OctoPrint version")), wxLIST_FORMAT_LEFT, 5 * em);
|
||||
list->AppendColumn(_(L("OctoPrint version")), wxLIST_FORMAT_LEFT, 15 * em);
|
||||
}
|
||||
|
||||
vsizer->Add(list, 1, wxEXPAND | wxALL, em);
|
||||
|
||||
wxBoxSizer *button_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
button_sizer->Add(new wxButton(this, wxID_OK, _L("OK")), 0, wxALL, em);
|
||||
button_sizer->Add(new wxButton(this, wxID_CANCEL, _L("Cancel")), 0, wxALL, em);
|
||||
// ^ Note: The Ok/Cancel labels are translated by wxWidgets
|
||||
auto dlg_btns = new GUI::DialogButtons(this, {"OK", "Cancel"});
|
||||
|
||||
vsizer->Add(button_sizer, 0, wxALIGN_CENTER);
|
||||
vsizer->Add(dlg_btns, 0, wxEXPAND);
|
||||
SetSizerAndFit(vsizer);
|
||||
|
||||
Bind(EVT_BONJOUR_REPLY, &BonjourDialog::on_reply, this);
|
||||
@@ -241,12 +242,15 @@ IPListDialog::IPListDialog(wxWindow* parent, const wxString& hostname, const std
|
||||
, m_list(new wxListView(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxSIMPLE_BORDER))
|
||||
, m_selected_index (selected_index)
|
||||
{
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
|
||||
const int em = GUI::wxGetApp().em_unit();
|
||||
m_list->SetMinSize(wxSize(40 * em, 30 * em));
|
||||
|
||||
wxBoxSizer* vsizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
auto* label = new wxStaticText(this, wxID_ANY, GUI::format_wxstr(_L("There are several IP addresses resolving to hostname %1%.\nPlease select one that should be used."), hostname));
|
||||
label->SetFont(Label::Body_14);
|
||||
vsizer->Add(label, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, em);
|
||||
|
||||
m_list->SetSingleStyle(wxLC_SINGLE_SEL);
|
||||
@@ -259,11 +263,9 @@ IPListDialog::IPListDialog(wxWindow* parent, const wxString& hostname, const std
|
||||
|
||||
vsizer->Add(m_list, 1, wxEXPAND | wxALL, em);
|
||||
|
||||
wxBoxSizer* button_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
button_sizer->Add(new wxButton(this, wxID_OK, _L("OK")), 0, wxALL, em);
|
||||
button_sizer->Add(new wxButton(this, wxID_CANCEL, _L("Cancel")), 0, wxALL, em);
|
||||
auto dlg_btns = new GUI::DialogButtons(this, {"OK", "Cancel"});
|
||||
|
||||
vsizer->Add(button_sizer, 0, wxALIGN_CENTER);
|
||||
vsizer->Add(dlg_btns, 0, wxEXPAND);
|
||||
SetSizerAndFit(vsizer);
|
||||
|
||||
GUI::wxGetApp().UpdateDlgDarkUI(this);
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -710,6 +710,38 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
||||
toggle_field("bottom_surface_pattern", has_bottom_shell);
|
||||
toggle_field("top_surface_density", has_top_shell);
|
||||
toggle_field("bottom_surface_density", has_bottom_shell);
|
||||
toggle_field("top_layer_direction", has_top_shell);
|
||||
toggle_field("bottom_layer_direction", has_bottom_shell);
|
||||
|
||||
toggle_line("top_surface_expansion", has_top_shell);
|
||||
toggle_line("top_surface_expansion_margin", has_top_shell);
|
||||
bool has_top_surface_expansion = config->opt_float("top_surface_expansion") > 0;
|
||||
toggle_field("top_surface_expansion_margin", has_top_surface_expansion);
|
||||
toggle_line("top_surface_expansion_direction", has_top_shell);
|
||||
toggle_field("top_surface_expansion_direction", has_top_surface_expansion);
|
||||
|
||||
// Orca: Archimedean Chords and Octagram Spiral are the centered surface patterns that the
|
||||
// pattern-centering feature acts on.
|
||||
auto is_centered_pattern = [](InfillPattern p) {
|
||||
return p == InfillPattern::ipArchimedeanChords || p == InfillPattern::ipOctagramSpiral;
|
||||
};
|
||||
bool is_top_centered = is_centered_pattern(config->option<ConfigOptionEnum<InfillPattern>>("top_surface_pattern")->value);
|
||||
bool is_bottom_centered = is_centered_pattern(config->option<ConfigOptionEnum<InfillPattern>>("bottom_surface_pattern")->value);
|
||||
bool has_centered_surface = (has_top_shell && is_top_centered) || (has_bottom_shell && is_bottom_centered);
|
||||
|
||||
// Orca: center of surface pattern
|
||||
toggle_line("center_of_surface_pattern", has_centered_surface);
|
||||
|
||||
// Orca: separate infills
|
||||
bool is_internal_infill_separable = is_separable_infill_pattern(config->option<ConfigOptionEnum<InfillPattern>>("sparse_infill_pattern")->value) ||
|
||||
config->opt_string("sparse_infill_rotate_template") != "" ||
|
||||
config->opt_string("solid_infill_rotate_template") != "";
|
||||
toggle_line("separated_infills", is_internal_infill_separable);
|
||||
|
||||
// Fill order is only meaningful for the center-based surface fill patterns; hide it otherwise.
|
||||
auto is_centered_fill = [](InfillPattern p) { return p == ipConcentric || p == ipArchimedeanChords || p == ipOctagramSpiral; };
|
||||
toggle_line("top_surface_fill_order", has_top_shell && is_centered_fill(config->opt_enum<InfillPattern>("top_surface_pattern")));
|
||||
toggle_line("bottom_surface_fill_order", has_bottom_shell && is_centered_fill(config->opt_enum<InfillPattern>("bottom_surface_pattern")));
|
||||
|
||||
for (auto el : { "infill_direction", "sparse_infill_line_width", "gap_fill_target","filter_out_gap_fill","infill_wall_overlap",
|
||||
"bridge_angle", "internal_bridge_angle", "relative_bridge_angle",
|
||||
@@ -844,6 +876,10 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
||||
// toggle_line("support_speed", have_support_material || have_skirt_height);
|
||||
// toggle_line("support_interface_speed", have_support_material && have_support_interface);
|
||||
|
||||
// Orca:
|
||||
for (auto el : {"small_support_perimeter_speed", "small_support_perimeter_threshold"})
|
||||
toggle_field(el, config->opt_bool("enable_support"));
|
||||
|
||||
// BBS
|
||||
//toggle_field("support_material_synchronize_layers", have_support_soluble);
|
||||
|
||||
@@ -910,7 +946,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
||||
toggle_line("wipe_tower_extra_rib_length", have_rib_wall);
|
||||
toggle_line("wipe_tower_rib_width", have_rib_wall);
|
||||
toggle_line("wipe_tower_fillet_wall", have_rib_wall);
|
||||
toggle_field("prime_tower_width", have_prime_tower && (supports_wipe_tower_2 || have_rib_wall));
|
||||
toggle_field("prime_tower_width", have_prime_tower && !have_rib_wall);
|
||||
|
||||
toggle_line("single_extruder_multi_material_priming", !bSEMM && have_prime_tower && supports_wipe_tower_2);
|
||||
|
||||
@@ -974,7 +1010,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
||||
|
||||
toggle_line("min_width_top_surface", config->opt_bool("only_one_wall_top") || ((config->opt_float("min_length_factor") > 0.5f) && have_arachne)); // 0.5 is default value
|
||||
|
||||
for (auto el : { "hole_to_polyhole_threshold", "hole_to_polyhole_twisted" })
|
||||
for (auto el : { "hole_to_polyhole_threshold", "hole_to_polyhole_twisted", "hole_to_polyhole_max_edges" })
|
||||
toggle_line(el, config->opt_bool("hole_to_polyhole"));
|
||||
|
||||
bool has_detect_overhang_wall = config->opt_bool("detect_overhang_wall");
|
||||
|
||||
@@ -28,6 +28,8 @@ list(APPEND SLIC3R_GUI_SOURCES
|
||||
GUI/DeviceCore/DevFilaSystem.h
|
||||
GUI/DeviceCore/DevFilaSystem.cpp
|
||||
GUI/DeviceCore/DevFilaSystemCtrl.cpp
|
||||
GUI/DeviceCore/DevFilaSwitch.h
|
||||
GUI/DeviceCore/DevFilaSwitch.cpp
|
||||
GUI/DeviceCore/DevFirmware.h
|
||||
GUI/DeviceCore/DevFirmware.cpp
|
||||
GUI/DeviceCore/DevPrintOptions.h
|
||||
@@ -47,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
|
||||
{
|
||||
@@ -40,6 +41,31 @@ enum AmsStatusMain
|
||||
AMS_STATUS_MAIN_UNKNOWN = 0xFF,
|
||||
};
|
||||
|
||||
// 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
|
||||
#define VIRTUAL_TRAY_MAIN_ID 255
|
||||
#define VIRTUAL_TRAY_DEPUTY_ID 254
|
||||
@@ -47,21 +73,44 @@ enum AmsStatusMain
|
||||
#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*/
|
||||
@@ -96,4 +145,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"
|
||||
@@ -111,7 +112,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(DUMMY < type && m_ams_type <= N3S);
|
||||
assert(DUMMY < type && m_ams_type <= AMS_LITE_MIXED);
|
||||
}
|
||||
|
||||
DevAms::~DevAms()
|
||||
@@ -137,7 +138,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;
|
||||
@@ -166,11 +168,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;
|
||||
}
|
||||
@@ -258,6 +262,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);
|
||||
@@ -304,6 +346,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);
|
||||
@@ -361,17 +412,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);
|
||||
@@ -403,6 +480,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
|
||||
@@ -415,6 +499,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));
|
||||
@@ -628,6 +717,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 <wx/string.h>
|
||||
@@ -137,6 +140,7 @@ public:
|
||||
AMS_LITE = 2, // AMS-Lite
|
||||
N3F = 3, // N3F
|
||||
N3S = 4, // N3S
|
||||
AMS_LITE_MIXED = 5, // AMS-Lite mixed (A2L / N9). Presents as AMS_LITE to generic callers; see GetAmsType()/IsAmsLiteMixed().
|
||||
};
|
||||
|
||||
public:
|
||||
@@ -150,11 +154,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;
|
||||
@@ -163,6 +172,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; }
|
||||
|
||||
@@ -170,13 +193,21 @@ public:
|
||||
int GetHumidityLevel() const { return m_humidity_level; }
|
||||
int GetHumidityPercent() const { return m_humidity_percent; }
|
||||
|
||||
bool SupportDrying() const { return m_ams_type > AMS_LITE; }
|
||||
// Only N3F/N3S dry. Written explicitly (not `> AMS_LITE`) so the new AMS_LITE_MIXED (N9) is
|
||||
// excluded; identical to the old expression for every pre-existing type.
|
||||
bool SupportDrying() const { return (m_ams_type == N3F) || (m_ams_type == N3S); }
|
||||
int GetLeftDryTime() const { return m_left_dry_time; }
|
||||
|
||||
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
|
||||
@@ -241,9 +272,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(); };
|
||||
@@ -253,6 +291,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;
|
||||
@@ -266,6 +309,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);
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
#include "DevUtilBackend.h"
|
||||
|
||||
#include "slic3r/GUI/BackgroundSlicingProcess.hpp"
|
||||
#include "slic3r/GUI/Plater.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;
|
||||
}
|
||||
|
||||
}; // namespace Slic3r
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* @file DevUtilBackend.h
|
||||
* @brief Static helpers bridging the backend (slicer/preset) to the device GUI.
|
||||
*
|
||||
* Scope (H2C nozzle rack): exposes only GetNozzleGroupResult — the accessor the print-dispatch
|
||||
* nozzle-mapping V1 request and result-resolve helpers read. Related helpers (per-nozzle info
|
||||
* collection via the ExtruderNozzleInfos/NozzleDef type pair, and the drying-preset lookup) are
|
||||
* not implemented here and left to the consuming code.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "libslic3r/MultiNozzleUtils.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
namespace GUI { class Plater; }
|
||||
|
||||
class DevUtilBackend
|
||||
{
|
||||
public:
|
||||
DevUtilBackend() = delete;
|
||||
|
||||
// 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);
|
||||
};
|
||||
|
||||
}; // namespace Slic3r
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "fast_float/fast_float.h"
|
||||
|
||||
#include "DeviceCore/DevFilaSystem.h"
|
||||
#include "DeviceCore/DevFilaSwitch.h"
|
||||
#include "DeviceCore/DevExtensionTool.h"
|
||||
#include "DeviceCore/DevExtruderSystem.h"
|
||||
#include "DeviceCore/DevNozzleSystem.h"
|
||||
@@ -39,6 +40,7 @@
|
||||
#include "DeviceCore/DevHMS.h"
|
||||
|
||||
#include "DeviceCore/DevMapping.h"
|
||||
#include "DeviceCore/DevMappingNozzle.h"
|
||||
#include "DeviceCore/DevManager.h"
|
||||
#include "DeviceCore/DevUtil.h"
|
||||
|
||||
@@ -567,11 +569,14 @@ MachineObject::MachineObject(DeviceManager* manager, NetworkAgent* agent, std::s
|
||||
m_extension_tool = DevExtensionTool::Create(this);
|
||||
m_nozzle_system = new DevNozzleSystem(this);
|
||||
m_fila_system = new DevFilaSystem(this);
|
||||
m_fila_switch = new DevFilaSwitch(this);
|
||||
m_hms_system = new DevHMS(this);
|
||||
m_config = new DevConfig(this);
|
||||
|
||||
m_ctrl = new DevCtrl(this);
|
||||
m_print_options = new DevPrintOptions(this);
|
||||
|
||||
m_nozzle_mapping_ptr = std::make_shared<DevNozzleMappingCtrl>(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -615,6 +620,9 @@ MachineObject::~MachineObject()
|
||||
delete m_fila_system;
|
||||
m_fila_system = nullptr;
|
||||
|
||||
delete m_fila_switch;
|
||||
m_fila_switch = nullptr;
|
||||
|
||||
delete m_hms_system;
|
||||
m_hms_system = nullptr;
|
||||
|
||||
@@ -861,7 +869,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)
|
||||
@@ -874,6 +886,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);
|
||||
@@ -1556,7 +1575,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 {
|
||||
@@ -1588,6 +1607,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);
|
||||
}
|
||||
@@ -1976,6 +2002,10 @@ int MachineObject::command_set_pa_calibration(const std::vector<PACalibResult> &
|
||||
j["print"]["filaments"][i]["n_coef"] = std::to_string(pa_calib_values[i].n_coef);
|
||||
else
|
||||
j["print"]["filaments"][i]["n_coef"] = "0.0";
|
||||
if (pa_calib_values[i].nozzle_pos_id >= 0) {
|
||||
j["print"]["filaments"][i]["nozzle_pos"] = pa_calib_values[i].nozzle_pos_id;
|
||||
j["print"]["filaments"][i]["nozzle_sn"] = pa_calib_values[i].nozzle_sn;
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "extrusion_cali_set: " << j.dump();
|
||||
@@ -1994,6 +2024,10 @@ int MachineObject::command_delete_pa_calibration(const PACalibIndexInfo& pa_cali
|
||||
j["print"]["nozzle_id"] = _generate_nozzle_id(pa_calib.nozzle_volume_type, to_string_nozzle_diameter(pa_calib.nozzle_diameter)).ToStdString();
|
||||
j["print"]["filament_id"] = pa_calib.filament_id;
|
||||
j["print"]["cali_idx"] = pa_calib.cali_idx;
|
||||
if (pa_calib.nozzle_pos_id >= 0) {
|
||||
j["print"]["nozzle_pos"] = pa_calib.nozzle_pos_id;
|
||||
j["print"]["nozzle_sn"] = pa_calib.nozzle_sn;
|
||||
}
|
||||
j["print"]["nozzle_diameter"] = to_string_nozzle_diameter(pa_calib.nozzle_diameter);
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "extrusion_cali_del: " << j.dump();
|
||||
@@ -2014,6 +2048,11 @@ int MachineObject::command_get_pa_calibration_tab(const PACalibExtruderInfo &cal
|
||||
j["print"]["nozzle_id"] = _generate_nozzle_id(calib_info.nozzle_volume_type, to_string_nozzle_diameter(calib_info.nozzle_diameter)).ToStdString();
|
||||
j["print"]["nozzle_diameter"] = to_string_nozzle_diameter(calib_info.nozzle_diameter);
|
||||
|
||||
if (calib_info.nozzle_pos_id >= 0) {
|
||||
j["print"]["nozzle_pos"] = calib_info.nozzle_pos_id;
|
||||
j["print"]["nozzle_sn"] = calib_info.nozzle_sn;
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "extrusion_cali_get: " << j.dump();
|
||||
request_tab_from_bbs = true;
|
||||
return this->publish_json(j);
|
||||
@@ -2040,6 +2079,10 @@ int MachineObject::commnad_select_pa_calibration(const PACalibIndexInfo& pa_cali
|
||||
j["print"]["slot_id"] = pa_calib_info.slot_id;
|
||||
j["print"]["cali_idx"] = pa_calib_info.cali_idx;
|
||||
j["print"]["filament_id"] = pa_calib_info.filament_id;
|
||||
if (pa_calib_info.nozzle_pos_id >= 0) {
|
||||
j["print"]["nozzle_pos"] = pa_calib_info.nozzle_pos_id;
|
||||
j["print"]["nozzle_sn"] = pa_calib_info.nozzle_sn;
|
||||
}
|
||||
j["print"]["nozzle_diameter"] = to_string_nozzle_diameter(pa_calib_info.nozzle_diameter);
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "extrusion_cali_sel: " << j.dump();
|
||||
@@ -2379,6 +2422,7 @@ void MachineObject::reset()
|
||||
jobState_ = 0;
|
||||
m_plate_index = -1;
|
||||
device_cert_installed = false;
|
||||
clear_auto_nozzle_mapping();// reset nozzle mapping
|
||||
|
||||
// reset print_json
|
||||
json empty_j;
|
||||
@@ -2896,6 +2940,7 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
|
||||
}
|
||||
|
||||
m_fan->ParseV2_0(jj);
|
||||
m_fila_switch->ParseFilaSwitchInfo(jj);
|
||||
|
||||
if (jj.contains("support_filament_backup")) {
|
||||
if (jj["support_filament_backup"].is_boolean()) {
|
||||
@@ -3000,6 +3045,8 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
|
||||
|
||||
|
||||
if (jj.contains("command")) {
|
||||
m_nozzle_mapping_ptr->ParseAutoNozzleMapping(jj);
|
||||
|
||||
if (jj["command"].get<std::string>() == "ams_change_filament") {
|
||||
if (jj.contains("errno")) {
|
||||
if (jj["errno"].is_number()) {
|
||||
@@ -4082,6 +4129,14 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
|
||||
pa_calib_result.nozzle_volume_type = convert_to_nozzle_type((*it)["nozzle_id"].get<std::string>());
|
||||
}
|
||||
|
||||
if ((*it).contains("nozzle_pos")) {
|
||||
pa_calib_result.nozzle_pos_id = (*it)["nozzle_pos"].get<int>();
|
||||
}
|
||||
|
||||
if ((*it).contains("nozzle_sn")) {
|
||||
pa_calib_result.nozzle_sn = (*it)["nozzle_sn"].get<std::string>();
|
||||
}
|
||||
|
||||
if (jj["nozzle_diameter"].is_number_float()) {
|
||||
pa_calib_result.nozzle_diameter = jj["nozzle_diameter"].get<float>();
|
||||
} else if (jj["nozzle_diameter"].is_string()) {
|
||||
@@ -4179,6 +4234,14 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
|
||||
pa_calib_result.nozzle_volume_type = NozzleVolumeType::nvtStandard;
|
||||
}
|
||||
|
||||
if (it->contains("nozzle_pos")) {
|
||||
pa_calib_result.nozzle_pos_id = (*it)["nozzle_pos"].get<int>();
|
||||
}
|
||||
|
||||
if (it->contains("nozzle_sn")) {
|
||||
pa_calib_result.nozzle_sn = (*it)["nozzle_sn"].get<std::string>();
|
||||
}
|
||||
|
||||
if ((*it)["k_value"].is_number_float())
|
||||
pa_calib_result.k_value = (*it)["k_value"].get<float>();
|
||||
else if ((*it)["k_value"].is_string())
|
||||
@@ -5004,6 +5067,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);
|
||||
@@ -5031,6 +5095,7 @@ void MachineObject::parse_new_info(json print)
|
||||
// fun2 may have infinite length, use get_flag_bits_no_border
|
||||
if (!fun2.empty()) {
|
||||
is_support_print_with_emmc = get_flag_bits_no_border(fun2, 0) == 1;
|
||||
is_support_check_track_switch_match_slice_printer = get_flag_bits_no_border(fun2, 19) == 1;
|
||||
}
|
||||
|
||||
/*aux*/
|
||||
@@ -5066,7 +5131,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;
|
||||
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
|
||||
|
||||
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;
|
||||
@@ -620,6 +635,7 @@ public:
|
||||
|
||||
// fun2
|
||||
bool is_support_print_with_emmc{false};
|
||||
bool is_support_check_track_switch_match_slice_printer{false};
|
||||
|
||||
bool installed_upgrade_kit{false};
|
||||
int bed_temperature_limit = -1;
|
||||
@@ -733,7 +749,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_ */
|
||||
|
||||
@@ -239,7 +239,7 @@ void ExtrusionCalibration::create()
|
||||
m_button_save_result->SetStyle(ButtonStyle::Confirm, ButtonType::Choice);
|
||||
m_button_save_result->Bind(wxEVT_BUTTON, &ExtrusionCalibration::on_click_save, this);
|
||||
|
||||
m_button_last_step = new Button(m_step_2_panel, _L("Back")); // Back for english
|
||||
m_button_last_step = new Button(m_step_2_panel, _CTX("Back", "Navigation")); // Back for english
|
||||
m_button_last_step->SetStyle(ButtonStyle::Regular, ButtonType::Choice);
|
||||
m_button_last_step->Bind(wxEVT_BUTTON, &ExtrusionCalibration::on_click_last, this);
|
||||
|
||||
|
||||
@@ -939,6 +939,7 @@ bool TextCtrl::value_was_changed()
|
||||
case coString:
|
||||
case coStrings:
|
||||
case coFloatOrPercent:
|
||||
case coFloatsOrPercents:
|
||||
return boost::any_cast<std::string>(m_value) != boost::any_cast<std::string>(val);
|
||||
default:
|
||||
return true;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -221,16 +221,7 @@ FileArchiveDialog::FileArchiveDialog(wxWindow* parent_window, mz_zip_archive* ar
|
||||
std::vector<std::pair<boost::filesystem::path, size_t>> filtered_entries; // second is unzipped size
|
||||
for (mz_uint i = 0; i < num_entries; ++i) {
|
||||
if (mz_zip_reader_file_stat(archive, i, &stat)) {
|
||||
std::string extra(1024, 0);
|
||||
boost::filesystem::path path;
|
||||
size_t extra_size = mz_zip_reader_get_filename_from_extra(archive, i, extra.data(), extra.size());
|
||||
if (extra_size > 0) {
|
||||
path = boost::filesystem::path(extra.substr(0, extra_size));
|
||||
} else {
|
||||
wxString wname = boost::nowide::widen(stat.m_filename);
|
||||
std::string name = into_u8(wname);
|
||||
path = boost::filesystem::path(name);
|
||||
}
|
||||
boost::filesystem::path path = Slic3r::decode_archive_entry_path(archive, stat);
|
||||
assert(!path.empty());
|
||||
if (!path.has_extension())
|
||||
continue;
|
||||
|
||||
@@ -489,7 +489,7 @@ void GCodeViewer::SequentialView::Marker::render_position_window(const libvgcode
|
||||
const float main_row_h = 2.0f * text_h + item_spacing_y; // Two lines of text (position and detail) + spacing between them
|
||||
const float properties_h = static_cast<float>(properties_rows.size()) * (text_h + 2.0f * cell_pad_y) + 2.0f * cell_pad_y + 1.0f + item_spacing_y // table rows
|
||||
+ item_spacing_y + show_button_h // Spacing() + Show/Hide button row
|
||||
+ item_spacing_y + 1.0f + style.FramePadding.y; // Spacing() + Separator() + Dummy()
|
||||
+ item_spacing_y + 1.0f + style.WindowPadding.y; // Spacing() + Separator() + Dummy()
|
||||
const float folded_window_h = std::ceil(window_pad_h + main_row_h); // Height of the window when properties are hidden, with padding, rounded up for better look
|
||||
const float unfolded_window_h = std::ceil(folded_window_h + properties_h); // Height of the window when properties are shown, with padding, rounded up for better look
|
||||
const float window_h = properties_shown ? unfolded_window_h : folded_window_h; // Final window height depending on whether properties are shown or not
|
||||
@@ -615,9 +615,12 @@ void GCodeViewer::SequentialView::Marker::render_position_window(const libvgcode
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Dummy({0, style.FramePadding.y});
|
||||
ImGui::Dummy({0, style.WindowPadding.y});
|
||||
}
|
||||
|
||||
float draw_area_height = ImGui::GetTextLineHeight() * 2.f + style.ItemSpacing.y;
|
||||
ImGui::Dummy({10.f, draw_area_height}); // reserve area
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding , 3.f * m_scale);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding , ImVec2(2.f, 2.f) * m_scale);
|
||||
ImGui::PushStyleColor(ImGuiCol_Button , ImVec4(0, 0, 0, 0));
|
||||
@@ -625,6 +628,10 @@ void GCodeViewer::SequentialView::Marker::render_position_window(const libvgcode
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive , ImVec4(84 / 255.f, 84 / 255.f, 90 / 255.f, 1.f));
|
||||
|
||||
const float main_wnd_height = ImGui::GetWindowHeight();
|
||||
const float draw_start_y = main_wnd_height - draw_area_height - style.WindowPadding.y;
|
||||
|
||||
ImGui::SetCursorPos(ImVec2(style.WindowPadding.x, draw_start_y));
|
||||
|
||||
// ORCA use glyph based button for fixing button sizes changing depends on used font size on platform
|
||||
const wchar_t foldIcon = properties_shown ? ImGui::UnfoldButtonIcon : ImGui::FoldButtonIcon;
|
||||
if (imgui.glyph_button(foldIcon, ImVec2(16.f, 16.f) * m_scale)) {
|
||||
@@ -640,10 +647,7 @@ void GCodeViewer::SequentialView::Marker::render_position_window(const libvgcode
|
||||
ImGui::PopStyleColor(3);
|
||||
ImGui::PopStyleVar(2);
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if(!properties_shown)
|
||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY() - style.FramePadding.y); // aligns button with next group
|
||||
ImGui::SetCursorPos(ImVec2(style.WindowPadding.x + style.ItemSpacing.x + 24.f * m_scale, draw_start_y - 1.f * m_scale));
|
||||
|
||||
ImGui::BeginGroup(); // group contents to make information area more compact
|
||||
|
||||
@@ -2911,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;
|
||||
@@ -4258,7 +4265,7 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
|
||||
};
|
||||
|
||||
auto append_print = [&imgui, imperial_units](const ColorRGBA& color, const std::array<float, 4>& offsets, const Times& times, std::pair<double, double> used_filament) {
|
||||
imgui.text(_u8L("Print"));
|
||||
imgui.text(_CTX_utf8("Print", "Noun"));
|
||||
ImGui::SameLine();
|
||||
|
||||
float icon_size = ImGui::GetTextLineHeight();
|
||||
@@ -4292,7 +4299,7 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
|
||||
for (const PartialTime& item : partial_times) {
|
||||
switch (item.type)
|
||||
{
|
||||
case PartialTime::EType::Print: { labels.push_back(_u8L("Print")); break; }
|
||||
case PartialTime::EType::Print: { labels.push_back(_CTX_utf8("Print", "Noun")); break; }
|
||||
case PartialTime::EType::Pause: { labels.push_back(_u8L("Pause")); break; }
|
||||
case PartialTime::EType::ColorChange: { labels.push_back(_u8L("Color change")); break; }
|
||||
}
|
||||
|
||||
+307
-131
@@ -1231,6 +1231,14 @@ GLCanvas3D::~GLCanvas3D()
|
||||
glsafe(::glDeleteTextures(1, &m_ssao_depth_texture_id));
|
||||
m_ssao_depth_texture_id = 0;
|
||||
}
|
||||
if (m_shadow_map_texture_id != 0) {
|
||||
glsafe(::glDeleteTextures(1, &m_shadow_map_texture_id));
|
||||
m_shadow_map_texture_id = 0;
|
||||
}
|
||||
if (m_shadow_map_fbo != 0) {
|
||||
glsafe(::glDeleteFramebuffers(1, &m_shadow_map_fbo));
|
||||
m_shadow_map_fbo = 0;
|
||||
}
|
||||
m_plate_shadow_mask.reset();
|
||||
}
|
||||
m_plate_shadow_mask_key.clear();
|
||||
@@ -2033,6 +2041,9 @@ void GLCanvas3D::render(bool only_init)
|
||||
|
||||
// draw scene
|
||||
glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
|
||||
// Invalidate the shadow map each frame; only the View3D path below rebuilds it. This keeps
|
||||
// the Preview / Assemble canvases from sampling a stale map with an outdated light matrix.
|
||||
m_shadow_map_valid = false;
|
||||
_render_background();
|
||||
|
||||
//BBS add partplater rendering logic
|
||||
@@ -2057,11 +2068,13 @@ void GLCanvas3D::render(bool only_init)
|
||||
_render_platelist(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), only_current, only_body, hover_id, true, show_grid);
|
||||
|
||||
//BBS: add outline logic
|
||||
_render_cast_shadows_on_plate(camera.get_view_matrix(), camera.get_projection_matrix());
|
||||
// Depth pass for object-on-object and self shadows; consumed by the gouraud shader below.
|
||||
_render_shadows(camera.get_view_matrix(), camera.get_projection_matrix());
|
||||
_render_objects(GLVolumeCollection::ERenderType::Opaque, !m_gizmos.is_running());
|
||||
_render_sla_slices();
|
||||
_render_selection();
|
||||
_render_objects(GLVolumeCollection::ERenderType::Transparent, !m_gizmos.is_running());
|
||||
_render_wireframe_overlay();
|
||||
}
|
||||
/* preview render */
|
||||
else if (m_canvas_type == ECanvasType::CanvasPreview && m_render_preview) {
|
||||
@@ -2087,6 +2100,7 @@ void GLCanvas3D::render(bool only_init)
|
||||
//_render_selection();
|
||||
// BBS: add outline logic
|
||||
_render_objects(GLVolumeCollection::ERenderType::Transparent, !m_gizmos.is_running());
|
||||
_render_wireframe_overlay();
|
||||
}
|
||||
|
||||
_render_sequential_clearance();
|
||||
@@ -6039,11 +6053,11 @@ void GLCanvas3D::_render_3d_navigator()
|
||||
strcpy(style.AxisLabels[ImGuizmo::Axis::Axis_Y], "Z"); // ORCA use uppercase to match text on tranform widgets
|
||||
strcpy(style.AxisLabels[ImGuizmo::Axis::Axis_Z], "X"); // ORCA use uppercase to match text on tranform widgets
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_FRONT], _utf8("Front").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_BACK], _utf8("Back").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_BACK], _CTX_utf8("Back", "Camera View").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_TOP], _utf8("Top").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_BOTTOM], _utf8("Bottom").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_LEFT], _CTX_utf8(L_CONTEXT("Left", "Camera"), "Camera").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_RIGHT], _CTX_utf8(L_CONTEXT("Right", "Camera"), "Camera").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_LEFT], _CTX_utf8("Left", "Camera View").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_RIGHT], _CTX_utf8("Right", "Camera View").c_str());
|
||||
|
||||
float sc = get_scale();
|
||||
#ifdef WIN32
|
||||
@@ -7861,9 +7875,8 @@ void GLCanvas3D::_render_platelist(const Transform3d& view_matrix, const Transfo
|
||||
wxGetApp().plater()->get_partplate_list().render(view_matrix, projection_matrix, bottom, only_current, only_body, hover_id, render_cali, show_grid);
|
||||
}
|
||||
|
||||
void GLCanvas3D::_render_cast_shadows_on_plate(const Transform3d& view_matrix, const Transform3d& projection_matrix)
|
||||
void GLCanvas3D::_render_shadows(const Transform3d& view_matrix, const Transform3d& projection_matrix)
|
||||
{
|
||||
// Check if shadow rendering is enabled in configuration
|
||||
if (wxGetApp().app_config == nullptr)
|
||||
return;
|
||||
if (!wxGetApp().app_config->get_bool(SETTING_OPENGL_REALISTIC_MODE))
|
||||
@@ -7877,54 +7890,181 @@ void GLCanvas3D::_render_cast_shadows_on_plate(const Transform3d& view_matrix, c
|
||||
if (shader == nullptr)
|
||||
return;
|
||||
|
||||
// Fixed light direction (pointing downward at an angle)
|
||||
// Drive shadow direction from current view angle: define light in eye-space,
|
||||
// then transform it to world-space with inverse view rotation.
|
||||
const Vec3d light_dir_eye = Vec3d(-0.4574957, 0.4574957, 0.7624929).normalized();
|
||||
const Matrix3d view_rot = view_matrix.matrix().block<3, 3>(0, 0);
|
||||
const Vec3d light_dir_to_light = (view_rot.transpose() * light_dir_eye).normalized();
|
||||
const Vec3d ray_dir = -light_dir_to_light; // Direction of shadow projection
|
||||
|
||||
if (std::abs(ray_dir.z()) < 1e-6)
|
||||
if (OpenGLManager::get_framebuffers_type() == OpenGLManager::EFramebufferType::Arb) {
|
||||
|
||||
// Light direction (same as used in shading and plate shading)
|
||||
const Vec3d light_dir_eye = Vec3d(-0.4574957, 0.4574957, 0.7624929).normalized();
|
||||
const Matrix3d view_rot = view_matrix.matrix().block<3, 3>(0, 0);
|
||||
const Vec3d dir_to_light = (view_rot.transpose() * light_dir_eye).normalized();
|
||||
|
||||
// Bounding box of the printable objects (the shadow casters).
|
||||
BoundingBoxf3 obj_bb;
|
||||
for (const GLVolume* volume : m_volumes.volumes) {
|
||||
if (volume == nullptr || !volume->is_active || !volume->printable || volume->is_modifier || volume->is_wipe_tower)
|
||||
continue;
|
||||
obj_bb.merge(volume->transformed_bounding_box());
|
||||
}
|
||||
if (!obj_bb.defined)
|
||||
return; // no objects to cast shadows
|
||||
|
||||
// Orthographic light-space basis (z points toward the light).
|
||||
const Vec3d up = (std::abs(dir_to_light.z()) > 0.99) ? Vec3d::UnitY() : Vec3d::UnitZ();
|
||||
const Vec3d z_axis = dir_to_light;
|
||||
const Vec3d x_axis = up.cross(z_axis).normalized();
|
||||
const Vec3d y_axis = z_axis.cross(x_axis).normalized();
|
||||
|
||||
// Fit the frustum to the object AABB *and* the object's shadow projected onto the plate
|
||||
// (clamped to the plate footprint). This keeps the map tight/high-res for short shadows
|
||||
// while still covering long shadows at grazing light angles, which previously fell outside
|
||||
// the map and were clipped.
|
||||
const Vec3d ray_dir = -dir_to_light; // direction the shadow travels
|
||||
const BoundingBoxf3 plate_bb = m_bed.build_volume().valid() ? m_bed.build_volume().bounding_volume() : obj_bb;
|
||||
|
||||
Vec3d lmin(DBL_MAX, DBL_MAX, DBL_MAX);
|
||||
Vec3d lmax(-DBL_MAX, -DBL_MAX, -DBL_MAX);
|
||||
auto enclose = [&](const Vec3d& p) {
|
||||
const Vec3d lp(x_axis.dot(p), y_axis.dot(p), z_axis.dot(p));
|
||||
lmin = lmin.cwiseMin(lp);
|
||||
lmax = lmax.cwiseMax(lp);
|
||||
};
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
const Vec3d corner((i & 1) ? obj_bb.max.x() : obj_bb.min.x(),
|
||||
(i & 2) ? obj_bb.max.y() : obj_bb.min.y(),
|
||||
(i & 4) ? obj_bb.max.z() : obj_bb.min.z());
|
||||
enclose(corner);
|
||||
// Where this corner's shadow lands on z = 0, clamped to the plate so a grazing angle
|
||||
// (t -> infinity) stays bounded.
|
||||
if (ray_dir.z() < -1e-6) {
|
||||
const double t = -corner.z() / ray_dir.z();
|
||||
Vec3d s = corner + t * ray_dir;
|
||||
s.x() = std::min(std::max(s.x(), plate_bb.min.x()), plate_bb.max.x());
|
||||
s.y() = std::min(std::max(s.y(), plate_bb.min.y()), plate_bb.max.y());
|
||||
s.z() = 0.0;
|
||||
enclose(s);
|
||||
}
|
||||
}
|
||||
|
||||
// Light "camera" placed just past the nearest enclosed point, looking toward the scene.
|
||||
const double range = lmax.z() - lmin.z();
|
||||
const double margin = std::max(1.0, 0.05 * range);
|
||||
const double cx = 0.5 * (lmin.x() + lmax.x());
|
||||
const double cy = 0.5 * (lmin.y() + lmax.y());
|
||||
const Vec3d eye = x_axis * cx + y_axis * cy + z_axis * (lmax.z() + margin);
|
||||
|
||||
Matrix4d light_view = Matrix4d::Identity();
|
||||
light_view.block<1, 3>(0, 0) = x_axis.transpose();
|
||||
light_view.block<1, 3>(1, 0) = y_axis.transpose();
|
||||
light_view.block<1, 3>(2, 0) = z_axis.transpose();
|
||||
light_view(0, 3) = -x_axis.dot(eye);
|
||||
light_view(1, 3) = -y_axis.dot(eye);
|
||||
light_view(2, 3) = -z_axis.dot(eye);
|
||||
|
||||
// Ortho fit to the light-space extent (symmetric in X/Y around cx,cy; +2% edge padding).
|
||||
const double halfx = std::max(0.5 * (lmax.x() - lmin.x()), 1.0) * 1.02;
|
||||
const double halfy = std::max(0.5 * (lmax.y() - lmin.y()), 1.0) * 1.02;
|
||||
const double near_z = margin * 0.5;
|
||||
const double far_z = range + margin * 1.5;
|
||||
Matrix4d light_proj = Matrix4d::Identity();
|
||||
light_proj(0, 0) = 1.0 / halfx;
|
||||
light_proj(1, 1) = 1.0 / halfy;
|
||||
light_proj(2, 2) = -2.0 / (far_z - near_z);
|
||||
light_proj(2, 3) = -(far_z + near_z) / (far_z - near_z);
|
||||
|
||||
m_shadow_light_vp = Transform3d(light_proj * light_view);
|
||||
|
||||
// Create / resize the depth texture and FBO
|
||||
const unsigned int size = 2048;
|
||||
if (m_shadow_map_texture_id == 0) {
|
||||
glsafe(::glGenTextures(1, &m_shadow_map_texture_id));
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, m_shadow_map_texture_id));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER));
|
||||
const float border[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
|
||||
glsafe(::glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border));
|
||||
m_shadow_map_size = 0;
|
||||
}
|
||||
if (m_shadow_map_size != size) {
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, m_shadow_map_texture_id));
|
||||
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, size, size, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr));
|
||||
m_shadow_map_size = size;
|
||||
}
|
||||
if (m_shadow_map_fbo == 0)
|
||||
glsafe(::glGenFramebuffers(1, &m_shadow_map_fbo));
|
||||
|
||||
// Save OpenGL state that we will modify
|
||||
GLint prev_viewport[4] = { 0, 0, 0, 0 };
|
||||
glsafe(::glGetIntegerv(GL_VIEWPORT, prev_viewport));
|
||||
GLint prev_fbo = 0;
|
||||
glsafe(::glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prev_fbo));
|
||||
GLboolean prev_color_mask[4] = { GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE };
|
||||
glsafe(::glGetBooleanv(GL_COLOR_WRITEMASK, prev_color_mask));
|
||||
const GLboolean prev_cull = ::glIsEnabled(GL_CULL_FACE);
|
||||
GLint prev_depth_func = GL_LESS;
|
||||
glsafe(::glGetIntegerv(GL_DEPTH_FUNC, &prev_depth_func));
|
||||
GLboolean prev_depth_mask = GL_TRUE;
|
||||
glsafe(::glGetBooleanv(GL_DEPTH_WRITEMASK, &prev_depth_mask));
|
||||
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, m_shadow_map_fbo));
|
||||
glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_shadow_map_texture_id, 0));
|
||||
glsafe(::glDrawBuffer(GL_NONE));
|
||||
glsafe(::glReadBuffer(GL_NONE));
|
||||
|
||||
if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
|
||||
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, static_cast<GLuint>(prev_fbo)));
|
||||
m_shadow_map_valid = false;
|
||||
} else {
|
||||
glsafe(::glViewport(0, 0, size, size));
|
||||
glsafe(::glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE));
|
||||
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||
glsafe(::glDepthMask(GL_TRUE));
|
||||
glsafe(::glDepthFunc(GL_LESS));
|
||||
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
|
||||
glsafe(::glEnable(GL_POLYGON_OFFSET_FILL));
|
||||
glsafe(::glPolygonOffset(4.0f, 4.0f));
|
||||
glsafe(::glDisable(GL_CULL_FACE));
|
||||
|
||||
shader->start_using();
|
||||
shader->set_uniform("projection_matrix", Transform3d(light_proj));
|
||||
for (GLVolume* volume : m_volumes.volumes) {
|
||||
if (volume == nullptr || !volume->is_active || !volume->printable || volume->is_modifier || volume->is_wipe_tower)
|
||||
continue;
|
||||
const Transform3d view_model = Transform3d(light_view) * volume->world_matrix();
|
||||
shader->set_uniform("view_model_matrix", view_model);
|
||||
volume->model.render(shader);
|
||||
}
|
||||
shader->stop_using();
|
||||
|
||||
// Restore state
|
||||
glsafe(::glDisable(GL_POLYGON_OFFSET_FILL));
|
||||
glsafe(::glColorMask(prev_color_mask[0], prev_color_mask[1], prev_color_mask[2], prev_color_mask[3]));
|
||||
if (prev_cull)
|
||||
glsafe(::glEnable(GL_CULL_FACE));
|
||||
else
|
||||
glsafe(::glDisable(GL_CULL_FACE));
|
||||
glsafe(::glDepthFunc(prev_depth_func));
|
||||
glsafe(::glDepthMask(prev_depth_mask));
|
||||
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, static_cast<GLuint>(prev_fbo)));
|
||||
glsafe(::glViewport(prev_viewport[0], prev_viewport[1], prev_viewport[2], prev_viewport[3]));
|
||||
|
||||
m_shadow_map_valid = true;
|
||||
}
|
||||
} else {
|
||||
m_shadow_map_valid = false;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------
|
||||
// Unified plate shadow: draw the build-plate footprint and darken it wherever the same
|
||||
// depth shadow map (built above) says the light is occluded. This replaces the old planar
|
||||
// stencil projection so plate, object and self shadows all come from one technique.
|
||||
// ----------------------------------------------------------------------------------
|
||||
if (!m_shadow_map_valid)
|
||||
return;
|
||||
|
||||
// Shadow projection matrix - flattens geometry onto Z=0 plane along light direction
|
||||
Matrix4d shadow_proj = Matrix4d::Identity();
|
||||
shadow_proj(0, 2) = -ray_dir.x() / ray_dir.z();
|
||||
shadow_proj(1, 2) = -ray_dir.y() / ray_dir.z();
|
||||
shadow_proj(2, 0) = 0.0;
|
||||
shadow_proj(2, 1) = 0.0;
|
||||
shadow_proj(2, 2) = 0.0;
|
||||
shadow_proj(2, 3) = 0.01; // Bias to prevent shadow acne
|
||||
GLShaderProgram* plate_shader = wxGetApp().get_shader("printbed_shadow");
|
||||
if (plate_shader == nullptr)
|
||||
return;
|
||||
|
||||
// Save OpenGL state
|
||||
GLint prev_depth_func = GL_LESS;
|
||||
glsafe(::glGetIntegerv(GL_DEPTH_FUNC, &prev_depth_func));
|
||||
GLboolean prev_depth_mask = GL_TRUE;
|
||||
glsafe(::glGetBooleanv(GL_DEPTH_WRITEMASK, &prev_depth_mask));
|
||||
GLint prev_stencil_mask = 0xFF;
|
||||
glsafe(::glGetIntegerv(GL_STENCIL_WRITEMASK, &prev_stencil_mask));
|
||||
GLboolean prev_stencil_test = GL_FALSE;
|
||||
glsafe(::glGetBooleanv(GL_STENCIL_TEST, &prev_stencil_test));
|
||||
|
||||
// ============================================================
|
||||
// PASS 0: Create stencil mask for the build plate (value = 1)
|
||||
// ============================================================
|
||||
glsafe(::glEnable(GL_STENCIL_TEST));
|
||||
glsafe(::glStencilMask(0xFF));
|
||||
glsafe(::glClearStencil(0));
|
||||
glsafe(::glClear(GL_STENCIL_BUFFER_BIT));
|
||||
|
||||
glsafe(::glStencilFunc(GL_ALWAYS, 1, 0xFF));
|
||||
glsafe(::glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE));
|
||||
|
||||
glsafe(::glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE));
|
||||
glsafe(::glDisable(GL_DEPTH_TEST));
|
||||
|
||||
shader->start_using();
|
||||
shader->set_uniform("projection_matrix", projection_matrix);
|
||||
|
||||
// Draw the build plate (cached model to avoid per-frame uploads)
|
||||
if (const BuildVolume& build_volume = m_bed.build_volume(); build_volume.valid()) {
|
||||
const std::string mask_key = build_volume.type() == BuildVolume_Type::Rectangle
|
||||
? (boost::format("rect|%1$.5f|%2$.5f|%3$.5f|%4$.5f")
|
||||
@@ -7980,87 +8120,49 @@ void GLCanvas3D::_render_cast_shadows_on_plate(const Transform3d& view_matrix, c
|
||||
}
|
||||
|
||||
if (m_plate_shadow_mask.is_initialized()) {
|
||||
shader->set_uniform("view_model_matrix", view_matrix);
|
||||
m_plate_shadow_mask.render(shader);
|
||||
// Blend the shadow over the already-drawn plate. Depth test keeps it behind anything
|
||||
// already in front; depth writes are off, and a small negative polygon offset lifts it
|
||||
// just above the bed to avoid z-fighting.
|
||||
GLboolean prev_depth_mask = GL_TRUE;
|
||||
glsafe(::glGetBooleanv(GL_DEPTH_WRITEMASK, &prev_depth_mask));
|
||||
GLint prev_depth_func = GL_LESS;
|
||||
glsafe(::glGetIntegerv(GL_DEPTH_FUNC, &prev_depth_func));
|
||||
|
||||
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||
glsafe(::glDepthMask(GL_FALSE));
|
||||
glsafe(::glDepthFunc(GL_LEQUAL));
|
||||
glsafe(::glEnable(GL_BLEND));
|
||||
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
|
||||
glsafe(::glEnable(GL_POLYGON_OFFSET_FILL));
|
||||
glsafe(::glPolygonOffset(-1.0f, -1.0f));
|
||||
|
||||
glsafe(::glActiveTexture(GL_TEXTURE4));
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, m_shadow_map_texture_id));
|
||||
glsafe(::glActiveTexture(GL_TEXTURE0));
|
||||
|
||||
plate_shader->start_using();
|
||||
plate_shader->set_uniform("view_model_matrix", view_matrix);
|
||||
plate_shader->set_uniform("projection_matrix", projection_matrix);
|
||||
plate_shader->set_uniform("shadow_map", 4);
|
||||
plate_shader->set_uniform("shadow_light_vp", m_shadow_light_vp);
|
||||
plate_shader->set_uniform("shadow_intensity", 0.35f);
|
||||
plate_shader->set_uniform("shadow_map_texel", 1.0f / static_cast<float>(m_shadow_map_size));
|
||||
m_plate_shadow_mask.render(plate_shader);
|
||||
plate_shader->stop_using();
|
||||
|
||||
glsafe(::glActiveTexture(GL_TEXTURE4));
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
|
||||
glsafe(::glActiveTexture(GL_TEXTURE0));
|
||||
|
||||
glsafe(::glDisable(GL_POLYGON_OFFSET_FILL));
|
||||
glsafe(::glDisable(GL_BLEND));
|
||||
glsafe(::glDepthFunc(prev_depth_func));
|
||||
glsafe(::glDepthMask(prev_depth_mask));
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// PASS 1: Project object shadows onto plate (increment stencil to 2)
|
||||
// ============================================================
|
||||
// Only render where plate exists (stencil == 1), then increment to 2
|
||||
glsafe(::glStencilFunc(GL_EQUAL, 1, 0xFF));
|
||||
glsafe(::glStencilOp(GL_KEEP, GL_KEEP, GL_INCR));
|
||||
|
||||
glsafe(::glDepthMask(GL_FALSE));
|
||||
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||
glsafe(::glDepthFunc(GL_ALWAYS)); // Shadows don't need depth testing
|
||||
glsafe(::glEnable(GL_POLYGON_OFFSET_FILL));
|
||||
glsafe(::glPolygonOffset(-2.0f, -2.0f));
|
||||
glsafe(::glDisable(GL_CULL_FACE));
|
||||
|
||||
// Render projected shadow geometry
|
||||
for (GLVolume* volume : m_volumes.volumes) {
|
||||
if (volume == nullptr || !volume->is_active || !volume->printable || volume->is_modifier || volume->is_wipe_tower)
|
||||
continue;
|
||||
|
||||
// CRITICAL FIX: Apply shadow projection in object's local space, then to world, then to view
|
||||
// This ensures shadows are cast from the object's actual position
|
||||
Matrix4d world_matrix = volume->world_matrix().matrix();
|
||||
|
||||
// Project the shadow - this flattens the geometry onto Z=0 in WORLD space
|
||||
Matrix4d shadow_world_matrix = shadow_proj * world_matrix;
|
||||
|
||||
// Transform to view space for rendering
|
||||
Matrix4d view_shadow_matrix = view_matrix.matrix() * shadow_world_matrix;
|
||||
|
||||
shader->set_uniform("view_model_matrix", view_shadow_matrix);
|
||||
shader->set_uniform("projection_matrix", projection_matrix);
|
||||
|
||||
volume->model.render(shader);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// PASS 2: Draw shadow color where stencil == 2
|
||||
// ============================================================
|
||||
glsafe(::glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE));
|
||||
glsafe(::glStencilFunc(GL_EQUAL, 2, 0xFF));
|
||||
glsafe(::glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP));
|
||||
glsafe(::glStencilMask(0x00));
|
||||
|
||||
glsafe(::glDepthFunc(GL_ALWAYS));
|
||||
glsafe(::glEnable(GL_BLEND));
|
||||
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
|
||||
|
||||
// Draw shadow fill
|
||||
shader->set_uniform("view_model_matrix", Transform3d::Identity());
|
||||
shader->set_uniform("projection_matrix", Transform3d::Identity());
|
||||
|
||||
const ColorRGBA shadow_fill_color(0.0f, 0.0f, 0.0f, 0.4f); // Darker shadow for visibility
|
||||
const ColorRGBA prev_bg_color = m_background.get_geometry().color;
|
||||
m_background.set_color(shadow_fill_color);
|
||||
shader->set_uniform("uniform_color", shadow_fill_color);
|
||||
m_background.render(shader);
|
||||
m_background.set_color(prev_bg_color);
|
||||
shader->set_uniform("uniform_color", prev_bg_color);
|
||||
|
||||
shader->stop_using();
|
||||
|
||||
// ============================================================
|
||||
// RESTORE STATE
|
||||
// ============================================================
|
||||
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||
glsafe(::glDepthMask(prev_depth_mask));
|
||||
glsafe(::glDepthFunc(prev_depth_func));
|
||||
glsafe(::glEnable(GL_CULL_FACE));
|
||||
glsafe(::glDisable(GL_POLYGON_OFFSET_FILL));
|
||||
glsafe(::glDisable(GL_BLEND));
|
||||
|
||||
if (!prev_stencil_test)
|
||||
glsafe(::glDisable(GL_STENCIL_TEST));
|
||||
glsafe(::glStencilMask(prev_stencil_mask));
|
||||
}
|
||||
|
||||
|
||||
void GLCanvas3D::_render_plane() const
|
||||
{
|
||||
;//TODO render assemble plane
|
||||
@@ -8139,12 +8241,31 @@ 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();
|
||||
|
||||
const bool phong_ssao = wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool(SETTING_OPENGL_PHONG_SSAO);
|
||||
shader->set_uniform("enable_ssao", phong_ssao);
|
||||
|
||||
// Object-on-object and self shadows: sample the depth map built in _render_shadow_map_pass().
|
||||
// shadow_intensity == 0 disables the effect entirely (unchanged behavior when off / unsupported).
|
||||
if (m_shadow_map_valid && m_shadow_map_texture_id != 0) {
|
||||
glsafe(::glActiveTexture(GL_TEXTURE4));
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, m_shadow_map_texture_id));
|
||||
glsafe(::glActiveTexture(GL_TEXTURE0));
|
||||
shader->set_uniform("shadow_map", 4);
|
||||
shader->set_uniform("shadow_light_vp", m_shadow_light_vp);
|
||||
shader->set_uniform("shadow_map_texel", 1.0f / static_cast<float>(m_shadow_map_size));
|
||||
shader->set_uniform("shadow_intensity", 0.35f);
|
||||
}
|
||||
else
|
||||
shader->set_uniform("shadow_intensity", 0.0f);
|
||||
|
||||
const Size& cvn_size = get_canvas_size();
|
||||
{
|
||||
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||
@@ -8186,7 +8307,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 {
|
||||
@@ -8221,7 +8342,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();
|
||||
@@ -8236,12 +8357,58 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with
|
||||
shader->set_uniform("show_wireframe", false);
|
||||
}*/
|
||||
|
||||
if (m_shadow_map_valid && m_shadow_map_texture_id != 0) {
|
||||
glsafe(::glActiveTexture(GL_TEXTURE4));
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
|
||||
glsafe(::glActiveTexture(GL_TEXTURE0));
|
||||
}
|
||||
|
||||
shader->stop_using();
|
||||
}
|
||||
|
||||
m_camera_clipping_plane = ClippingPlane::ClipsNothing();
|
||||
}
|
||||
|
||||
void GLCanvas3D::_render_wireframe_overlay()
|
||||
{
|
||||
if (!wxGetApp().plater()->is_show_wireframe())
|
||||
return;
|
||||
if (m_volumes.empty())
|
||||
return;
|
||||
|
||||
#if SLIC3R_OPENGL_ES
|
||||
GLShaderProgram* shader = wxGetApp().get_shader("wireframe");
|
||||
#else
|
||||
GLShaderProgram* shader = wxGetApp().get_shader("mm_contour");
|
||||
#endif
|
||||
if (shader == nullptr)
|
||||
return;
|
||||
|
||||
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||
const Transform3d& view_matrix = camera.get_view_matrix();
|
||||
const Transform3d& proj_matrix = camera.get_projection_matrix();
|
||||
const Size sz = get_canvas_size();
|
||||
|
||||
shader->start_using();
|
||||
shader->set_uniform("offset", OpenGLManager::get_gl_info().is_mesa() ? 0.0005 : 0.00001);
|
||||
shader->set_uniform("view_model_matrix", view_matrix);
|
||||
shader->set_uniform("projection_matrix", proj_matrix);
|
||||
|
||||
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||
#if !SLIC3R_OPENGL_ES
|
||||
if (!OpenGLManager::get_gl_info().is_core_profile())
|
||||
glsafe(::glLineWidth(1.0f));
|
||||
glsafe(::glPolygonMode(GL_FRONT_AND_BACK, GL_LINE));
|
||||
#endif
|
||||
|
||||
m_volumes.render(GLVolumeCollection::ERenderType::Opaque, false, view_matrix, proj_matrix, sz);
|
||||
|
||||
#if !SLIC3R_OPENGL_ES
|
||||
glsafe(::glPolygonMode(GL_FRONT_AND_BACK, GL_FILL));
|
||||
#endif
|
||||
shader->stop_using();
|
||||
}
|
||||
|
||||
//BBS: GUI refactor: add canvas size as parameters
|
||||
void GLCanvas3D::_render_gcode(int canvas_width, int canvas_height)
|
||||
{
|
||||
@@ -9280,8 +9447,14 @@ void GLCanvas3D::_render_canvas_toolbar()
|
||||
[this]{wxGetApp().toggle_show_outline();}
|
||||
);
|
||||
|
||||
create_menu_item( _utf8(L("Wireframe")),
|
||||
m_canvas_type != ECanvasType::CanvasPreview, // not work on preview
|
||||
p->is_show_wireframe(),
|
||||
[this, p]{p->toggle_show_wireframe(); m_dirty = true;}
|
||||
);
|
||||
|
||||
create_menu_item( _utf8(L("Realistic View")),
|
||||
true,
|
||||
m_canvas_type != ECanvasType::CanvasPreview, // not work on preview
|
||||
cfg->get_bool(SETTING_OPENGL_REALISTIC_MODE),
|
||||
[this, &cfg]{
|
||||
cfg->set_bool(SETTING_OPENGL_REALISTIC_MODE, !cfg->get_bool(SETTING_OPENGL_REALISTIC_MODE));
|
||||
@@ -10126,6 +10299,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,
|
||||
|
||||
@@ -732,6 +732,12 @@ public:
|
||||
std::array<unsigned int, 2> m_ssao_texture_size{ { 0, 0 } };
|
||||
GLModel m_plate_shadow_mask;
|
||||
std::string m_plate_shadow_mask_key;
|
||||
// Depth-based shadow map used to cast object shadows onto other objects and themselves.
|
||||
unsigned int m_shadow_map_fbo{ 0 };
|
||||
unsigned int m_shadow_map_texture_id{ 0 };
|
||||
unsigned int m_shadow_map_size{ 0 };
|
||||
Transform3d m_shadow_light_vp{ Transform3d::Identity() };
|
||||
bool m_shadow_map_valid{ false };
|
||||
public:
|
||||
explicit GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed);
|
||||
~GLCanvas3D();
|
||||
@@ -1251,11 +1257,14 @@ private:
|
||||
void _render_ssao_pass(unsigned int width, unsigned int height);
|
||||
void _render_background();
|
||||
void _render_bed(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool show_axes);
|
||||
void _render_cast_shadows_on_plate(const Transform3d& view_matrix, const Transform3d& projection_matrix);
|
||||
// Build the light-space depth shadow map (consumed by gouraud/phong for object & self shadows)
|
||||
// and cast it onto the build plate. Realistic view only.
|
||||
void _render_shadows(const Transform3d& view_matrix, const Transform3d& projection_matrix);
|
||||
//BBS: add part plate related logic
|
||||
void _render_platelist(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool only_current, bool only_body = false, int hover_id = -1, bool render_cali = false, bool show_grid = true);
|
||||
//BBS: add outline drawing logic
|
||||
void _render_objects(GLVolumeCollection::ERenderType type, bool with_outline = true);
|
||||
void _render_wireframe_overlay();
|
||||
//BBS: GUI refactor: add canvas size as parameters
|
||||
void _render_gcode(int canvas_width, int canvas_height);
|
||||
//BBS: render a plane for assemble
|
||||
|
||||
@@ -457,10 +457,9 @@ void GLModel::init_from(const indexed_triangle_set& its)
|
||||
data.reserve_indices(3 * its.indices.size());
|
||||
|
||||
// Read user preference: smooth normals enabled
|
||||
const bool realistic_mode = wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool(SETTING_OPENGL_REALISTIC_MODE);
|
||||
const bool smooth_normals_enabled = wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool(SETTING_OPENGL_PHONG_SMOOTH_NORMALS);
|
||||
|
||||
if (realistic_mode && smooth_normals_enabled) {
|
||||
if (smooth_normals_enabled) {
|
||||
// Use per-corner smooth normals (via IGL)
|
||||
using MapMatrixXfUnaligned = Eigen::Map<const Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>>;
|
||||
using MapMatrixXiUnaligned = Eigen::Map<const Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>>;
|
||||
|
||||
@@ -68,6 +68,10 @@ std::pair<bool, std::string> GLShadersManager::init()
|
||||
valid &= append_shader("thumbnail", { prefix + "thumbnail.vs", prefix + "thumbnail.fs"});
|
||||
// used to render printbed
|
||||
valid &= append_shader("printbed", { prefix + "printbed.vs", prefix + "printbed.fs" });
|
||||
#if !SLIC3R_OPENGL_ES
|
||||
// used to cast the object shadow map onto the build plate (realistic view)
|
||||
valid &= append_shader("printbed_shadow", { prefix + "printbed_shadow.vs", prefix + "printbed_shadow.fs" });
|
||||
#endif // !SLIC3R_OPENGL_ES
|
||||
valid &= append_shader("hotbed", {prefix + "hotbed.vs", prefix + "hotbed.fs"});
|
||||
// used to render options in gcode preview
|
||||
if (GUI::wxGetApp().is_gl_version_greater_or_equal_to(3, 3)) {
|
||||
|
||||
+62
-22
@@ -2931,10 +2931,11 @@ bool GUI_App::on_init_inner()
|
||||
//update_label_colours_from_appconfig();
|
||||
}
|
||||
if (bool new_sys_menu_enabled = app_config->get("sys_menu_enabled") == "1";
|
||||
init_sys_menu_enabled != new_sys_menu_enabled)
|
||||
init_sys_menu_enabled != new_sys_menu_enabled) {
|
||||
#ifdef __WINDOWS__
|
||||
NppDarkMode::SetSystemMenuForApp(new_sys_menu_enabled);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
// Orca: we allow user to pin the version of plugin, so we don't need to remove old networking plugins when the app version is updated
|
||||
@@ -3748,8 +3749,24 @@ void GUI_App::switch_printer_agent()
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_agent->get_printer_agent() == new_printer_agent)
|
||||
// The factory caches agents per ID, so an identical pointer means the agent type is unchanged.
|
||||
if (m_agent->get_printer_agent() == new_printer_agent) {
|
||||
// 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 (effective_agent_id != BBL_PRINTER_AGENT_ID && m_device_manager) {
|
||||
const std::string print_host = config.opt_string("print_host");
|
||||
if (!print_host.empty()) {
|
||||
const std::string dev_id = MachineObject::dev_id_from_address(print_host, config.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);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Swap the agent
|
||||
m_agent->set_printer_agent(new_printer_agent);
|
||||
@@ -3760,10 +3777,8 @@ void GUI_App::switch_printer_agent()
|
||||
// Start discovery so Python agents can populate the device list via SSDP callback
|
||||
m_agent->start_discovery(true, false);
|
||||
|
||||
{
|
||||
// Auto-switch MachineObject
|
||||
select_machine(agent_info.id);
|
||||
}
|
||||
// Auto-switch MachineObject (new agent has empty device_info, so always re-select)
|
||||
select_machine(agent_info.id);
|
||||
}
|
||||
|
||||
void GUI_App::select_machine(const std::string& agent_id)
|
||||
@@ -5236,21 +5251,28 @@ void GUI_App::on_http_error(wxCommandEvent &evt)
|
||||
if (plater != nullptr && wxGetApp().imgui()->display_initialized()) {
|
||||
std::string text;
|
||||
|
||||
// Name the specific preset in the header so the user knows which one conflicts.
|
||||
// The agent injects the local preset name into every 409 conflict body, so this is
|
||||
// normally populated; fall back to a generic header if it is somehow missing.
|
||||
const std::string header = conflict_preset_name.empty()
|
||||
? _u8L("Cloud sync conflict:")
|
||||
: format(_u8L("Cloud sync conflict for preset \"%s\":"), conflict_preset_name);
|
||||
|
||||
switch (conflict_code) {
|
||||
case -1:
|
||||
text = _u8L("Cloud sync conflict: this preset has a newer version in OrcaCloud.\n"
|
||||
text = header + " " + _u8L("This preset has a newer version in OrcaCloud.\n"
|
||||
"Pull downloads the cloud copy. Force push overwrites it with your local preset.");
|
||||
break;
|
||||
case -2:
|
||||
text = _u8L("Cloud sync conflict: a preset with this name already exists in OrcaCloud.\n"
|
||||
text = header + " " + _u8L("A preset with this name already exists in OrcaCloud.\n"
|
||||
"Pull downloads the cloud copy. Force push overwrites it with your local preset.");
|
||||
break;
|
||||
case -3:
|
||||
text = _u8L("Cloud sync conflict: a preset with the same name was previously deleted from the cloud.\n"
|
||||
text = header + " " + _u8L("A preset with the same name was previously deleted from the cloud.\n"
|
||||
"Delete will delete your local preset. Force push overwrites it with your local preset.");
|
||||
break;
|
||||
default:
|
||||
text = _u8L("Cloud sync conflict: there was an unexpected or unidentified preset conflict.\n"
|
||||
text = header + " " + _u8L("There was an unexpected or unidentified preset conflict.\n"
|
||||
"Pull downloads the cloud copy. Force push overwrites it with your local preset.");
|
||||
break;
|
||||
};
|
||||
@@ -5269,10 +5291,12 @@ void GUI_App::on_http_error(wxCommandEvent &evt)
|
||||
[this, conflict_setting_id, conflict_preset_name, conflict_user_id](wxEvtHandler*) {
|
||||
if (mainframe == nullptr)
|
||||
return false;
|
||||
MessageDialog
|
||||
dlg(mainframe,
|
||||
_L("Force push will overwrite the cloud copy with your local preset changes.\nDo you want to continue?"),
|
||||
_L("Resolve cloud sync conflict"), wxCENTER | wxYES_NO | wxNO_DEFAULT | wxICON_WARNING);
|
||||
const wxString confirm_msg = conflict_preset_name.empty()
|
||||
? _L("Force push will overwrite the cloud copy with your local preset changes.\nDo you want to continue?")
|
||||
: format_wxstr(_L("Force push will overwrite the cloud copy of preset \"%s\" with your local changes.\nDo you want to continue?"),
|
||||
conflict_preset_name);
|
||||
MessageDialog dlg(mainframe, confirm_msg, _L("Resolve cloud sync conflict"),
|
||||
wxCENTER | wxYES_NO | wxNO_DEFAULT | wxICON_WARNING);
|
||||
if (dlg.ShowModal() != wxID_YES)
|
||||
return false;
|
||||
|
||||
@@ -6693,14 +6717,14 @@ void GUI_App::sync_preset(Preset* preset, bool force)
|
||||
result = 0; // Set to 0 so the sync_info gets saved below
|
||||
|
||||
// Show user notification
|
||||
CallAfter([this] {
|
||||
CallAfter([this, name = preset->name] {
|
||||
static bool size_limit_dialog_notified = false;
|
||||
if (size_limit_dialog_notified)
|
||||
return;
|
||||
size_limit_dialog_notified = true;
|
||||
if (mainframe == nullptr)
|
||||
return;
|
||||
auto msg = _L("The preset content is too large to sync to the cloud (exceeds 1MB). Please reduce the preset size by removing custom configurations or use it locally only.");
|
||||
auto msg = format_wxstr(_L("The preset \"%s\" is too large to sync to the cloud (exceeds 1MB). Please reduce the preset size by removing custom configurations or use it locally only."), name);
|
||||
MessageDialog(mainframe, msg, _L("Sync user presets"), wxICON_WARNING | wxOK).ShowModal();
|
||||
});
|
||||
// NOTE: Don't return here - let execution continue to save the sync_info
|
||||
@@ -7077,6 +7101,10 @@ void GUI_App::start_sync_user_preset(bool with_progress_dlg)
|
||||
// finishFn tears down the progress dialog (and clears the re-entrancy guard), so it
|
||||
// must run on every exit path — otherwise an early bail-out would leak the modal
|
||||
// dialog and leave the guard stuck, blocking all later manual syncs.
|
||||
// Guard the whole thread body: an uncaught exception here (e.g. a transient
|
||||
// boost::filesystem error while scanning the preset folder) would otherwise
|
||||
// propagate out of the thread and terminate the entire application.
|
||||
try {
|
||||
if (!m_agent) { finishFn(false); return; }
|
||||
|
||||
// One-time scan for orphaned .info files left over from offline deletions; queues HTTP DELETEs.
|
||||
@@ -7252,7 +7280,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;
|
||||
}
|
||||
@@ -7318,6 +7346,11 @@ void GUI_App::start_sync_user_preset(bool with_progress_dlg)
|
||||
boost::this_thread::sleep_for(boost::chrono::milliseconds(500));
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << "user preset sync thread terminated by exception: " << e.what();
|
||||
} catch (...) {
|
||||
BOOST_LOG_TRIVIAL(error) << "user preset sync thread terminated by unknown exception";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7825,14 +7858,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
|
||||
@@ -8696,8 +8729,13 @@ void GUI_App::scan_orphaned_info_files()
|
||||
if (!fs::exists(type_dir))
|
||||
continue;
|
||||
|
||||
// Iterate through all .info files
|
||||
for (auto& entry : boost::filesystem::directory_iterator(type_dir)) {
|
||||
// Iterate through all .info files. Use the error_code-based iterator so a transient
|
||||
// directory-read failure (e.g. macOS readdir returning ENOTSUP) is logged and skipped
|
||||
// instead of throwing an uncaught exception that would terminate the app from the
|
||||
// background sync thread this runs on.
|
||||
boost::system::error_code ec;
|
||||
for (boost::filesystem::directory_iterator it(type_dir, ec), end; !ec && it != end; it.increment(ec)) {
|
||||
const auto& entry = *it;
|
||||
if (entry.path().extension() != ".info")
|
||||
continue;
|
||||
|
||||
@@ -8716,6 +8754,8 @@ void GUI_App::scan_orphaned_info_files()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ec)
|
||||
BOOST_LOG_TRIVIAL(warning) << "scan_orphaned_info_files: failed to scan " << type_dir.string() << ": " << ec.message();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -132,6 +132,9 @@ std::map<std::string, std::vector<SimpleSettingData>> SettingsFactory::PART_CATE
|
||||
{"infill_anchor", "", 1},
|
||||
{"infill_anchor_max", "", 1},
|
||||
{"top_surface_pattern", "", 1},
|
||||
{"top_surface_expansion", "", 1},
|
||||
{"top_surface_expansion_margin", "", 1},
|
||||
{"top_surface_expansion_direction", "", 1},
|
||||
{"bottom_surface_pattern", "", 1},
|
||||
{"internal_solid_infill_pattern", "", 1},
|
||||
{"align_infill_direction_to_model", "", 1},
|
||||
@@ -141,6 +144,8 @@ std::map<std::string, std::vector<SimpleSettingData>> SettingsFactory::PART_CATE
|
||||
{"infill_wall_overlap", "", 1},
|
||||
{"top_bottom_infill_wall_overlap", "", 1},
|
||||
{"solid_infill_direction", "", 1},
|
||||
{"top_layer_direction", "", 1},
|
||||
{"bottom_layer_direction", "", 1},
|
||||
{"infill_direction", "", 1},
|
||||
{"bridge_angle", "", 1},
|
||||
{"internal_bridge_angle", "", 1},
|
||||
@@ -185,7 +190,7 @@ std::vector<SimpleSettingData> SettingsFactory::get_visible_options(const std::s
|
||||
//Quality
|
||||
"wall_infill_order", "ironing_type", "inner_wall_line_width", "outer_wall_line_width", "top_surface_line_width",
|
||||
//Shell
|
||||
"wall_loops", "top_shell_layers", "bottom_shell_layers", "top_shell_thickness", "bottom_shell_thickness",
|
||||
"wall_loops", "top_shell_layers", "bottom_shell_layers", "top_shell_thickness", "bottom_shell_thickness", "top_surface_expansion", "top_surface_expansion_margin", "top_surface_expansion_direction",
|
||||
//Infill
|
||||
"sparse_infill_density", "sparse_infill_pattern", "top_surface_pattern", "bottom_surface_pattern", "infill_combination", "infill_direction", "infill_wall_overlap",
|
||||
//speed
|
||||
@@ -590,6 +595,7 @@ wxMenu* MenuFactory::append_submenu_add_handy_model(wxMenu* menu, ModelVolumeTyp
|
||||
static const std::vector<HandyModel> handy_models = {
|
||||
{L("Orca Cube"), {"OrcaCube_v2.drc", "OrcaPlug_v2.drc"}, true},
|
||||
{L("OrcaSliced Combo"), {"OrcaSliced.3mf", "OrcaCube_v2.drc", "OrcaPlug_v2.drc"}, true},
|
||||
{L("Orca Badge"), {"OrcaBadge.3mf"}},
|
||||
{L("Orca Tolerance Test"), {"OrcaToleranceTest.drc"}},
|
||||
{L("3DBenchy"), {"3DBenchy.drc"}},
|
||||
{L("Cali Cat"), {"calicat.drc"}},
|
||||
|
||||
@@ -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
|
||||
@@ -2316,9 +2317,9 @@ void ObjectList::load_modifier(const wxArrayString& input_files, ModelObject& mo
|
||||
try {
|
||||
if (boost::iends_with(input_file, ".stp") ||
|
||||
boost::iends_with(input_file, ".step")) {
|
||||
double linear = string_to_double_decimal_point(wxGetApp().app_config->get("linear_defletion"));
|
||||
double linear = string_to_double_decimal_point(wxGetApp().app_config->get("linear_deflection"));
|
||||
if (linear <= 0) linear = 0.003;
|
||||
double angle = string_to_double_decimal_point(wxGetApp().app_config->get("angle_defletion"));
|
||||
double angle = string_to_double_decimal_point(wxGetApp().app_config->get("angle_deflection"));
|
||||
if (angle <= 0) angle = 0.5;
|
||||
bool split_compound = wxGetApp().app_config->get_bool("is_split_compound");
|
||||
model = Model::read_from_step(
|
||||
@@ -2328,8 +2329,8 @@ void ObjectList::load_modifier(const wxArrayString& input_files, ModelObject& mo
|
||||
if (wxGetApp().app_config->get_bool("enable_step_mesh_setting")) {
|
||||
StepMeshDialog mesh_dlg(nullptr, file, linear, angle);
|
||||
if (mesh_dlg.ShowModal() == wxID_OK) {
|
||||
linear_value = mesh_dlg.get_linear_defletion();
|
||||
angle_value = mesh_dlg.get_angle_defletion();
|
||||
linear_value = mesh_dlg.get_linear_deflection();
|
||||
angle_value = mesh_dlg.get_angle_deflection();
|
||||
is_split = mesh_dlg.get_split_compound_value();
|
||||
return 1;
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -1905,7 +1905,7 @@ bool GLGizmoAdvancedCut::render_slider_double_input(const std::string &label, fl
|
||||
mean_size *= float(units_mm_to_in);
|
||||
min_size *= float(units_mm_to_in);
|
||||
}
|
||||
std::string format = value_in < 0.f ? " " : m_imperial_units ? "%.4f " + _u8L("in") : "%.2f " + _u8L("mm");
|
||||
std::string format = value_in < 0.f ? " " : m_imperial_units ? "%.4f " + _CTX_utf8("in", "inches") : "%.2f " + _u8L("mm");
|
||||
|
||||
m_imgui->bbl_slider_float_style(("##" + label).c_str(), &value, min_size, mean_size, format.c_str());
|
||||
|
||||
|
||||
@@ -241,7 +241,7 @@ std::string GLGizmoCut3D::get_tooltip() const
|
||||
std::string tooltip;
|
||||
if (m_hover_id == Z || (m_dragging && m_hover_id == CutPlane)) {
|
||||
double koef = m_imperial_units ? GizmoObjectManipulation::mm_to_in : 1.0;
|
||||
std::string unit_str = " " + (m_imperial_units ? _u8L("in") : _u8L("mm"));
|
||||
std::string unit_str = " " + (m_imperial_units ? _CTX_utf8("in", "inches") : _u8L("mm"));
|
||||
const BoundingBoxf3& tbb = m_transformed_bounding_box;
|
||||
|
||||
const std::string name = m_keep_as_parts ? _u8L("Part") : _u8L("Object");
|
||||
@@ -541,7 +541,7 @@ bool GLGizmoCut3D::render_double_input(const std::string& label, double& value_i
|
||||
ImGui::InputDouble(("##" + label).c_str(), &value, 0.0f, 0.0f, "%.2f", ImGuiInputTextFlags_CharsDecimal);
|
||||
|
||||
ImGui::SameLine();
|
||||
m_imgui->text(m_imperial_units ? _L("in") : _L("mm"));
|
||||
m_imgui->text(m_imperial_units ? _CTX("in", "inches") : _L("mm"));
|
||||
|
||||
value_in = value * (m_imperial_units ? GizmoObjectManipulation::in_to_mm : 1.0);
|
||||
return !is_approx(old_val, value);
|
||||
@@ -582,7 +582,7 @@ bool GLGizmoCut3D::render_slider_two_input(const std::string& label, float& valu
|
||||
if (m_imperial_units) {
|
||||
min_size *= f_mm_to_in;
|
||||
}
|
||||
std::string format = value_in < 0.f ? " " : m_imperial_units ? "%.4f " + _u8L("in") : "%.2f " + _u8L("mm");
|
||||
std::string format = value_in < 0.f ? " " : m_imperial_units ? "%.4f " + _CTX_utf8("in", "inches") : "%.2f " + _u8L("mm");
|
||||
|
||||
m_imgui->bbl_slider_float_style(("##" + label).c_str(), &value, min_size, mean_size, format.c_str());
|
||||
|
||||
@@ -653,7 +653,7 @@ bool GLGizmoCut3D::render_slider_input(const std::string& label, float& value_in
|
||||
if (m_imperial_units) {
|
||||
min_size *= f_mm_to_in;
|
||||
}
|
||||
std::string format = value_in < 0.f ? " " : m_imperial_units ? "%.4f " + _u8L("in") : "%.2f " + _u8L("mm");
|
||||
std::string format = value_in < 0.f ? " " : m_imperial_units ? "%.4f " + _CTX_utf8("in", "inches") : "%.2f " + _u8L("mm");
|
||||
|
||||
m_imgui->bbl_slider_float_style(("##" + label).c_str(), &value, min_size, max_value, format.c_str());
|
||||
|
||||
@@ -2472,7 +2472,7 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors, flo
|
||||
void GLGizmoCut3D::render_build_size()
|
||||
{
|
||||
double koef = m_imperial_units ? GizmoObjectManipulation::mm_to_in : 1.0;
|
||||
wxString unit_str = m_imperial_units ? _L("in") : _L("mm");
|
||||
wxString unit_str = m_imperial_units ? _CTX("in", "inches") : _L("mm");
|
||||
Vec3d tbb_sz = m_transformed_bounding_box.size() * koef; // ORCA
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
@@ -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)
|
||||
|
||||
@@ -1951,7 +1951,7 @@ void GLGizmoEmboss::draw_model_type()
|
||||
float minimum_spacing_x = 8.0f;
|
||||
float minimum_offset_x = ImGui::GetCursorPosX() + minimum_spacing_x;
|
||||
float offset_x = std::max(m_gui_cfg->input_offset, minimum_offset_x);
|
||||
ImGui::SameLine(offset_x);
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGuiWrapper::push_radio_style(m_parent.get_scale()); // ORCA
|
||||
if (ImGui::RadioButton(_u8L("Join").c_str(), type == part))
|
||||
|
||||
@@ -315,6 +315,17 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
|
||||
ImGui::SameLine(drag_left_width + sliders_left_width);
|
||||
ImGui::PushItemWidth(1.5 * slider_icon_width);
|
||||
ImGui::BBLDragFloat("##cursor_radius_input", &m_cursor_radius, 0.05f, 0.0f, 0.0f, "%.2f");
|
||||
|
||||
if (m_imgui->bbl_checkbox(_L("Vertical"), m_vertical_only)) {
|
||||
if (m_vertical_only) {
|
||||
m_horizontal_only = false;
|
||||
}
|
||||
}
|
||||
if (m_imgui->bbl_checkbox(_L("Horizontal"), m_horizontal_only)) {
|
||||
if (m_horizontal_only) {
|
||||
m_vertical_only = false;
|
||||
}
|
||||
}
|
||||
} else if (m_current_tool == ImGui::SphereButtonIcon) {
|
||||
m_cursor_type = TriangleSelector::CursorType::SPHERE;
|
||||
m_tool_type = ToolType::BRUSH;
|
||||
@@ -327,6 +338,17 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
|
||||
ImGui::SameLine(drag_left_width + sliders_left_width);
|
||||
ImGui::PushItemWidth(1.5 * slider_icon_width);
|
||||
ImGui::BBLDragFloat("##cursor_radius_input", &m_cursor_radius, 0.05f, 0.0f, 0.0f, "%.2f");
|
||||
|
||||
if (m_imgui->bbl_checkbox(_L("Vertical"), m_vertical_only)) {
|
||||
if (m_vertical_only) {
|
||||
m_horizontal_only = false;
|
||||
}
|
||||
}
|
||||
if (m_imgui->bbl_checkbox(_L("Horizontal"), m_horizontal_only)) {
|
||||
if (m_horizontal_only) {
|
||||
m_vertical_only = false;
|
||||
}
|
||||
}
|
||||
} else if (m_current_tool == ImGui::FillButtonIcon) {
|
||||
m_cursor_type = TriangleSelector::CursorType::POINTER;
|
||||
m_tool_type = ToolType::SMART_FILL;
|
||||
|
||||
@@ -1252,7 +1252,7 @@ void GLGizmoMeasure::render_dimensioning()
|
||||
const bool use_inches = wxGetApp().app_config->get_bool("use_inches");
|
||||
const double curr_value = use_inches ? GizmoObjectManipulation::mm_to_in * distance : distance;
|
||||
const std::string curr_value_str = format_double(curr_value);
|
||||
const std::string units = use_inches ? _u8L("in") : _u8L("mm");
|
||||
const std::string units = use_inches ? _CTX_utf8("in", "inches") : _u8L("mm");
|
||||
const float value_str_width = 20.0f + ImGui::CalcTextSize(curr_value_str.c_str()).x;
|
||||
static double edit_value = 0.0;
|
||||
|
||||
@@ -1303,7 +1303,7 @@ void GLGizmoMeasure::render_dimensioning()
|
||||
return;
|
||||
|
||||
const double ratio = new_value / old_value;
|
||||
wxGetApp().plater()->take_snapshot(_u8L("Scale"), UndoRedo::SnapshotType::GizmoAction);
|
||||
wxGetApp().plater()->take_snapshot(_CTX_utf8("Scale", "Verb"), UndoRedo::SnapshotType::GizmoAction);
|
||||
// apply scale
|
||||
TransformationType type;
|
||||
type.set_world();
|
||||
@@ -2130,7 +2130,7 @@ void GLGizmoMeasure::show_face_face_assembly_senior()
|
||||
void GLGizmoMeasure::init_render_input_window()
|
||||
{
|
||||
m_use_inches = wxGetApp().app_config->get_bool("use_inches");
|
||||
m_units = m_use_inches ? " " + _u8L("in") : " " + _u8L("mm");
|
||||
m_units = " " + (m_use_inches ? _CTX_utf8("in", "inches") : _u8L("mm"));
|
||||
m_space_size = ImGui::CalcTextSize(" ").x * 2;
|
||||
m_input_size_max = ImGui::CalcTextSize("-100.00").x * 1.2;
|
||||
m_same_model_object = is_two_volume_in_same_model_object();
|
||||
|
||||
@@ -1704,7 +1704,7 @@ void GLGizmoSVG::draw_size()
|
||||
|
||||
if (m_keep_ratio) {
|
||||
std::stringstream ss;
|
||||
ss << std::setprecision(2) << std::fixed << width << " x " << height << " " << (use_inch ? "in" : "mm");
|
||||
ss << std::setprecision(2) << std::fixed << width << " x " << height << " " << (use_inch ? _CTX("in", "inches") : _L("mm"));
|
||||
|
||||
ImGui::SameLine(m_gui_cfg->input_offset);
|
||||
ImGui::SetNextItemWidth(m_gui_cfg->input_width);
|
||||
@@ -1989,7 +1989,7 @@ void GLGizmoSVG::draw_mirroring()
|
||||
|
||||
void GLGizmoSVG::draw_model_type()
|
||||
{
|
||||
ImGui::AlignTextToFramePadding();
|
||||
//ImGui::AlignTextToFramePadding();
|
||||
bool is_last_solid_part = m_volume->is_the_only_one_part();
|
||||
std::string title = _u8L("Operation");
|
||||
if (is_last_solid_part) {
|
||||
@@ -2005,6 +2005,8 @@ void GLGizmoSVG::draw_model_type()
|
||||
ModelVolumeType part = ModelVolumeType::MODEL_PART;
|
||||
ModelVolumeType type = m_volume->type();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
//TRN EmbossOperation
|
||||
ImGuiWrapper::push_radio_style(m_parent.get_scale()); //ORCA
|
||||
if (ImGui::RadioButton(_u8L("Join").c_str(), type == part))
|
||||
|
||||
@@ -143,9 +143,9 @@ bool GLGizmoScale3D::on_init()
|
||||
std::string GLGizmoScale3D::on_get_name() const
|
||||
{
|
||||
if (!on_is_activable() && m_state == EState::Off) {
|
||||
return _u8L("Scale") + ":\n" + _u8L("Please select at least one object.");
|
||||
return _CTX_utf8("Scale", "Verb") + ":\n" + _u8L("Please select at least one object.");
|
||||
} else {
|
||||
return _u8L("Scale");
|
||||
return _CTX_utf8("Scale", "Verb");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ GizmoObjectManipulation::GizmoObjectManipulation(GLCanvas3D& glcanvas)
|
||||
: m_glcanvas(glcanvas)
|
||||
{
|
||||
m_imperial_units = wxGetApp().app_config->get("use_inches") == "1";
|
||||
m_new_unit_string = m_imperial_units ? L("in") : L("mm");
|
||||
m_new_unit_string = m_imperial_units ? L_CONTEXT("in", "inches") : L("mm");
|
||||
|
||||
const wxString shift = GUI::shortkey_shift_prefix();
|
||||
const wxString alt = GUI::shortkey_alt_prefix();
|
||||
@@ -92,7 +92,7 @@ void GizmoObjectManipulation::update_ui_from_settings()
|
||||
if (m_imperial_units != (wxGetApp().app_config->get("use_inches") == "1")) {
|
||||
m_imperial_units = wxGetApp().app_config->get("use_inches") == "1";
|
||||
|
||||
m_new_unit_string = m_imperial_units ? L("in") : L("mm");
|
||||
m_new_unit_string = m_imperial_units ? L_CONTEXT("in", "inches") : L("mm");
|
||||
|
||||
update_buffered_value();
|
||||
}
|
||||
@@ -504,9 +504,12 @@ void GizmoObjectManipulation::on_change(const std::string &opt_key, int axis, do
|
||||
bool GizmoObjectManipulation::render_combo(
|
||||
ImGuiWrapper *imgui_wrapper, const std::string &label, const std::vector<std::string> &lines, size_t &selection_idx, float label_width, float item_width)
|
||||
{
|
||||
ImGui::AlignTextToFramePadding();
|
||||
imgui_wrapper->text(label);
|
||||
ImGui::SameLine(label_width);
|
||||
if(!label.empty()){
|
||||
ImGui::AlignTextToFramePadding();
|
||||
imgui_wrapper->text(label);
|
||||
ImGui::SameLine(label_width);
|
||||
}
|
||||
|
||||
ImGui::PushItemWidth(item_width);
|
||||
|
||||
size_t selection_out = selection_idx;
|
||||
@@ -642,8 +645,9 @@ static const char* label_scale_values[2][3] = {
|
||||
{ "##size_x", "##size_y", "##size_z"}
|
||||
};
|
||||
|
||||
bool GizmoObjectManipulation::reset_button(ImGuiWrapper *imgui_wrapper, float caption_max, float unit_size, float space_size, float end_text_size)
|
||||
bool GizmoObjectManipulation::reset_button(ImGuiWrapper *imgui_wrapper, bool enabled)
|
||||
{
|
||||
imgui_wrapper->disabled_begin(!enabled);
|
||||
bool pressed = false;
|
||||
ImTextureID normal_id = m_glcanvas.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_RESET);
|
||||
ImTextureID hover_id = m_glcanvas.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_RESET_HOVER);
|
||||
@@ -657,14 +661,17 @@ bool GizmoObjectManipulation::reset_button(ImGuiWrapper *imgui_wrapper, float ca
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);
|
||||
|
||||
pressed = ImGui::ImageButton3(normal_id, hover_id, button_size);
|
||||
pressed = ImGui::ImageButton3(normal_id, hover_id, button_size, {0,0}, {1,1}, -1, {0,0,0,0}, {1,1,1, enabled ? 1.f : 0.f}); // ORCA make icon invisible to prevent changes on layout
|
||||
|
||||
ImGui::PopStyleVar(1);
|
||||
|
||||
imgui_wrapper->disabled_end();
|
||||
return pressed;
|
||||
}
|
||||
|
||||
bool GizmoObjectManipulation::reset_zero_button(ImGuiWrapper *imgui_wrapper, float caption_max, float unit_size, float space_size, float end_text_size)
|
||||
bool GizmoObjectManipulation::reset_zero_button(ImGuiWrapper *imgui_wrapper, bool enabled)
|
||||
{
|
||||
imgui_wrapper->disabled_begin(!enabled);
|
||||
bool pressed = false;
|
||||
ImTextureID normal_id = m_glcanvas.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_RESET_ZERO);
|
||||
ImTextureID hover_id = m_glcanvas.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_RESET_ZERO_HOVER);
|
||||
@@ -678,9 +685,11 @@ bool GizmoObjectManipulation::reset_zero_button(ImGuiWrapper *imgui_wrapper, flo
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);
|
||||
|
||||
pressed = ImGui::ImageButton3(normal_id, hover_id, button_size);
|
||||
pressed = ImGui::ImageButton3(normal_id, hover_id, button_size, {0,0}, {1,1}, -1, {0,0,0,0}, {1,1,1, enabled ? 1.f : 0.f}); // ORCA make icon invisible to prevent changes on layout
|
||||
|
||||
ImGui::PopStyleVar(1);
|
||||
|
||||
imgui_wrapper->disabled_end();
|
||||
return pressed;
|
||||
}
|
||||
|
||||
@@ -765,8 +774,17 @@ void GizmoObjectManipulation::do_render_move_window(ImGuiWrapper *imgui_wrapper,
|
||||
};
|
||||
|
||||
float space_size = imgui_wrapper->get_style_scaling() * 8;
|
||||
float position_size = imgui_wrapper->calc_text_size(_L("Position")).x + space_size;
|
||||
float caption_max = imgui_wrapper->calc_text_size(_L("Object coordinates")).x + 2 * space_size;
|
||||
//ORCA
|
||||
float coord_combo_width = std::max({
|
||||
imgui_wrapper->calc_text_size(_L("World")).x,
|
||||
imgui_wrapper->calc_text_size(_L("Object")).x,
|
||||
imgui_wrapper->calc_text_size(_L("Part")).x
|
||||
}) + imgui_wrapper->calc_text_size("xxx"sv).x + imgui_wrapper->scaled(3.5f);
|
||||
float label_max = std::max({
|
||||
imgui_wrapper->calc_text_size(_L("Position")).x,
|
||||
imgui_wrapper->calc_text_size(_L("Relative")).x
|
||||
});
|
||||
float caption_max = std::max(label_max, coord_combo_width - 3 * space_size);
|
||||
float end_text_size = imgui_wrapper->calc_text_size(this->m_new_unit_string).x;
|
||||
|
||||
// position
|
||||
@@ -786,7 +804,7 @@ void GizmoObjectManipulation::do_render_move_window(ImGuiWrapper *imgui_wrapper,
|
||||
unsigned int current_active_id = ImGui::GetActiveID();
|
||||
|
||||
Selection & selection = m_glcanvas.get_selection();
|
||||
std::vector<std::string> modes = {_u8L("World coordinates"), _u8L("Object coordinates")};//_u8L("Part coordinates")
|
||||
std::vector<std::string> modes = {_u8L("World"), _u8L("Object")};//_u8L("Part") // ORCA use shorter terms to make UI more compact
|
||||
if (selection.is_multiple_full_object() || selection.is_wipe_tower()) {
|
||||
modes.pop_back();
|
||||
}
|
||||
@@ -796,16 +814,17 @@ void GizmoObjectManipulation::do_render_move_window(ImGuiWrapper *imgui_wrapper,
|
||||
selection_idx = 0;
|
||||
}
|
||||
|
||||
float caption_cs_size = imgui_wrapper->calc_text_size(""sv).x;
|
||||
float caption_size = caption_cs_size + 2 * space_size;
|
||||
float combox_content_size = imgui_wrapper->calc_text_size(_L("Object coordinates")).x * 1.2 + imgui_wrapper->calc_text_size("xxx"sv).x + imgui_wrapper->scaled(3);
|
||||
ImGuiWrapper::push_combo_style(m_glcanvas.get_scale());
|
||||
bool combox_changed = false;
|
||||
if (render_combo(imgui_wrapper, "", modes, selection_idx, caption_size, combox_content_size)) {
|
||||
if (render_combo(imgui_wrapper, "", modes, selection_idx, 0, coord_combo_width)) {
|
||||
combox_changed = true;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
auto tooltip_str = _L("Coordinate system used for transform actions.");
|
||||
imgui_wrapper->tooltip(tooltip_str, imgui_wrapper->calc_text_size(tooltip_str).x + 3 * space_size);
|
||||
}
|
||||
ImGuiWrapper::pop_combo_style();
|
||||
caption_max = combox_content_size - 4 * space_size;
|
||||
|
||||
// ORCA use TextColored to match axes color
|
||||
float offset_to_center = (unit_size - ImGui::CalcTextSize("O").x) / 2;
|
||||
ImGui::SameLine(caption_max + index * space_size + offset_to_center);
|
||||
@@ -819,7 +838,7 @@ void GizmoObjectManipulation::do_render_move_window(ImGuiWrapper *imgui_wrapper,
|
||||
index_unit = 1;
|
||||
ImGui::AlignTextToFramePadding();
|
||||
if (selection.is_single_full_instance() && is_instance_coordinates()) {
|
||||
imgui_wrapper->text(_L("Translate(Relative)"));
|
||||
imgui_wrapper->text(_L("Relative")); // ORCA
|
||||
}
|
||||
else {
|
||||
imgui_wrapper->text(_L("Position"));
|
||||
@@ -924,10 +943,15 @@ void GizmoObjectManipulation::do_render_rotate_window(ImGuiWrapper *imgui_wrappe
|
||||
};
|
||||
|
||||
float space_size = imgui_wrapper->get_style_scaling() * 8;
|
||||
float position_size = imgui_wrapper->calc_text_size(_L("Rotate (relative)")).x + space_size;
|
||||
float World_size = imgui_wrapper->calc_text_size(_L("World coordinates")).x + space_size;
|
||||
float caption_max = std::max(position_size, World_size) + 2 * space_size;
|
||||
float end_text_size = imgui_wrapper->calc_text_size(this->m_new_unit_string).x;
|
||||
// ORCA
|
||||
float caption_max = std::max({
|
||||
imgui_wrapper->calc_text_size(_L("Relative")).x,
|
||||
imgui_wrapper->calc_text_size(_L("Absolute")).x,
|
||||
imgui_wrapper->calc_text_size(_L("World")).x
|
||||
//imgui_wrapper->calc_text_size(_L("Object")).x,
|
||||
//imgui_wrapper->calc_text_size(_L("Part")).x
|
||||
}) + 3.f * space_size;
|
||||
float end_text_size = ImGui::CalcTextSize("°").x; // ORCA rotate gizmo not uses mm or inch
|
||||
|
||||
// position
|
||||
Vec3d original_position;
|
||||
@@ -946,7 +970,11 @@ void GizmoObjectManipulation::do_render_rotate_window(ImGuiWrapper *imgui_wrappe
|
||||
ImGui::AlignTextToFramePadding();
|
||||
unsigned int current_active_id = ImGui::GetActiveID();
|
||||
ImGui::PushItemWidth(caption_max);
|
||||
imgui_wrapper->text(_L("World coordinates"));
|
||||
imgui_wrapper->text(_L("World")); // ORCA
|
||||
if (ImGui::IsItemHovered()) {
|
||||
auto tooltip_str = _L("Coordinate system used for transform actions.");
|
||||
imgui_wrapper->tooltip(tooltip_str, imgui_wrapper->calc_text_size(tooltip_str).x + 3 * space_size);
|
||||
}
|
||||
// ORCA use TextColored to match axes color
|
||||
float offset_to_center = (unit_size - ImGui::CalcTextSize("O").x) / 2;
|
||||
ImGui::SameLine(caption_max + index * space_size + offset_to_center);
|
||||
@@ -962,7 +990,7 @@ void GizmoObjectManipulation::do_render_rotate_window(ImGuiWrapper *imgui_wrappe
|
||||
// ImGui::PushItemWidth(unit_size * 2);
|
||||
bool is_relative_input = false;
|
||||
ImGui::AlignTextToFramePadding();
|
||||
imgui_wrapper->text(_L("Rotate (relative)"));
|
||||
imgui_wrapper->text(_L("Relative")); // ORCA
|
||||
ImGui::SameLine(caption_max + index * space_size);
|
||||
ImGui::PushItemWidth(unit_size);
|
||||
if (ImGui::BBLInputDouble(label_values[1][0], &rotation[0], 0.0f, 0.0f, "%.2f")) {
|
||||
@@ -991,19 +1019,14 @@ void GizmoObjectManipulation::do_render_rotate_window(ImGuiWrapper *imgui_wrappe
|
||||
}
|
||||
}
|
||||
|
||||
if (m_show_clear_rotation) {
|
||||
ImGui::SameLine(caption_max + 3 * unit_size + 4 * space_size + end_text_size);
|
||||
if (reset_button(imgui_wrapper, caption_max, unit_size, space_size, end_text_size)) {
|
||||
reset_rotation_value(true);
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
float tooltip_size = imgui_wrapper->calc_text_size(_L("Reset current rotation to the value when open the rotation tool.")).x + 3 * space_size;
|
||||
imgui_wrapper->tooltip(_u8L("Reset current rotation to the value when open the rotation tool."), tooltip_size);
|
||||
}
|
||||
} else {
|
||||
ImGui::SameLine(caption_max + 3 * unit_size + 5 * space_size + end_text_size);
|
||||
ImGui::InvisibleButton("", ImVec2(ImGui::GetFontSize(), ImGui::GetFontSize()));
|
||||
ImGui::SameLine(caption_max + index_unit * unit_size + (++index) * space_size + end_text_size);
|
||||
if (reset_button(imgui_wrapper, m_show_clear_rotation)) // ORCA reserve icon space to prevent changes on layout
|
||||
reset_rotation_value(true);
|
||||
if (m_show_clear_rotation && ImGui::IsItemHovered()) {
|
||||
float tooltip_size = imgui_wrapper->calc_text_size(_L("Reset current rotation to the value when open the rotation tool.")).x + 3 * space_size;
|
||||
imgui_wrapper->tooltip(_u8L("Reset current rotation to the value when open the rotation tool."), tooltip_size);
|
||||
}
|
||||
|
||||
// send focus to m_glcanvas
|
||||
bool focued_on_text = false;
|
||||
for (int j = 0; j < 3; j++) {
|
||||
@@ -1018,7 +1041,7 @@ void GizmoObjectManipulation::do_render_rotate_window(ImGuiWrapper *imgui_wrappe
|
||||
index = 1;
|
||||
index_unit = 1;
|
||||
ImGui::AlignTextToFramePadding();
|
||||
imgui_wrapper->text(_L("Rotate (absolute)"));
|
||||
imgui_wrapper->text(_L("Absolute"));
|
||||
ImGui::SameLine(caption_max + index * space_size);
|
||||
ImGui::PushItemWidth(unit_size);
|
||||
bool is_absolute_input = false;
|
||||
@@ -1048,13 +1071,12 @@ void GizmoObjectManipulation::do_render_rotate_window(ImGuiWrapper *imgui_wrappe
|
||||
}
|
||||
}
|
||||
|
||||
if (m_show_reset_0_rotation) {
|
||||
ImGui::SameLine(caption_max + 3 * unit_size + 4 * space_size + end_text_size);
|
||||
if (reset_zero_button(imgui_wrapper, caption_max, unit_size, space_size, end_text_size)) { reset_rotation_value(false); }
|
||||
if (ImGui::IsItemHovered()) {
|
||||
float tooltip_size = imgui_wrapper->calc_text_size(_L("Reset current rotation to real zeros.")).x + 3 * space_size;
|
||||
imgui_wrapper->tooltip(_L("Reset current rotation to real zeros."), tooltip_size);
|
||||
}
|
||||
ImGui::SameLine(caption_max + index_unit * unit_size + (++index) * space_size + end_text_size);
|
||||
if (reset_zero_button(imgui_wrapper, m_show_reset_0_rotation)) // ORCA reserve icon space to prevent changes on layout
|
||||
reset_rotation_value(false);
|
||||
if (m_show_reset_0_rotation && ImGui::IsItemHovered()) {
|
||||
float tooltip_size = imgui_wrapper->calc_text_size(_L("Reset current rotation to real zeros.")).x + 3 * space_size;
|
||||
imgui_wrapper->tooltip(_L("Reset current rotation to real zeros."), tooltip_size);
|
||||
}
|
||||
// send focus to m_glcanvas
|
||||
bool absolute_focued_on_text = false;
|
||||
@@ -1132,8 +1154,17 @@ void GizmoObjectManipulation::do_render_scale_input_window(ImGuiWrapper* imgui_w
|
||||
};
|
||||
|
||||
float space_size = imgui_wrapper->get_style_scaling() * 8;
|
||||
float scale_size = imgui_wrapper->calc_text_size(_L("Scale")).x + space_size;
|
||||
float caption_max = imgui_wrapper->calc_text_size(_L("Object coordinates")).x + 2 * space_size;
|
||||
// ORCA
|
||||
float coord_combo_width = std::max({
|
||||
imgui_wrapper->calc_text_size(_L("World")).x,
|
||||
imgui_wrapper->calc_text_size(_L("Object")).x,
|
||||
imgui_wrapper->calc_text_size(_L("Part")).x
|
||||
}) + imgui_wrapper->calc_text_size("xxx"sv).x + imgui_wrapper->scaled(3.5f);
|
||||
float label_max = std::max({
|
||||
imgui_wrapper->calc_text_size(_CTX("Scale", "Noun")).x,
|
||||
imgui_wrapper->calc_text_size(_L("Size")).x
|
||||
});
|
||||
float caption_max = std::max(label_max, coord_combo_width - 3 * space_size);
|
||||
float end_text_size = imgui_wrapper->calc_text_size(this->m_new_unit_string).x;
|
||||
ImGui::AlignTextToFramePadding();
|
||||
unsigned int current_active_id = ImGui::GetActiveID();
|
||||
@@ -1150,7 +1181,7 @@ void GizmoObjectManipulation::do_render_scale_input_window(ImGuiWrapper* imgui_w
|
||||
int index_unit = 1;
|
||||
|
||||
Selection & selection = m_glcanvas.get_selection();
|
||||
std::vector<std::string> modes = {_u8L("World coordinates"), _u8L("Object coordinates"), _u8L("Part coordinates")};
|
||||
std::vector<std::string> modes = {_u8L("World"), _u8L("Object"), _u8L("Part")}; // ORCA use shorter terms to make UI more compact
|
||||
if (selection.is_single_full_object()) { modes.pop_back(); }
|
||||
if (selection.is_multiple_full_object()) {
|
||||
modes.pop_back();
|
||||
@@ -1162,17 +1193,17 @@ void GizmoObjectManipulation::do_render_scale_input_window(ImGuiWrapper* imgui_w
|
||||
selection_idx = 0;
|
||||
}
|
||||
|
||||
float caption_cs_size = imgui_wrapper->calc_text_size(""sv).x;
|
||||
float caption_size = caption_cs_size + 2 * space_size;
|
||||
float combox_content_size = imgui_wrapper->calc_text_size(_L("Object coordinates")).x * 1.2 + imgui_wrapper->calc_text_size("xxx"sv).x + imgui_wrapper->scaled(3);
|
||||
ImGuiWrapper::push_combo_style(m_glcanvas.get_scale());
|
||||
bool combox_changed = false;
|
||||
if (render_combo(imgui_wrapper, "", modes, selection_idx, caption_size, combox_content_size)) {
|
||||
if (render_combo(imgui_wrapper, "", modes, selection_idx, 0, coord_combo_width)) {
|
||||
combox_changed = true;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
auto tooltip_str = _L("Coordinate system used for transform actions.");
|
||||
imgui_wrapper->tooltip(tooltip_str, imgui_wrapper->calc_text_size(tooltip_str).x + 3 * space_size);
|
||||
}
|
||||
ImGuiWrapper::pop_combo_style();
|
||||
caption_max = combox_content_size - 4 * space_size;
|
||||
//ImGui::Dummy(ImVec2(caption_max, -1));
|
||||
|
||||
// ORCA use TextColored to match axes color
|
||||
float offset_to_center = (unit_size - ImGui::CalcTextSize("O").x) / 2;
|
||||
ImGui::SameLine(caption_max + space_size + offset_to_center);
|
||||
@@ -1187,7 +1218,7 @@ void GizmoObjectManipulation::do_render_scale_input_window(ImGuiWrapper* imgui_w
|
||||
|
||||
//ImGui::PushItemWidth(unit_size * 2);
|
||||
ImGui::AlignTextToFramePadding();
|
||||
imgui_wrapper->text(_L("Scale"));
|
||||
imgui_wrapper->text(_CTX("Scale", "Noun"));
|
||||
ImGui::SameLine(caption_max + space_size);
|
||||
ImGui::PushItemWidth(unit_size);
|
||||
ImGui::BBLInputDouble(label_scale_values[0][0], &scale[0], 0.0f, 0.0f, "%.2f");
|
||||
@@ -1203,14 +1234,9 @@ void GizmoObjectManipulation::do_render_scale_input_window(ImGuiWrapper* imgui_w
|
||||
m_buffered_scale = scale;
|
||||
}
|
||||
|
||||
if (m_show_clear_scale) {
|
||||
ImGui::SameLine(caption_max + 3 * unit_size + 4 * space_size + end_text_size);
|
||||
if (reset_button(imgui_wrapper, caption_max, unit_size, space_size, end_text_size))
|
||||
reset_scale_value();
|
||||
} else {
|
||||
ImGui::SameLine(caption_max + 3 * unit_size + 5 * space_size + end_text_size);
|
||||
ImGui::InvisibleButton("", ImVec2(ImGui::GetFontSize(), ImGui::GetFontSize()));
|
||||
}
|
||||
ImGui::SameLine(caption_max + 3 * unit_size + 4 * space_size + end_text_size);
|
||||
if (reset_button(imgui_wrapper, m_show_clear_scale))
|
||||
reset_scale_value();
|
||||
|
||||
//Size
|
||||
Vec3d original_size;
|
||||
|
||||
@@ -139,8 +139,8 @@ public:
|
||||
void do_render_rotate_window(ImGuiWrapper *imgui_wrapper, std::string window_name, float x, float y, float bottom_limit);
|
||||
void do_render_scale_input_window(ImGuiWrapper* imgui_wrapper, std::string window_name, float x, float y, float bottom_limit);
|
||||
float max_unit_size(int number, Vec3d &vec1, Vec3d &vec2,std::string str);
|
||||
bool reset_button(ImGuiWrapper *imgui_wrapper, float caption_max, float unit_size, float space_size, float end_text_size);
|
||||
bool reset_zero_button(ImGuiWrapper *imgui_wrapper, float caption_max, float unit_size, float space_size, float end_text_size);
|
||||
bool reset_button(ImGuiWrapper *imgui_wrapper, bool enabled);
|
||||
bool reset_zero_button(ImGuiWrapper *imgui_wrapper, bool enabled);
|
||||
bool bbl_checkbox(const wxString &label, bool &value);
|
||||
|
||||
void set_init_rotation(const Geometry::Transformation &value);
|
||||
|
||||
@@ -2781,6 +2781,11 @@ void ImGuiWrapper::init_font(bool compress)
|
||||
builder.BuildRanges(&ranges); // Build the final result (ordered ranges with all the unique characters submitted)
|
||||
|
||||
io.Fonts->Flags |= ImFontAtlasFlags_NoPowerOfTwoHeight;
|
||||
// TexDesiredWidth will be increased adaptively below if the built height
|
||||
// exceeds GL_MAX_TEXTURE_SIZE. Leave it at 0 so ImGui picks the minimum
|
||||
// width for small glyph sets and we only widen when actually necessary.
|
||||
io.Fonts->TexDesiredWidth = 0;
|
||||
|
||||
ImFontConfig cfg = ImFontConfig();
|
||||
cfg.OversampleH = cfg.OversampleV = 1;
|
||||
//FIXME replace with io.Fonts->AddFontFromMemoryTTF(buf_decompressed_data, (int)buf_decompressed_size, m_font_size, nullptr, ranges.Data);
|
||||
@@ -2852,7 +2857,23 @@ void ImGuiWrapper::init_font(bool compress)
|
||||
io.Fonts->AddCustomRectFontGlyph(default_font, icon.first, icon_sz * 4, icon_sz * 4, 3.0 * font_scale + icon_sz * 4);
|
||||
}
|
||||
|
||||
// Build texture atlas
|
||||
// Build texture atlas, widening it if the height would exceed GL_MAX_TEXTURE_SIZE.
|
||||
// Increasing the width allows the packing algorithm to grow more horizontally which reduces the height.
|
||||
io.Fonts->Build();
|
||||
GLint gl_max_tex_size = 0;
|
||||
glsafe(::glGetIntegerv(GL_MAX_TEXTURE_SIZE, &gl_max_tex_size));
|
||||
constexpr int max_retries = 6;
|
||||
for (int attempt = 0; attempt < max_retries && io.Fonts->TexHeight > gl_max_tex_size; ++attempt) {
|
||||
io.Fonts->TexDesiredWidth = (io.Fonts->TexDesiredWidth > 0 ? io.Fonts->TexDesiredWidth : io.Fonts->TexWidth) * 2;
|
||||
io.Fonts->Build();
|
||||
}
|
||||
if (io.Fonts->TexHeight > gl_max_tex_size) {
|
||||
// Shouldn't really happen
|
||||
BOOST_LOG_TRIVIAL(error) << "Font atlas height " << io.Fonts->TexHeight
|
||||
<< " still exceeds GL_MAX_TEXTURE_SIZE (" << gl_max_tex_size << ")"
|
||||
<< " after " << max_retries << " attempts; rendering may be incomplete";
|
||||
}
|
||||
|
||||
unsigned char* pixels;
|
||||
int width, height;
|
||||
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bits (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory.
|
||||
|
||||
@@ -644,7 +644,7 @@ void Slic3r::GUI::ImageGrid::renderContent1(wxDC &dc, wxPoint const &pt, int ind
|
||||
if (m_file_sys->GetFileType() == PrinterFileSystem::F_MODEL) {
|
||||
if (secondAction != _L("Play"))
|
||||
thirdAction = secondAction;
|
||||
secondAction = _L("Print");
|
||||
secondAction = _CTX("Print", "Verb");
|
||||
}
|
||||
// Draw buttons on hovered item
|
||||
wxRect rect{pt.x, pt.y + m_content_rect.GetBottom() - m_buttons_background.GetHeight(), m_content_rect.GetWidth(), m_buttons_background.GetHeight()};
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
@@ -2018,10 +2018,10 @@ wxBoxSizer* MainFrame::create_side_tools()
|
||||
});
|
||||
|
||||
// upload and print
|
||||
SideButton* send_gcode_btn = new SideButton(p, _L("Print"), "");
|
||||
SideButton* send_gcode_btn = new SideButton(p, _CTX("Print", "Verb"), "");
|
||||
send_gcode_btn->SetCornerRadius(0);
|
||||
send_gcode_btn->Bind(wxEVT_BUTTON, [this, p](wxCommandEvent&) {
|
||||
m_print_btn->SetLabel(_L("Print"));
|
||||
m_print_btn->SetLabel(_CTX("Print", "Verb"));
|
||||
m_print_select = eSendGcode;
|
||||
m_print_enable = get_enable_print_status();
|
||||
m_print_btn->Enable(m_print_enable);
|
||||
@@ -2667,9 +2667,9 @@ static void add_common_view_menu_items(wxMenu* view_menu, MainFrame* mainFrame,
|
||||
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
|
||||
append_menu_item(view_menu, wxID_ANY, _L("Rear") + "\t" + ctrl + "4", _L("Rear View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("rear"); },
|
||||
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
|
||||
append_menu_item(view_menu, wxID_ANY, _CTX(L_CONTEXT("Left", "Camera"), "Camera") + "\t" + ctrl + "5", _L("Left View"),[mainFrame](wxCommandEvent &) {mainFrame->select_view("left"); },
|
||||
append_menu_item(view_menu, wxID_ANY, _CTX("Left", "Camera View") + "\t" + ctrl + "5", _L("Left View"),[mainFrame](wxCommandEvent &) {mainFrame->select_view("left"); },
|
||||
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
|
||||
append_menu_item(view_menu, wxID_ANY, _CTX(L_CONTEXT("Right", "Camera"), "Camera") + "\t" + ctrl + "6", _L("Right View"),[mainFrame](wxCommandEvent &) { mainFrame->select_view("right"); },
|
||||
append_menu_item(view_menu, wxID_ANY, _CTX("Right", "Camera View") + "\t" + ctrl + "6", _L("Right View"),[mainFrame](wxCommandEvent &) { mainFrame->select_view("right"); },
|
||||
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
|
||||
}
|
||||
|
||||
@@ -3279,8 +3279,9 @@ void MainFrame::init_menubar_as_editor()
|
||||
},
|
||||
"", nullptr, []() { return true; }, this);
|
||||
|
||||
auto top_menu = m_topbar->GetTopMenu();
|
||||
top_menu->AppendSeparator();
|
||||
auto top_menu = m_topbar->GetTopMenu();
|
||||
top_menu->AppendSeparator();
|
||||
|
||||
append_menu_item(
|
||||
top_menu, wxID_ANY, _L("Preset Bundle") + "\t", "",
|
||||
[this](wxCommandEvent &) {
|
||||
@@ -3316,7 +3317,6 @@ void MainFrame::init_menubar_as_editor()
|
||||
},
|
||||
"", nullptr, []() { return true; }, this);
|
||||
|
||||
|
||||
//m_topbar->AddDropDownMenuItem(preference_item);
|
||||
//m_topbar->AddDropDownMenuItem(printer_item);
|
||||
//m_topbar->AddDropDownMenuItem(language_item);
|
||||
@@ -3427,18 +3427,24 @@ void MainFrame::init_menubar_as_editor()
|
||||
plater()->get_current_canvas3D()->force_set_focus();
|
||||
},
|
||||
"", nullptr, []() { return true; }, this);
|
||||
|
||||
append_menu_item(
|
||||
fileMenu, wxID_ANY, _L("Sync Presets"), _L("Pull and apply the latest presets from OrcaCloud"),
|
||||
[this](wxCommandEvent&) {
|
||||
if (!wxGetApp().is_user_login()) {
|
||||
MessageDialog info_dlg(this, _L("You must be logged in to sync presets from cloud."), _L("Sync Presets"),
|
||||
wxOK | wxICON_INFORMATION);
|
||||
MessageDialog info_dlg(this, _L("You must be logged in to sync presets from cloud."),
|
||||
_L("Sync Presets"), wxOK | wxICON_INFORMATION);
|
||||
info_dlg.ShowModal();
|
||||
return;
|
||||
}
|
||||
if (m_plater)
|
||||
m_plater->get_notification_manager()->push_notification(
|
||||
into_u8(_L("Syncing presets from cloud\u2026")));
|
||||
wxGetApp().restart_sync_user_preset();
|
||||
},
|
||||
"", nullptr, [this]() { return wxGetApp().is_user_login() && !wxGetApp().app_config->get_stealth_mode(); }, this);
|
||||
}, "", nullptr,
|
||||
[this]() {
|
||||
return wxGetApp().is_user_login() && !wxGetApp().app_config->get_stealth_mode();
|
||||
}, this);
|
||||
|
||||
fileMenu->AppendSeparator();
|
||||
append_menu_item(
|
||||
@@ -3749,7 +3755,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;
|
||||
@@ -4062,7 +4068,7 @@ void MainFrame::set_print_button_to_default(PrintSelectType select_type)
|
||||
m_print_btn->Enable(m_print_enable);
|
||||
this->Layout();
|
||||
} else if (select_type == PrintSelectType::eSendGcode) {
|
||||
m_print_btn->SetLabel(_L("Print"));
|
||||
m_print_btn->SetLabel(_CTX("Print", "Verb"));
|
||||
m_print_select = eSendGcode;
|
||||
if (m_print_enable)
|
||||
m_print_enable = get_enable_print_status() && can_send_gcode();
|
||||
|
||||
@@ -520,7 +520,7 @@ void MediaFilePanel::fetchUrl(boost::weak_ptr<PrinterFileSystem> wfs)
|
||||
fs->SetUrl(res);
|
||||
}
|
||||
});
|
||||
});
|
||||
}, wxGetApp().get_printer_cloud_provider());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -556,7 +556,7 @@ void MediaFilePanel::doAction(size_t index, int action)
|
||||
} else if (action == 1) {
|
||||
if (fs->GetFileType() == PrinterFileSystem::F_MODEL) {
|
||||
if (index != -1) {
|
||||
auto dlg = new MediaProgressDialog(_L("Print"), this, [fs] { fs->FetchModelCancel(); });
|
||||
auto dlg = new MediaProgressDialog(_CTX("Print", "Verb"), this, [fs] { fs->FetchModelCancel(); });
|
||||
dlg->Update(0, _L("Fetching model information..."));
|
||||
fs->FetchModel(index, [this, fs, dlg, index](int result, std::string const &data) {
|
||||
dlg->Destroy();
|
||||
@@ -566,7 +566,7 @@ void MediaFilePanel::doAction(size_t index, int action)
|
||||
wxString msg = data.empty() ? _L("Failed to fetch model information from printer.") :
|
||||
from_u8(data);
|
||||
CallAfter([this, msg] {
|
||||
MessageDialog(this, msg, _L("Print"), wxOK).ShowModal();
|
||||
MessageDialog(this, msg, _CTX("Print", "Verb"), wxOK).ShowModal();
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -579,7 +579,7 @@ void MediaFilePanel::doAction(size_t index, int action)
|
||||
|| plate_data_list.empty()) {
|
||||
MessageDialog(this,
|
||||
_L("Failed to parse model information."),
|
||||
_L("Print"), wxOK).ShowModal();
|
||||
_CTX("Print", "Verb"), wxOK).ShowModal();
|
||||
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();
|
||||
};
|
||||
|
||||
@@ -353,6 +353,7 @@ bool Mouse3DController::State::apply(const Mouse3DController::Params ¶ms, Ca
|
||||
rot = Vec3d(rot.x(), -rot.z(), rot.y());
|
||||
rot = Vec3d(rot.x() * pitchmult, rot.y() * yawmult, rot.z() * rollmult);
|
||||
camera.rotate_local_around_target(Vec3d(rot.x(), - rot.z(), rot.y()));
|
||||
camera.auto_type(Camera::EType::Perspective);
|
||||
} else {
|
||||
assert(input_queue_item.is_buttons());
|
||||
switch (input_queue_item.type_or_buttons) {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2299,9 +2299,17 @@ void NotificationManager::push_import_finished_notification(const std::string& p
|
||||
void NotificationManager::SharedProfilesNotification::init()
|
||||
{
|
||||
PopNotification::init();
|
||||
// Add two extra lines for the hyperlink row ("Browse shared profiles" + "Don't show again")
|
||||
// and 1 more additional line for adding spacing between them to make it easier to click
|
||||
m_lines_count = m_lines_count + 2; // ORCA
|
||||
|
||||
// PopNotification::count_lines() may append a duplicate "hypertext doesn't fit inline" placeholder endline (same value as the previous entry)
|
||||
// for the generic renderer's benefit. This class always renders its hyperlink on its own dedicated line regardless,
|
||||
// so that placeholder is meaningless here and would otherwise be drawn as a spurious blank text row.
|
||||
if (!m_hypertext.empty() && m_endlines.size() >= 2 && m_endlines.back() == m_endlines[m_endlines.size() - 2]) {
|
||||
m_endlines.pop_back();
|
||||
m_lines_count--;
|
||||
}
|
||||
|
||||
// Reserve rows for: "Browse shared profiles" hyperlink, spacing, "Don't show again"
|
||||
m_lines_count += 3;
|
||||
}
|
||||
|
||||
void NotificationManager::SharedProfilesNotification::render_text(ImGuiWrapper& imgui,
|
||||
@@ -2327,22 +2335,26 @@ void NotificationManager::SharedProfilesNotification::render_text(ImGuiWrapper&
|
||||
}
|
||||
}
|
||||
|
||||
// Render "Browse shared profiles" hyperlink on the next line
|
||||
float hyper_y = starting_y + m_endlines.size() * shift_y - m_line_height / 2.f;
|
||||
render_hypertext(imgui, x_offset, hyper_y, m_hypertext);
|
||||
|
||||
// Render "Don't show again" hyperlink after the browse link
|
||||
{
|
||||
float dont_show_y = hyper_y + ImGui::CalcTextSize((m_hypertext + " ").c_str()).y + m_line_height / 2.f;
|
||||
std::string dont_show_text = _u8L("Don't show again");
|
||||
float hyper_y = starting_y + m_endlines.size() * shift_y + m_line_height * .5f;
|
||||
float dont_show_y = hyper_y + ImGui::CalcTextSize((m_hypertext + " ").c_str()).y + m_line_height * .5f;
|
||||
std::string dont_show_text = _u8L("Don't show again") + std::to_string(m_endlines.size());
|
||||
ImVec2 part_size = ImGui::CalcTextSize(dont_show_text.c_str());
|
||||
|
||||
if (!m_multiline && m_lines_count > 2) {
|
||||
render_hypertext(imgui, x_offset + (m_endlines.size() == 1 ? 0 : ImGui::CalcTextSize((line + " ").c_str()).x) , starting_y + shift_y, _u8L("More"), true);
|
||||
}
|
||||
else {
|
||||
// Render "Browse shared profiles" hyperlink on the next line
|
||||
render_hypertext(imgui, x_offset, hyper_y, m_hypertext);
|
||||
|
||||
// Invisible button
|
||||
ImGui::SetCursorPosX(x_offset); // ORCA render on new line to prevent long translations from being cut off
|
||||
ImGui::SetCursorPosY(dont_show_y);
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
|
||||
// Render "Don't show again" hyperlink after the browse link
|
||||
if (imgui.button("##dont_show_btn", part_size.x + 6, part_size.y + 10)) {
|
||||
wxGetApp().app_config->set_bool("show_shared_profiles_notification", false);
|
||||
wxGetApp().app_config->save();
|
||||
@@ -2370,6 +2382,7 @@ void NotificationManager::SharedProfilesNotification::render_text(ImGuiWrapper&
|
||||
ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd,
|
||||
IM_COL32((int)(color.x * 255), (int)(color.y * 255), (int)(color.z * 255),
|
||||
(int)(color.w * 255.f * (m_state == EState::FadingOut ? m_current_fade_opacity : 1.f))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2382,8 +2395,10 @@ bool NotificationManager::SharedProfilesNotification::on_text_click()
|
||||
void NotificationManager::SharedProfilesNotification::render_hypertext(ImGuiWrapper& imgui,
|
||||
const float text_x, const float text_y, const std::string text, bool more)
|
||||
{
|
||||
render_hyperlink_action(imgui, text_x, text_y, text, "##browse_btn",
|
||||
[this] { if (on_text_click()) close(); });
|
||||
if (more)
|
||||
PopNotification::render_hypertext(imgui, text_x, text_y, text, true);
|
||||
else
|
||||
render_hyperlink_action(imgui, text_x, text_y, text, "##browse_btn", [this] { if (on_text_click()) close(); });
|
||||
}
|
||||
|
||||
void NotificationManager::OrcaSyncConflictNotification::init()
|
||||
|
||||
+211
-11
@@ -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();
|
||||
@@ -385,7 +401,7 @@ void PartPlate::set_spiral_vase_mode(bool spiral_mode, bool as_global)
|
||||
}
|
||||
}
|
||||
|
||||
bool PartPlate::valid_instance(int obj_id, int instance_id)
|
||||
bool PartPlate::valid_instance(int obj_id, int instance_id) const
|
||||
{
|
||||
if ((obj_id >= 0) && (obj_id < m_model->objects.size()))
|
||||
{
|
||||
@@ -1688,7 +1704,7 @@ std::vector<int> PartPlate::get_extruders_under_cli(bool conside_custom_gcode, D
|
||||
int obj_id = it->first;
|
||||
int instance_id = it->second;
|
||||
|
||||
if ((obj_id >= 0) && (obj_id < m_model->objects.size()))
|
||||
if (valid_instance(obj_id, instance_id))
|
||||
{
|
||||
ModelObject* object = m_model->objects[obj_id];
|
||||
ModelInstance* instance = object->instances[instance_id];
|
||||
@@ -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;
|
||||
@@ -2359,6 +2386,9 @@ void PartPlate::set_pos_and_size(Vec3d& origin, int width, int depth, int height
|
||||
for (std::set<std::pair<int, int>>::iterator it = obj_to_instance_set.begin(); it != obj_to_instance_set.end(); ++it) {
|
||||
int obj_id = it->first;
|
||||
int instance_id = it->second;
|
||||
if (!valid_instance(obj_id, instance_id))
|
||||
continue;
|
||||
|
||||
ModelObject* object = m_model->objects[obj_id];
|
||||
ModelInstance* instance = object->instances[instance_id];
|
||||
|
||||
@@ -2833,7 +2863,7 @@ void PartPlate::duplicate_all_instance(unsigned int dup_count, bool need_skip, s
|
||||
int obj_id = it->first;
|
||||
int instance_id = it->second;
|
||||
|
||||
if ((obj_id >= 0) && (obj_id < m_model->objects.size()))
|
||||
if (valid_instance(obj_id, instance_id))
|
||||
{
|
||||
ModelObject* object = m_model->objects[obj_id];
|
||||
ModelInstance* instance = object->instances[instance_id];
|
||||
@@ -2866,7 +2896,7 @@ void PartPlate::duplicate_all_instance(unsigned int dup_count, bool need_skip, s
|
||||
int obj_id = it->first;
|
||||
int instance_id = it->second;
|
||||
|
||||
if ((obj_id >= 0) && (obj_id < m_model->objects.size()))
|
||||
if (valid_instance(obj_id, instance_id))
|
||||
{
|
||||
ModelObject* object = m_model->objects[obj_id];
|
||||
ModelInstance* instance = object->instances[instance_id];
|
||||
@@ -2983,8 +3013,8 @@ int PartPlate::printable_instance_size()
|
||||
int obj_id = it->first;
|
||||
int instance_id = it->second;
|
||||
|
||||
if (obj_id >= m_model->objects.size())
|
||||
continue;
|
||||
if (!valid_instance(obj_id, instance_id))
|
||||
continue;
|
||||
|
||||
ModelObject * object = m_model->objects[obj_id];
|
||||
ModelInstance *instance = object->instances[instance_id];
|
||||
@@ -3006,7 +3036,7 @@ bool PartPlate::has_printable_instances()
|
||||
int obj_id = it->first;
|
||||
int instance_id = it->second;
|
||||
|
||||
if (obj_id >= m_model->objects.size())
|
||||
if (!valid_instance(obj_id, instance_id))
|
||||
continue;
|
||||
|
||||
ModelObject* object = m_model->objects[obj_id];
|
||||
@@ -3030,7 +3060,8 @@ bool PartPlate::is_all_instances_unprintable()
|
||||
int obj_id = it->first;
|
||||
int instance_id = it->second;
|
||||
|
||||
if (obj_id >= m_model->objects.size()) continue;
|
||||
if (!valid_instance(obj_id, instance_id))
|
||||
continue;
|
||||
|
||||
ModelObject * object = m_model->objects[obj_id];
|
||||
ModelInstance *instance = object->instances[instance_id];
|
||||
@@ -3502,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,
|
||||
@@ -3810,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"))
|
||||
@@ -3836,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()
|
||||
@@ -3844,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)
|
||||
@@ -3856,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);
|
||||
}
|
||||
|
||||
@@ -4221,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();
|
||||
@@ -4237,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;
|
||||
@@ -6279,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)) {
|
||||
@@ -6438,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, " ");
|
||||
@@ -6451,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, " ");
|
||||
@@ -6471,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);
|
||||
@@ -6494,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");
|
||||
@@ -6504,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");
|
||||
|
||||
@@ -167,7 +167,7 @@ private:
|
||||
wxCoord m_name_texture_height;
|
||||
|
||||
void init();
|
||||
bool valid_instance(int obj_id, int instance_id);
|
||||
bool valid_instance(int obj_id, int instance_id) const;
|
||||
void generate_print_polygon(ExPolygon &print_polygon);
|
||||
void generate_exclude_polygon(ExPolygon &exclude_polygon);
|
||||
void generate_logo_polygon(ExPolygon &logo_polygon);
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+988
-127
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();
|
||||
@@ -757,6 +769,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<-
|
||||
|
||||
@@ -783,6 +783,47 @@ wxBoxSizer *PreferencesDialog::create_camera_orbit_mult_input(wxString title, wx
|
||||
return m_sizer;
|
||||
}
|
||||
|
||||
wxBoxSizer *PreferencesDialog::create_item_decimal_input(wxString title, wxString title2, wxString tooltip, std::string param, double min, double max, int decimals, const wxString wiki_url)
|
||||
{
|
||||
auto tip = tooltip.IsEmpty() ? title : tooltip; // auto fill tooltips with title if its empty
|
||||
|
||||
wxBoxSizer *m_sizer = create_item_label(title, tip, wiki_url);
|
||||
|
||||
auto input = new ::TextInput(m_parent, wxEmptyString, title2, wxEmptyString, wxDefaultPosition, DESIGN_INPUT_SIZE, wxTE_PROCESS_ENTER);
|
||||
StateColor input_bg(std::pair<wxColour, int>(wxColour("#F0F0F1"), StateColor::Disabled), std::pair<wxColour, int>(*wxWHITE, StateColor::Enabled));
|
||||
input->SetBackgroundColor(input_bg);
|
||||
input->GetTextCtrl()->SetValue(app_config->get(param));
|
||||
wxTextValidator validator(wxFILTER_NUMERIC);
|
||||
input->SetToolTip(tooltip);
|
||||
input->GetTextCtrl()->SetValidator(validator);
|
||||
|
||||
m_sizer->Add(input, 0, wxALIGN_CENTER_VERTICAL);
|
||||
|
||||
auto apply_value = [this, param, input, min, max, decimals]() {
|
||||
auto value = input->GetTextCtrl()->GetValue();
|
||||
double conv = min;
|
||||
if (value.ToCDouble(&conv)) {
|
||||
conv = conv < min ? min : conv > max ? max : conv;
|
||||
auto strval = std::string(wxString::FromCDouble(conv, decimals).mb_str());
|
||||
input->GetTextCtrl()->SetValue(strval);
|
||||
app_config->set(param, strval);
|
||||
}
|
||||
};
|
||||
|
||||
input->GetTextCtrl()->Bind(wxEVT_TEXT_ENTER, [apply_value](wxCommandEvent &e) {
|
||||
apply_value();
|
||||
wxGetApp().app_config->save();
|
||||
e.Skip();
|
||||
});
|
||||
|
||||
input->GetTextCtrl()->Bind(wxEVT_KILL_FOCUS, [apply_value](wxFocusEvent &e) {
|
||||
apply_value();
|
||||
e.Skip();
|
||||
});
|
||||
|
||||
return m_sizer;
|
||||
}
|
||||
|
||||
wxBoxSizer *PreferencesDialog::create_item_backup(wxString title, wxString tooltip)
|
||||
{
|
||||
auto tip = tooltip.IsEmpty() ? title : tooltip; // auto fill tooltips with title if its empty
|
||||
@@ -1605,6 +1646,15 @@ void PreferencesDialog::create_items()
|
||||
);
|
||||
g_sizer->Add(item_step_dialog);
|
||||
|
||||
auto item_step_linear = create_item_decimal_input(_L("STEP importing: linear deflection"), "mm", _L("Linear deflection used when meshing imported STEP files.\nSmaller values produce higher-quality meshes but increase processing time.\nUsed as the default in the import dialog, or directly when the import dialog is disabled.\nDefault: 0.003 mm."), "linear_deflection", 0.001, 0.1, 3);
|
||||
g_sizer->Add(item_step_linear);
|
||||
|
||||
auto item_step_angle = create_item_decimal_input(_L("STEP importing: angle deflection"), "", _L("Angle deflection used when meshing imported STEP files.\nSmaller values produce higher-quality meshes but increase processing time.\nUsed as the default in the import dialog, or directly when the import dialog is disabled.\nDefault: 0.5."), "angle_deflection", 0.01, 1.0, 2);
|
||||
g_sizer->Add(item_step_angle);
|
||||
|
||||
auto item_step_split = create_item_checkbox(_L("STEP importing: Split into multiple objects"), _L("If enabled, compound and compsolid shapes in imported STEP files are split into multiple objects.\nUsed as the default in the import dialog, or directly when the import dialog is disabled.\nDefault: disabled."), "is_split_compound");
|
||||
g_sizer->Add(item_step_split);
|
||||
|
||||
auto item_draco_bits = create_item_spinctrl(_L("Quality level for Draco export"), "",
|
||||
_L("bits"),
|
||||
_L("Controls the quantization bit depth used when compressing the mesh to Draco format.\n"
|
||||
@@ -1614,6 +1664,13 @@ void PreferencesDialog::create_items()
|
||||
);
|
||||
g_sizer->Add(item_draco_bits);
|
||||
|
||||
auto item_full_source_paths = create_item_checkbox(_L("Store full source file paths in projects"),
|
||||
_L("If enabled, saved projects store the absolute path to imported source files (STEP/STL/...), so "
|
||||
"\"Reload from disk\" still works when the source file is kept in a different folder than the project. "
|
||||
"If disabled, only the filename is stored, which keeps projects portable and avoids embedding absolute paths."),
|
||||
"export_sources_full_pathnames");
|
||||
g_sizer->Add(item_full_source_paths);
|
||||
|
||||
//// GENERAL > Preset
|
||||
g_sizer->Add(create_item_title(_L("Preset")), 1, wxEXPAND);
|
||||
|
||||
@@ -1740,6 +1797,17 @@ void PreferencesDialog::create_items()
|
||||
g_sizer = f_sizers.back();
|
||||
g_sizer->AddGrowableCol(0, 1);
|
||||
|
||||
//// GRAPHICS > General
|
||||
g_sizer->Add(create_item_title(_L("General")), 1, wxEXPAND);
|
||||
|
||||
auto smooth_normals = create_item_checkbox(
|
||||
_L("Smooth normals"),
|
||||
_L("Applies smooth normals to the model.\n\nRequires manual scene reload to take effect "
|
||||
"(right-click on 3D view → \"Reload All\")."),
|
||||
SETTING_OPENGL_PHONG_SMOOTH_NORMALS
|
||||
);
|
||||
g_sizer->Add(smooth_normals);
|
||||
|
||||
//// GRAPHICS > Realistic view
|
||||
g_sizer->Add(create_item_title(_L("Realistic View")), 1, wxEXPAND);
|
||||
|
||||
@@ -1759,20 +1827,11 @@ void PreferencesDialog::create_items()
|
||||
|
||||
auto item_realistic_shadows = create_item_checkbox(
|
||||
_L("Shadows"),
|
||||
_L("Renders cast shadows on the plate in realistic view."),
|
||||
_L("Renders cast shadows on the plate, other objects, and each object onto itself in realistic view."),
|
||||
SETTING_OPENGL_PHONG_BASIC_PLATE_SHADOWS
|
||||
);
|
||||
g_sizer->Add(item_realistic_shadows);
|
||||
|
||||
|
||||
auto item_realistic_smooth_normals = create_item_checkbox(
|
||||
_L("Smooth normals"),
|
||||
_L("Applies smooth normals to the realistic view.\n\nRequires manual scene reload to take effect "
|
||||
"(right-click on 3D view → \"Reload All\")."),
|
||||
SETTING_OPENGL_PHONG_SMOOTH_NORMALS
|
||||
);
|
||||
g_sizer->Add(item_realistic_smooth_normals);
|
||||
|
||||
//// GRAPHICS > Anti-aliasing
|
||||
g_sizer->Add(create_item_title(_L("Anti-aliasing")), 1, wxEXPAND);
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user