Merge branch 'main' into feature/filament_id

Brings in 560 commits (merge base 2026-07-04 .. origin/main af9fd10d7a).
19 conflicts resolved; the other 138 touched files auto-merged.

Conflict resolutions
- Snapmaker/Polymaker (7): main normalised JSON key order in #15039 while this
  branch inserted filament_id/filament_vendor. Took main's key order and kept
  this branch's values, inserting a single filament_id key rather than letting
  git's line merge leave duplicate "from"/"instantiation" keys.
- re3D rPETG @0.8/@1.75 nozzle (2): main renamed these from "re3D Greengate
  rPETG @*" and re-vendored GreenGate3D -> re3D (#15169). Git's rename-aware
  merge produced a file with two filament_vendor keys; took main's version
  wholesale instead. The id is reconciled by the tooling, not by hand.
- re3D rPP (1): kept main's inherits change (fdm_filament_pet -> fdm_filament_pp,
  local filament_type override dropped) and its widened compatible_printers.
  The flattened type is still PP, so the product triple and id OFfHGM1D are
  unchanged.
- re3D Greengate rPETG.json, Afinia {ABS+,ABS,PLA,TPU,Value ABS,Value PLA}.json
  (7 modify/delete): accepted main's deletions. The Afinia ids are not orphaned
  - the surviving @HS presets resolve the same triple and already carry the
  same ids, so nothing is retired.
- .github/workflows/check_profiles.yml: kept both main's new "validate slice"
  step and this branch's tree-wide filament-subtype check.
- tests/libslic3r/CMakeLists.txt: kept both test_filament_id_succession.cpp and
  main's test_preset_diff.cpp.

Verified
- No conflict markers; all 12795 profile JSONs parse with no duplicate keys.
- All branch artifacts (tooling, snapshot, ledger, doc, tests) intact and
  unmodified by the merge.
- The succession-ledger C++ call sites survived main's refactors; audited
  Preset.{cpp,hpp}, PresetBundle.cpp, DeviceManager.cpp, PresetComboBoxes.cpp,
  CaliHistoryDialog.cpp, MoonrakerPrinterAgent.cpp against base/ours/theirs.
- scripts/tests: 115/116 pass.

Known follow-up: assign_filament_ids.py --check reports 243 errors, all from
filament data main added in the last month (BBL/addnorth, Qidi X5/Plus 5,
Snapmaker U1, re3D). The single failing unit test is the live-tree conformance
test asserting that count is zero. Reconciliation lands separately.
This commit is contained in:
SoftFever
2026-08-08 02:05:37 +08:00
2224 changed files with 2621810 additions and 1287330 deletions
+108 -18
View File
@@ -70,6 +70,9 @@ float FullTransparentModdifiedToFixAlpha = 0.3f;
// value like 0.18f could not because in C++ (int)(0.18f * 255) == 45 however in OpenGL it renders this as 46
// which breaks the `SelectMachineDialog::record_edge_pixels_data()` function!
float FULL_BLACK_THRESHOLD = 0.2f;
// Keep depth_tex away from texture unit 0 to avoid sampler-type aliasing with
// shadow/environment samplers when realistic view is disabled.
static constexpr int OUTLINE_DEPTH_TEX_UNIT = 5;
Slic3r::ColorRGBA adjust_color_for_rendering(const Slic3r::ColorRGBA &colors)
{
@@ -495,7 +498,60 @@ void GLVolume::render_with_outline(const GUI::Size& cnv_size)
simple_render(shader, model_objects, colors);
return;
}
// 0th. render pass, render the model using stencil buffer
glsafe(::glEnable(GL_STENCIL_TEST));
glsafe(::glStencilMask(0xFF));
glsafe(::glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE));
glsafe(::glClearStencil(0));
glsafe(::glClear(GL_STENCIL_BUFFER_BIT));
glsafe(::glStencilFunc(GL_ALWAYS, 0xFF, 0xFF));
if (tverts_range == std::make_pair<size_t, size_t>(0, -1))
model.render(shader);
else
model.render(this->tverts_range, shader);
glsafe(::glStencilFunc(GL_NOTEQUAL, 0xFF, 0xFF));
glsafe(::glStencilMask(0x00));
shader->set_uniform("is_outline", true);
shader->set_uniform("screen_size", Vec2f{cnv_size.get_width(), cnv_size.get_height()});
if (tverts_range == std::make_pair<size_t, size_t>(0, -1))
model.render(shader);
else
model.render(this->tverts_range, shader);
shader->set_uniform("is_outline", false);
glsafe(::glStencilMask(0xFF));
glsafe(::glDisable(GL_STENCIL_TEST));
// render the outline using depth buffer and discard the pixels that are not on the outline
// The silhouette is resolved per sample in the shader (see DetectSilho in gouraud.fs/phong.fs).
// That needs the GL 3.2 entry points and a shader that declares depth_tex as sampler2DMS, which
// only the 140 ones do and only under GL_ARB_texture_multisample - so ask the compiled program
// rather than the GL version, or a sampler2D ends up bound to a multisample texture.
// Only the Arb branch below allocates a multisample texture, so keep the target consistent with it.
const bool use_msaa_outline = framebuffers_type == GUI::OpenGLManager::EFramebufferType::Arb &&
GUI::wxGetApp().is_gl_version_greater_or_equal_to(3, 2) &&
shader->get_uniform_location("msaa_samples") >= 0;
const GLenum depth_tex_target = use_msaa_outline ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D;
// Keep the depth texture off image unit 0. The object shaders leave shadow_map (and
// environment_tex) at the default sampler value 0 whenever the shadow pass is skipped - which is
// the case with realistic view off - and GL forbids two sampler types referring to the same image
// unit. A sampler2DMS on unit 0 then makes every draw fail with INVALID_OPERATION on drivers that
// enforce it (Mesa), i.e. the model disappears entirely. Unit 5 is unused (shadow_map takes 4).
const int depth_tex_unit = OUTLINE_DEPTH_TEX_UNIT;
int aa_samples = 1;
if (use_msaa_outline) {
if (const AppConfig* app_config = GUI::wxGetApp().app_config; app_config != nullptr) {
const std::string value = app_config->get(SETTING_OPENGL_AA_SAMPLES);
if (value == "2" || value == "4" || value == "8" || value == "16")
aa_samples = ::atoi(value.c_str());
}
// Never request more samples than the driver supports for depth textures (a 1-sample texture
// is used when MSAA is disabled, keeping a single code path for the sampler2DMS shader).
GLint max_samples = 1;
glsafe(::glGetIntegerv(GL_MAX_DEPTH_TEXTURE_SAMPLES, &max_samples));
if (aa_samples > max_samples)
aa_samples = max_samples < 1 ? 1 : max_samples;
if (aa_samples < 1)
aa_samples = 1;
}
// 1st. render pass, render the model into a separate render target that has only depth buffer
GLuint depth_fbo = 0;
GLuint depth_tex = 0;
@@ -503,21 +559,26 @@ void GLVolume::render_with_outline(const GUI::Size& cnv_size)
glsafe(::glGenFramebuffers(1, &depth_fbo));
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, depth_fbo));
glActiveTexture(GL_TEXTURE0);
glsafe(::glActiveTexture(GL_TEXTURE0 + depth_tex_unit));
glsafe(::glGenTextures(1, &depth_tex));
glsafe(::glBindTexture(GL_TEXTURE_2D, depth_tex));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, cnv_size.get_width(), cnv_size.get_height(), 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr));
glsafe(::glBindTexture(depth_tex_target, depth_tex));
if (use_msaa_outline) {
// Multisample textures do not take filter/wrap parameters.
glsafe(::glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, aa_samples, GL_DEPTH_COMPONENT32F, cnv_size.get_width(), cnv_size.get_height(), GL_TRUE));
} else {
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, cnv_size.get_width(), cnv_size.get_height(), 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr));
}
glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depth_tex, 0));
glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depth_tex_target, depth_tex, 0));
} else {
glsafe(::glGenFramebuffersEXT(1, &depth_fbo));
glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, depth_fbo));
glActiveTexture(GL_TEXTURE0);
glsafe(::glActiveTexture(GL_TEXTURE0 + depth_tex_unit));
glsafe(::glGenTextures(1, &depth_tex));
glsafe(::glBindTexture(GL_TEXTURE_2D, depth_tex));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
@@ -526,14 +587,17 @@ void GLVolume::render_with_outline(const GUI::Size& cnv_size)
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, cnv_size.get_width(), cnv_size.get_height(), 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr));
glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, depth_tex, 0));
glsafe(::glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, depth_tex, 0));
}
// Unbind before drawing: the texture is this framebuffer's depth attachment, so leaving it bound
// to a sampled unit would be a feedback loop.
glsafe(::glBindTexture(depth_tex_target, 0));
glsafe(::glActiveTexture(GL_TEXTURE0));
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
if (tverts_range == std::make_pair<size_t, size_t>(0, -1))
model.render(shader);
else
model.render(this->tverts_range, shader);
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
// 2nd. render pass, just a normal render with the depth buffer passed as a texture
if (framebuffers_type == GUI::OpenGLManager::EFramebufferType::Arb) {
@@ -543,13 +607,17 @@ void GLVolume::render_with_outline(const GUI::Size& cnv_size)
}
shader->set_uniform("is_outline", true);
shader->set_uniform("screen_size", Vec2f{cnv_size.get_width(), cnv_size.get_height()});
glActiveTexture(GL_TEXTURE0);
glsafe(::glBindTexture(GL_TEXTURE_2D, depth_tex));
shader->set_uniform("depth_tex", 0);
shader->set_uniform("msaa_samples", aa_samples);
glsafe(::glActiveTexture(GL_TEXTURE0 + depth_tex_unit));
glsafe(::glBindTexture(depth_tex_target, depth_tex));
glsafe(::glActiveTexture(GL_TEXTURE0));
shader->set_uniform("depth_tex", depth_tex_unit);
simple_render(shader, model_objects, colors);
// Some clean up to do
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
glsafe(::glActiveTexture(GL_TEXTURE0 + depth_tex_unit));
glsafe(::glBindTexture(depth_tex_target, 0));
glsafe(::glActiveTexture(GL_TEXTURE0));
shader->set_uniform("is_outline", false);
if (framebuffers_type == GUI::OpenGLManager::EFramebufferType::Arb) {
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, 0));
@@ -1024,7 +1092,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())
@@ -1052,6 +1121,10 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type,
const float support_normal_z = get_selection_support_normal_z();
// Prime depth_tex on every frame so non-outline draws do not keep the
// default sampler unit 0, which can conflict with other sampler types.
shader->set_uniform("depth_tex", OUTLINE_DEPTH_TEX_UNIT);
for (GLVolumeWithIdAndZ& volume : to_render) {
#if ENABLE_MODIFIERS_ALWAYS_TRANSPARENT
if (type == ERenderType::Transparent) {
@@ -1108,7 +1181,24 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type,
//use -1 ad a invalid type
shader->set_uniform("print_volume.type", -1);
}
// Per-extruder printable-height shading. The flag is set to
// 2.0 only for multi-extruder printers (two per-extruder heights); otherwise it is forced to 0.0
// on every render so no stale flag survives a multi->single-extruder plate switch, keeping the
// shared gouraud shader pixel-identical for single-extruder printers. When active the height
// branch reads print_volume.xy_data (the bed rect), so set it explicitly here.
std::array<float, 3> extruder_printable_heights = {0.0f, 0.0f, 0.0f};
if (printable_heights != nullptr && printable_heights->size() > 1) {
extruder_printable_heights[0] = 2.0f;
extruder_printable_heights[1] = static_cast<float>((*printable_heights)[0]);
extruder_printable_heights[2] = static_cast<float>((*printable_heights)[1]);
shader->set_uniform("extruder_printable_heights", extruder_printable_heights);
shader->set_uniform("print_volume.xy_data", m_print_volume.data);
}
else {
shader->set_uniform("extruder_printable_heights", extruder_printable_heights);
}
shader->set_uniform("volume_world_matrix", volume.first->world_matrix());
shader->set_uniform("slope.actived", m_slope.isGlobalActive && !volume.first->is_modifier && !volume.first->is_wipe_tower);
shader->set_uniform("slope.volume_world_normal_matrix", static_cast<Matrix3f>(volume.first->world_matrix().matrix().block(0, 0, 3, 3).inverse().transpose().cast<float>()));
+4 -1
View File
@@ -497,7 +497,10 @@ public:
const Transform3d& projection_matrix,
const GUI::Size& cnv_size,
std::function<bool(const GLVolume &)> filter_func = std::function<bool(const GLVolume &)>(),
bool partly_inside_enable =true
bool partly_inside_enable =true,
// Per-extruder printable heights (extruder_printable_height); null / size<=1
// leaves the shader's extruder_printable_heights flag at 0.0 (single-extruder = inert).
std::vector<double> * printable_heights = nullptr
) const;
// Clear the geometry
File diff suppressed because it is too large Load Diff
+253
View File
@@ -0,0 +1,253 @@
#pragma once
#include "GUI_ObjectLayers.hpp"
#include "slic3r/GUI/Widgets/AMSItem.hpp"
#include "slic3r/GUI/Widgets/Label.hpp"
#include "slic3r/GUI/Widgets/PopupWindow.hpp"
#include "slic3r/GUI/wxExtensions.hpp"
#include "slic3r/GUI/DeviceCore/DevFilaSystem.h"
#include "slic3r/GUI/I18N.hpp" // Orca: explicit _L() catalog include
#include <chrono>
#include <optional>
//Previous defintions
class wxGrid;
namespace Slic3r {
namespace GUI {
enum class DryCtrState {
IDLE,
DRY_WHEN_PRINT,
UNKNOWN
};
enum class DryCtrDev {
N3S,
N3F,
UNKNOWN
};
struct DryingPreset {
DryCtrState state;
DryCtrDev dev;
int dry_temp;
int dry_time;
};
class FilamentItemPanel : public wxPanel
{
public:
FilamentItemPanel(wxWindow* parent, const wxString& text, const std::string& icon_name = "",
wxWindowID id = wxID_ANY);
void SetText(const wxString& text);
void SetIcon(const std::string& icon_name);
void msw_rescale();
private:
void OnPaint(wxPaintEvent& event);
void OnSize(wxSizeEvent& event);
Label* m_text_label;
wxStaticBitmap* m_icon_bitmap;
int m_target_size;
std::string m_icon_name;
ScalableBitmap m_icon;
};
class AMSFilamentPanel : public wxPanel
{
wxBoxSizer* m_filament_sizer;
Label* m_ams_name_label;
int m_border_radius;
wxPanel* m_filament_container{nullptr};
std::vector<FilamentItemPanel*> m_filament_items;
public:
AMSFilamentPanel(wxWindow* parent, const wxString& ams_name, wxWindowID id = wxID_ANY);
void AddFilamentItem(const wxString& text, const std::string& icon_name);
void AddFilamentItem(FilamentItemPanel* panel);
void SetAmsName(const wxString& ams_name);
void Clear();
void msw_rescale();
private:
void OnPaint(wxPaintEvent& event);
};
class AMSDryCtrWin : public DPIDialog
{
public:
AMSDryCtrWin(wxWindow *parent);
~AMSDryCtrWin();
void msw_rescale();
void update(std::shared_ptr<DevFilaSystem> fila_system, MachineObject* obj);
void set_ams_id(const std::string& ams_id);
protected:
void on_dpi_changed(const wxRect &suggested_rect) override;
private:
wxSimplebook* m_main_simplebook{nullptr};
wxPanel* m_original_page{nullptr};
wxWindow* m_amswin{nullptr};
wxBoxSizer* m_sizer_ams_items{nullptr};
wxScrolledWindow* m_panel_prv_left {nullptr};
wxScrolledWindow* m_panel_prv_right{nullptr};
wxBoxSizer* m_sizer_prv_left{nullptr};
wxBoxSizer* m_sizer_prv_right{nullptr};
// left panel related members
ScalableBitmap m_humidity_image;
wxStaticBitmap* m_humidity_img{nullptr};
Label* m_image_description{nullptr};
wxStaticBitmap* m_image_description_icon{nullptr};
ScalableBitmap m_description_icon_bitmap;
Label* m_humidity_data_label = nullptr;
Label* m_temperature_data_label = nullptr;
Label* m_time_data_label = nullptr;
wxBoxSizer* m_time_descrition_container = nullptr;
// right panel related members
wxBoxSizer* m_normal_state_sizer{nullptr};
wxBoxSizer* m_cannot_dry_sizer{nullptr};
wxBoxSizer* m_dry_error_sizer{nullptr};
Label* m_cannot_dry_description_label = nullptr;
// right panel normal state
ComboBox* m_trays_combo;
std::vector<FilamentBaseInfo> m_tray_ids;
wxTextCtrl* m_temperature_input;
wxTextCtrl* m_time_input;
Label* m_normal_description;
Button* m_start_button{nullptr};
Button* m_next_button{nullptr};
Button* m_stop_button{nullptr};
Button* m_back_button{nullptr};
Button* m_unload_button{nullptr};
// guide page description
Label* m_guide_title_label{nullptr};
Label* m_guide_description_label{nullptr};
wxCheckBox* m_rotate_spool_toggle{nullptr};
wxPanel* m_progress_page;
ProgressBar* m_progress_gauge;
wxTimer* m_progress_timer;
std::optional<std::chrono::steady_clock::time_point> m_stop_button_restore_deadline;
std::optional<std::chrono::steady_clock::time_point> m_unload_button_restore_deadline;
Label* m_progress_title;
int m_progress_value;
int m_progress_message_index;
std::vector<wxString> m_progress_text = {
_L("Starting: Checking adapter connection"),
_L("Starting: Checking filament status"),
_L("Starting: Checking drying presets"),
_L("Starting: Checking filament location"),
_L("Starting: Checking air intake"),
_L("Starting: Checking air vent")
};
// Guide page
wxPanel* m_guide_page{nullptr};
AMSFilamentPanel* m_ams_filament_panel{nullptr};
std::map<std::string, FilamentItemPanel*> m_filament_items;
wxStaticBitmap* m_image_placeholder{nullptr};
ScalableBitmap m_guide_image;
bool m_is_ams_changed = false;
std::weak_ptr<DevFilaSystem> m_fila_system;
struct {
std::string m_ams_id;
DevAmsType m_model = DevAmsType::EXT_SPOOL;
DevAms::DryStatus m_dry_status = DevAms::DryStatus::Off;
DevAms::DrySubStatus m_dry_sub_status = DevAms::DrySubStatus::Off;
int m_humidity_percent;
int m_temperature;
int m_left_dry_time;
float m_recommand_dry_temp;
} m_ams_info;
struct {
std::unordered_map<std::string, std::string> m_filament_names;
std::unordered_map<std::string, std::string> m_filament_type;
std::unordered_map<std::string, int> m_dry_temp;
std::unordered_map<std::string, int> m_dry_time;
} m_dry_setting;
struct {
bool m_is_printing = false;
} m_printer_status;
private:
void create();
wxBoxSizer* create_guide_page_sizer(wxPanel* parent);
wxBoxSizer* create_main_content_section(wxPanel* parent);
wxBoxSizer* create_guide_info_filament(wxPanel* parent);
wxBoxSizer* create_guide_info_section(wxPanel* parent);
wxBoxSizer* create_guide_right_section(wxPanel* parent);
wxBoxSizer* create_main_page_sizer(wxPanel* parent);
wxBoxSizer* create_left_panel(wxPanel* parent);
wxBoxSizer* create_humidity_status_section(wxPanel* parent);
wxBoxSizer* create_description_item(wxPanel* parent, const wxString& title, Label*& dataLabel);
wxBoxSizer* create_status_descriptions_section(wxPanel* parent);
wxBoxSizer* create_right_panel(wxPanel* parent);
wxBoxSizer* create_normal_state_panel(wxPanel* parent);
wxBoxSizer* create_cannot_dry_panel(wxPanel* parent);
wxBoxSizer* create_drying_error_panel(wxPanel* parent);
Button* create_button(wxPanel* parent, const wxString& title,
const wxColour& background_color, const wxColour& border_color, const wxColour& text_color);
wxBoxSizer* create_progress_page_sizer(wxPanel* parent);
void OnProgressTimer(wxTimerEvent& event);
void OnClose(wxCloseEvent& event);
void OnShow(wxShowEvent& event);
void OnFilamentSelectionChanged(wxCommandEvent& event);
wxScrolledWindow* create_preview_scrolled_window(wxWindow* parent);
bool check_values_changed(DevAms* dev_ams);
int update_image(DevAmsType type, DevAms::DryStatus status, DevAms::DrySubStatus sub_status, int humidity_percent);
void update_img_description(DevAms::DryStatus status, DevAms::DrySubStatus sub_status);
void update_normal_description(DevAms* dev_ams);
int update_state(DevAms* dev_ams);
int update_dryness_status(DevAms* dev_ams);
int update_ams_change(DevAms* dev_ams);
int update_filament_list(DevAms* dev_ams, MachineObject* obj);
void update_filament_guide_info(DevAms* dev_ams);
void update_normal_state(DevAms* dev_ams);
void update_printer_state(MachineObject* obj);
std::shared_ptr<DevFilaSystem> get_fila_system() const;
void start_sending_drying_command();
void restore_stop_button_if_deadline_passed();
void restore_unload_button_if_deadline_passed();
void update_button_size(Button* button);
bool is_dry_status_changed(DevAms* dev_ams);
bool is_dry_ctr_idle(DevAms* dev_ams);
bool is_ams_changed(DevAms* dev_ams);
bool is_dry_ctr_idle();
bool is_tray_changed(DevAms* dev_ams);
bool is_dry_ctr_err(DevAms* dev_ams);
};
} // GUI
} // Slic3r
+124 -46
View File
@@ -16,6 +16,9 @@
#include "DeviceCore/DevExtruderSystem.h"
#include "DeviceCore/DevFilaBlackList.h"
#include "DeviceCore/DevFilaSystem.h"
#include "DeviceCore/DevFilaSwitch.h"
#include "DeviceCore/DevNozzleSystem.h" // DevNozzle / GetNozzleByPosId / GetNozzleRack
#include "DeviceCore/DevNozzleRack.h" // DevNozzleRack::IsSupported (rack gating)
#define FILAMENT_MAX_TEMP 300
#define FILAMENT_MIN_TEMP 120
@@ -293,12 +296,9 @@ void AMSMaterialsSetting::create_panel_kn(wxWindow* parent)
m_ratio_text->SetForegroundColour(wxColour(50, 58, 61));
m_ratio_text->SetFont(Label::Head_14);
std::string language = wxGetApp().app_config->get("language");
wxString region = "en";
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);
// Orca: link to the Orca Slicer pressure-advance wiki (region-agnostic).
wxString link_url = "https://www.orcaslicer.com/wiki/pressure_advance_calib";
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);
@@ -446,9 +446,19 @@ void AMSMaterialsSetting::update_filament_editing(bool is_printing)
m_tip_readonly->Wrap(FromDIP(380));
m_tip_readonly->Show(is_printing);
}
if (m_view_only) { // Orca: view-only (2D laser/cut) — lock every edit control and hide apply/reset
m_comboBox_filament->Enable(false);
m_comboBox_cali_result->Enable(false);
m_input_k_val->Enable(false);
m_input_n_val->Enable(false);
m_button_confirm->Hide();
m_button_reset->Hide();
}
}
void AMSMaterialsSetting::on_select_reset(wxCommandEvent& event) {
if (m_view_only) return; // Orca: view-only never commits
MessageDialog msg_dlg(nullptr, _L("Are you sure you want to clear the filament information?"), wxEmptyString, wxICON_WARNING | wxOK | wxCANCEL);
auto result = msg_dlg.ShowModal();
if (result != wxID_OK)
@@ -531,56 +541,123 @@ 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;
if (m_view_only) return; // Orca: view-only never commits
//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();
@@ -762,6 +839,7 @@ void AMSMaterialsSetting::on_picker_color(wxCommandEvent& event)
void AMSMaterialsSetting::on_clr_picker(wxMouseEvent &event)
{
if (m_view_only) return; // Orca: view-only disables color editing
if(!m_is_third)
return;
+4
View File
@@ -124,6 +124,9 @@ public:
std::string ams_setting_id;
bool m_is_third;
// Orca: view-only mode (laser/cut). When set, the dialog is inspectable but every
// editing control is disabled and no command is sent.
bool m_view_only = false;
wxString m_brand_filament;
wxString m_brand_sn;
wxString m_brand_tmp;
@@ -183,6 +186,7 @@ protected:
int m_pa_cali_select_id = 0;
// Orca: native wxComboBox on macOS (the custom ComboBox mis-renders the filament dropdown there)
#ifdef __APPLE__
wxComboBox *m_comboBox_filament;
#else
+3 -2
View File
@@ -272,7 +272,7 @@ void AMSSetting::create()
m_sizer_main->Add(0, 0, 0, wxTOP, FromDIP(10));
m_sizer_main->Add(m_static_ams_settings, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(24));
m_sizer_main->Add(0, 0, 0, wxTOP, FromDIP(10));
m_sizer_main->Add(m_panel_body, 1, wxBottom | wxLEFT | wxRIGHT | wxEXPAND, FromDIP(24));
m_sizer_main->Add(m_panel_body, 1, wxBOTTOM | wxLEFT | wxRIGHT | wxEXPAND, FromDIP(24)); // Orca: wxBOTTOM (REF uses the wxEdge enum by mistake)
this->SetSizer(m_sizer_main);
this->Layout();
@@ -644,7 +644,7 @@ void AMSSettingTypePanel::Update(const MachineObject* obj)
}
if (ptr->IsSwitching()) {
int display_percent = obj->get_upgrade_percent();
int display_percent = obj->get_upgrade_percent(); // Orca: read upgrade progress via the kept MachineObject accessor
if (display_percent == 100 || display_percent == 0) {
display_percent = 1;// special case, sometimes it's switching but percent is 0 or 100
}
@@ -735,6 +735,7 @@ void AMSSettingTypePanel::OnAmsTypeChanged(wxCommandEvent& event)
event.Skip();
}
// Orca: AMSSettingArrangeAMSOrder impl kept compiled-out (feature intentionally not shipped).
#if 0 /*used option*/
AMSSettingArrangeAMSOrder::AMSSettingArrangeAMSOrder(wxWindow* parent)
: wxPanel(parent)
+1
View File
@@ -129,6 +129,7 @@ private:
AnimaIcon* m_switching_icon;
};
// Orca: the firmware AMS-reorder ("Arrange AMS Order") panel is intentionally not shipped.
#if 0
class AMSSettingArrangeAMSOrder : public wxPanel
{
+3 -1
View File
@@ -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>")
+390
View File
@@ -0,0 +1,390 @@
#include "ActionRegistry.hpp"
#include "GUI.hpp"
#include "GUI_App.hpp"
#include "I18N.hpp"
#include "slic3r/plugin/PluginManager.hpp"
#include <libslic3r/AppConfig.hpp>
#include <slic3r/plugin/PythonPluginInterface.hpp>
#include <wx/thread.h>
#include <algorithm>
#include <cmath>
#include <ctime>
#include <unordered_map>
namespace Slic3r { namespace GUI {
namespace {
constexpr const char* kConfigSection = "speed_dial";
nlohmann::json parse_config_json(const std::string& value, nlohmann::json fallback)
{
nlohmann::json parsed = nlohmann::json::parse(value, nullptr, false);
return parsed.is_discarded() ? std::move(fallback) : parsed;
}
nlohmann::json read_section(const char* key, nlohmann::json fallback)
{
return parse_config_json(wxGetApp().app_config->get(kConfigSection, key), std::move(fallback));
}
void write_section(const char* key, const nlohmann::json& j)
{
wxGetApp().app_config->set(kConfigSection, key, j.dump());
}
std::vector<std::string> read_string_array(const char* key)
{
auto j = read_section(key, nlohmann::json::array());
std::vector<std::string> v;
for (auto& e : j)
if (e.is_string())
v.push_back(e.get<std::string>());
return v;
}
// frecency = frequency + recency; score halves every 30 idle days.
double frecency_score(int count, long long last, long long now)
{
if (count <= 0)
return 0.0;
constexpr double HALF_LIFE_DAYS = 30.0;
double age = std::max(0.0, double(now - last) / 86400.0);
return count * std::pow(2.0, -age / HALF_LIFE_DAYS);
}
// ---- script-plugin action source (the one and only source) ------------------
std::string find_loaded_source_name(PluginManager& manager, const std::string& plugin_key)
{
PluginDescriptor descriptor;
if (manager.try_get_plugin_descriptor(plugin_key, descriptor) && !descriptor.name.empty())
return descriptor.name;
return plugin_key;
}
// A runnable script capability exposed as a speed-dial action. source_key = plugin_key
// (identity), so a plugin display-name change does not re-key the action.
struct PluginScriptAction : AppAction
{
static constexpr const char* kIdPrefix = "plugin_script_action";
std::string plugin_key;
std::string capability;
// The id an action for (plugin_key, capability) would have - lets refresh_capability
// remove a gone capability without materialising the action.
static std::string id_for(const std::string& plugin_key, const std::string& capability)
{
return AppAction::compose_id(kIdPrefix, capability.empty() ? plugin_key : capability, plugin_key);
}
PluginScriptAction(std::string plugin_key_in, std::string capability_in, std::string source_name)
: AppAction(kIdPrefix,
capability_in.empty() ? plugin_key_in : capability_in, // title
plugin_key_in, // source_key
std::move(source_name)),
plugin_key(std::move(plugin_key_in)), capability(std::move(capability_in))
{}
AppActionRunResult run() const override
{
std::string error;
const ExecutionResult result = PluginManager::instance().run_script_capability(plugin_key, capability, error);
if (!error.empty())
return {AppActionRunResult::Level::Error, from_u8(error)};
const bool skipped = result.status == PluginResult::Skipped;
const wxString fallback = skipped ? _L("Script plugin skipped.") : _L("Script plugin finished.");
return {skipped ? AppActionRunResult::Level::Info : AppActionRunResult::Level::Success,
result.message.empty() ? fallback : from_u8(result.message)};
}
};
// Builds an action for a capability, or nullptr if it is not a currently-loaded,
// enabled script capability.
std::unique_ptr<AppAction> make_action(const std::string& plugin_key, const std::string& capability,
const std::string& source_name)
{
PluginManager& manager = PluginManager::instance();
if (!manager.is_plugin_loaded(plugin_key))
return nullptr;
// only_enabled defaults true, so a disabled capability resolves to nullptr here.
if (!manager.get_plugin_capability({PluginCapabilityType::Script, capability, plugin_key}))
return nullptr;
return std::make_unique<PluginScriptAction>(plugin_key, capability, source_name);
}
} // namespace
void ActionRegistry::init()
{
assert(wxThread::IsMain());
assert(!m_started);
m_started = true;
PluginManager& manager = PluginManager::instance();
auto on_source = [this](const std::string& plugin_key, ActionChange change) {
if (!wxTheApp || wxGetApp().is_closing())
return;
wxGetApp().CallAfter([this, plugin_key, change] {
if (!wxGetApp().is_closing())
this->refresh_source(plugin_key, change);
});
};
auto on_capability = [this](const PluginCapabilityId& capability, ActionChange change) {
if (capability.type != PluginCapabilityType::Script || !wxTheApp || wxGetApp().is_closing())
return;
const std::string plugin_key = capability.plugin_key;
const std::string name = capability.name;
wxGetApp().CallAfter([this, plugin_key, name, change] {
if (!wxGetApp().is_closing())
this->refresh_capability(plugin_key, name, change);
});
};
// Subscribe before enumerating so a concurrent load cannot land between the initial
// snapshot and callback registration. Duplicate notifications are safe: upsert is by
// id and the m_actions scan in refresh_source is idempotent.
manager.subscribe_on_load_callback(
[on_source](const std::string& key) { on_source(key, ActionChange::Added); });
manager.subscribe_on_unload_callback(
[on_source](const std::string& key) { on_source(key, ActionChange::Removed); });
manager.subscribe_on_capability_load_callback(
[on_capability](const PluginCapabilityId& capability) {
on_capability(capability, ActionChange::Added);
});
manager.subscribe_on_capability_unload_callback(
[on_capability](const PluginCapabilityId& capability) {
on_capability(capability, ActionChange::Removed);
});
// enumerate current script capabilities
std::unordered_map<std::string, std::string> source_names;
for (const PluginDescriptor& desc : manager.get_plugin_descriptors())
if (!desc.name.empty())
source_names.emplace(desc.plugin_key, desc.name);
for (const auto& capability : manager.get_plugin_capabilities("", PluginCapabilityType::Script)) {
if (!capability)
continue;
const std::string& key = capability->audit_plugin_key();
auto it = source_names.find(key);
const std::string& source_name = it == source_names.end() ? key : it->second;
if (auto action = make_action(key, capability->name(), source_name))
upsert(std::move(action));
}
}
void ActionRegistry::refresh_source(const std::string& plugin_key, ActionChange change)
{
assert(wxThread::IsMain());
// Remove this source's current actions. Collect first - erasing from m_actions while
// iterating invalidates the iterator. why: m_actions (not the loader) is the source of
// truth, so this is correct even after the plugin has already unloaded.
std::vector<std::string> stale;
for (const auto& [id, action] : m_actions)
if (action->source_key() == plugin_key)
stale.push_back(id);
for (const std::string& id : stale)
remove(id);
if (change == ActionChange::Removed)
return;
PluginManager& manager = PluginManager::instance();
const std::string source_name = find_loaded_source_name(manager, plugin_key);
for (const auto& capability : manager.get_plugin_capabilities(plugin_key, PluginCapabilityType::Script)) {
if (!capability)
continue;
if (auto action = make_action(plugin_key, capability->name(), source_name))
upsert(std::move(action));
}
}
void ActionRegistry::refresh_capability(const std::string& plugin_key, const std::string& capability,
ActionChange change)
{
assert(wxThread::IsMain());
const std::string id = PluginScriptAction::id_for(plugin_key, capability);
if (change == ActionChange::Removed) {
remove(id);
return;
}
PluginManager& manager = PluginManager::instance();
if (auto action = make_action(plugin_key, capability, find_loaded_source_name(manager, plugin_key)))
upsert(std::move(action));
else
remove(id);
}
void ActionRegistry::upsert(std::unique_ptr<AppAction> action)
{
assert(wxThread::IsMain());
if (!action)
return;
seed_state(*action);
std::string id = action->id();
std::shared_ptr<AppAction> stored = std::move(action);
m_actions.insert_or_assign(std::move(id), std::move(stored));
}
void ActionRegistry::remove(const std::string& id)
{
assert(wxThread::IsMain());
m_actions.erase(id);
}
void ActionRegistry::seed_state(AppAction& a) const
{
auto favs = read_string_array("favourite_actions");
a.favourite = std::find(favs.begin(), favs.end(), a.id()) != favs.end();
nlohmann::json stats = read_section("stats", nlohmann::json::object());
auto it = stats.find(a.id());
if (it != stats.end() && it->is_object()) {
a.count = it->value("count", 0);
a.last = it->value("last", 0LL);
} else {
a.count = 0;
a.last = 0;
}
}
// ---- read surface -----------------------------------------------------------
const AppAction* ActionRegistry::by_id(const std::string& id) const
{
assert(wxThread::IsMain());
auto it = m_actions.find(id);
return it == m_actions.end() ? nullptr : it->second.get();
}
AppAction* ActionRegistry::find(const std::string& id)
{
return const_cast<AppAction*>(by_id(id));
}
// ---- dispatch + write-through ----------------------------------------------
AppActionRunResult ActionRegistry::run(const std::string& id)
{
assert(wxThread::IsMain());
auto it = m_actions.find(id);
if (it == m_actions.end())
return {}; // default Info, empty message
// why: hold a shared_ptr keep-alive, never a bare map entry. A runner may pump a
// nested event loop; a queued source refresh can erase the entry while the
// keep-alive preserves the action until run returns.
std::shared_ptr<AppAction> keep = it->second;
AppActionRunResult o = keep->run();
if (o.level == AppActionRunResult::Level::Busy)
return o;
// Bump stats (write-through). Re-read to avoid clobbering a concurrent field.
nlohmann::json stats = read_section("stats", nlohmann::json::object());
if (!stats.is_object()) // corrupt (valid-JSON, non-object) value degrades to empty
stats = nlohmann::json::object();
nlohmann::json& e = stats[id];
if (!e.is_object())
e = nlohmann::json::object();
e["count"] = e.value("count", 0) + 1;
e["last"] = (long long) std::time(nullptr);
write_section("stats", stats);
if (AppAction* live = find(id)) { live->count = e["count"]; live->last = e["last"]; }
return o;
}
void ActionRegistry::set_favourite(const std::string& id, bool on)
{
assert(wxThread::IsMain());
auto favs = read_string_array("favourite_actions");
auto it = std::find(favs.begin(), favs.end(), id);
if (on && it == favs.end())
favs.push_back(id);
if (!on && it != favs.end())
favs.erase(it);
write_section("favourite_actions", nlohmann::json(favs));
if (AppAction* live = find(id))
live->favourite = on;
}
void ActionRegistry::reorder_favourites(const std::vector<std::string>& ids)
{
assert(wxThread::IsMain());
auto cur = read_string_array("favourite_actions");
std::vector<std::string> next;
// keep the requested order, but only ids that are actually favourites (guard a bad payload)
for (const auto& id : ids)
if (std::find(cur.begin(), cur.end(), id) != cur.end() &&
std::find(next.begin(), next.end(), id) == next.end())
next.push_back(id);
// why: don't drop favourites the page omitted (e.g. pins with no live action hidden from the bar)
for (const auto& id : cur)
if (std::find(next.begin(), next.end(), id) == next.end())
next.push_back(id);
write_section("favourite_actions", nlohmann::json(next));
}
bool ActionRegistry::should_ask(const std::string& id) const
{
assert(wxThread::IsMain());
auto arr = read_string_array("ask_suppressed");
return std::find(arr.begin(), arr.end(), id) == arr.end();
}
void ActionRegistry::suppress_ask(const std::string& id)
{
assert(wxThread::IsMain());
auto arr = read_string_array("ask_suppressed");
if (std::find(arr.begin(), arr.end(), id) == arr.end())
arr.push_back(id);
write_section("ask_suppressed", nlohmann::json(arr));
}
// ---- snapshot ---------------------------------------------------------------
nlohmann::json ActionRegistry::snapshot() const
{
assert(wxThread::IsMain());
std::vector<const AppAction*> sorted;
sorted.reserve(m_actions.size());
for (const auto& entry : m_actions)
sorted.push_back(entry.second.get());
const long long now = (long long) std::time(nullptr);
std::sort(sorted.begin(), sorted.end(), [&](const AppAction* a, const AppAction* b) {
double sa = frecency_score(a->count, a->last, now);
double sb = frecency_score(b->count, b->last, now);
if (sa != sb)
return sa > sb;
if (a->title() != b->title())
return a->title() < b->title();
if (a->source_name() != b->source_name())
return a->source_name() < b->source_name();
return a->id() < b->id();
});
nlohmann::json actions = nlohmann::json::array();
for (const AppAction* a : sorted)
actions.push_back({{"id", a->id()},
{"title", a->title()},
{"source", a->source_name()},
{"shortcut", ""}});
// why: favourites is the ORDERED pin list - it must come from favourite_actions
// as stored, not be re-derived from the frecency-sorted actions (that would
// reorder the favourites bar). The page (js) filters out ids with no live action itself.
nlohmann::json favourites(read_string_array("favourite_actions"));
return {{"actions", std::move(actions)}, {"favourites", std::move(favourites)}};
}
}} // namespace Slic3r::GUI
+135
View File
@@ -0,0 +1,135 @@
#pragma once
#include <nlohmann/json.hpp>
#include <wx/string.h>
#include <wx/thread.h>
#include <cassert>
#include <memory>
#include <string>
#include <string_view>
#include <unordered_map>
#include <utility>
#include <vector>
namespace Slic3r { namespace GUI {
// How a source's action set changed. Drives the registry's refresh handlers.
enum class ActionChange { Added, Removed };
// Result of running an AppAction, in the action layer's own vocabulary. Concrete
// actions translate their runner-specific result into this generic shape.
struct AppActionRunResult
{
enum class Level { Success, Info, Error, Busy };
Level level = Level::Info;
wxString message; // empty = "nothing worth showing"
};
// A speed-dial action: identity + user-state seeded from config + how to run itself.
// Abstract base - the only virtual is run(); concrete subclasses know how to run
// and what their source is.
// note: named AppAction, not Action - Slic3r::GUI::Action is already taken by
// UnsavedChangesDialog's exit-action enum, and this header reaches most GUI TUs.
struct AppAction
{
const std::string& id() const { return m_id; }
const std::string& title() const { return m_title; }
const std::string& source_key() const { return m_source_key; } // stable source identity
const std::string& source_name() const { return m_source_name; } // source display name
// Builds the stable id "<prefix>:<title>:<source_key>". why: one place owns the
// format - both the base ctor and the one raw-key lookup (removing a capability
// without an action object) go through this; ids are never parsed back apart.
static std::string compose_id(std::string_view prefix, std::string_view title, std::string_view source_key)
{
std::string out;
out.reserve(prefix.size() + title.size() + source_key.size() + 2);
out.append(prefix).append(1, ':').append(title).append(1, ':').append(source_key);
return out;
}
// seeded from AppConfig for the snapshot / sort:
bool favourite = false;
int count = 0;
long long last = 0; // epoch seconds
virtual ~AppAction() = default;
virtual AppActionRunResult run() const = 0; // re-resolves + runs (UI thread)
protected:
// The definition is constructor-set and immutable. Refreshes replace an action
// instead of mutating identity after the registry has indexed it by id.
// why: source_key (not the display name) carries identity, so renaming the source's
// display name leaves the id - and its persisted stats/favourite - intact.
AppAction(std::string_view prefix, std::string title, std::string source_key, std::string source_name)
: m_id(compose_id(prefix, title, source_key)),
m_title(std::move(title)),
m_source_key(std::move(source_key)),
m_source_name(std::move(source_name)) {}
private:
std::string m_id; // <prefix>:<title>:<source_key> - stable identity + AppConfig key
std::string m_title; // display name
std::string m_source_key; // stable identity of the action's source (e.g. plugin_key)
std::string m_source_name; // display name of the action's source
};
// Self-contained sink and single owner of runnable actions for the app session.
//
// Workflow:
// 1. init() (once, UI thread) subscribes to the plugin loader and enumerates the
// current script capabilities into actions.
// 2. Loader load/unload callbacks route through refresh_source()/refresh_capability(),
// which upsert()/remove() actions. The registry keeps the only action list and
// restores persisted user state as actions arrive.
// 3. Consumers use by_id(), snapshot(), and run() without knowing the source.
//
// note: there is exactly one source (script plugins), so it lives inline here rather
// than behind a polymorphic source interface.
class ActionRegistry
{
public:
// Subscribes to the plugin loader and enumerates its current actions. Call once
// on the UI thread after the plugin system is up; wires the initial list and live
// updates together.
void init();
// Takes ownership, seeds persisted state, then inserts the action or replaces
// the action with the same id. A null action is ignored.
void upsert(std::unique_ptr<AppAction> action);
// Removes the action with this id. Missing ids are a harmless no-op.
void remove(const std::string& id);
// Always-clean read surface. UI thread only.
const AppAction* by_id(const std::string& id) const;
// Dispatch + write-through (registry is the only thing that touches AppConfig).
AppActionRunResult run(const std::string& id); // runs + bumps stats
void set_favourite(const std::string& id, bool on);
void reorder_favourites(const std::vector<std::string>& ids); // persist a new bar order
// Run-confirm gate, keyed by action id (per-action "don't ask again").
bool should_ask(const std::string& id) const;
void suppress_ask(const std::string& id);
// Flat, frecency-sorted snapshot for the webview: {actions:[...], favourites:[...]}.
nlohmann::json snapshot() const;
private:
void seed_state(AppAction& a) const; // favourite/stats from config
AppAction* find(const std::string& id);
// Loader callbacks (marshalled to the UI thread) land here. refresh_source rebuilds
// one plugin's whole action set; refresh_capability touches a single capability.
void refresh_source(const std::string& plugin_key, ActionChange change);
void refresh_capability(const std::string& plugin_key, const std::string& capability, ActionChange change);
bool m_started = false; // init() runs exactly once; guards double-subscription
std::unordered_map<std::string, std::shared_ptr<AppAction>> m_actions; // UI-thread confined; no lock
};
}} // namespace Slic3r::GUI
File diff suppressed because it is too large Load Diff
+119 -26
View File
@@ -34,7 +34,7 @@
#include "Widgets/CheckBox.hpp"
#include "Widgets/ComboBox.hpp"
#include "Widgets/ScrolledWindow.hpp"
#include "Widgets/SwitchButton.hpp"
#include "Widgets/SwitchButton.hpp" // Orca: SwitchBoard (m_nozzle_btn_panel); not transitively available here
#include "Widgets/PopupWindow.hpp"
#include <wx/simplebook.h>
#include <wx/hashmap.h>
@@ -43,7 +43,19 @@
#define MAPPING_ITEM_INVALID_REMAIN -1
namespace Slic3r { namespace GUI {
// Previous definitions
namespace Slic3r
{
class DevNozzleRack;
namespace GUI
{
class wgtDeviceNozzleRackSelect;
class wgtMsgPanel;
};
};
namespace Slic3r {
namespace GUI {
#define AMS_TOTAL_COUNT 4
@@ -57,7 +69,8 @@ enum TrayType {
enum ShowType {
LEFT, // only show left ams and left ext
RIGHT, //only show right ams and right ext
LEFT_AND_RIGHT //show left and right ams at the same time
LEFT_AND_RIGHT, //show left and right ams at the same time
LEFT_AND_RIGHT_DYNAMIC //show all left and right at one panel when use_dynamic_switch
};
struct TrayData
@@ -78,23 +91,59 @@ struct TrayData
class MaterialItem: public wxPanel
{
protected:
int m_text_pos_x = 0;
int m_text_pos_y = -1;
bool m_dropdown_allow_painted = true;
wxString m_mapping_text;
public:
MaterialItem(wxWindow *parent, wxColour mcolour, wxString mname);
// Orca: filament_id defaults to empty so the existing 3-arg callers keep compiling;
// m_filament_id is otherwise unused here.
MaterialItem(wxWindow *parent, wxColour mcolour, wxString mname, std::string filament_id = std::string());
~MaterialItem();
wxPanel* m_main_panel;
void allow_paint_dropdown(bool flag);
void set_ams_text(const wxString& txt);
void set_ams_info(wxColour col, wxString txt,
int ctype = 0, std::vector<wxColour> cols = std::vector<wxColour>(),
bool record_back_info = false);
void set_material_cols(int ctype, const std::vector<wxColour>& cols);
void reset_ams_info();
virtual void reset_valid_info();
void set_nozzle_info(const wxString& mapped_nozzle_str);
void disable();
void enable();
void on_normal();
void on_selected();
void on_warning();
bool is_selected() const { return m_selected;}
bool is_warning() const { return m_warning;}
void msw_rescale();
protected:
void messure_size();
public:
std::string m_filament_id;
wxColour m_material_coloul;
int m_material_ctype = 0;
std::vector<wxColour> m_material_cols;
wxString m_material_name;
wxString m_mapped_nozzle_str;
//info
wxColour m_ams_coloul;
wxString m_ams_name;
int m_ams_ctype = 0;
std::vector<wxColour> m_ams_cols = std::vector<wxColour>();
//reset
wxColour m_back_ams_coloul;
wxString m_back_ams_name;
@@ -107,34 +156,24 @@ 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};
bool m_match {true};
bool m_enable {true};
void msw_rescale();
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();
void disable();
void enable();
void on_normal();
void on_selected();
void on_warning();
void paintEvent(wxPaintEvent &evt);
virtual void render(wxDC &dc);
void match(bool mat);
virtual void doRender(wxDC &dc);
virtual void reset_valid_info();
};
class MaterialSyncItem : public MaterialItem
{
public:
MaterialSyncItem(wxWindow *parent, wxColour mcolour, wxString mname);
// Orca: filament_id defaults to empty for existing 3-arg callers (see MaterialItem).
MaterialSyncItem(wxWindow *parent, wxColour mcolour, wxString mname, std::string filament_id = std::string());
~MaterialSyncItem();
int get_real_offset();
void render(wxDC &dc) override;
@@ -166,7 +205,7 @@ public:
public:
void update_data(TrayData data);
void send_event(int fliament_id);
void set_data(const wxString& tag_name, wxColour colour, wxString name, bool remain_detect, TrayData data, bool unmatch = false);
void set_data(const wxString& tag_name, wxColour colour, wxString name, bool remain_detect, TrayData data, bool unmatch = false, std::optional<wxString> tooltip_opt = std::nullopt);
void set_checked(bool checked);
void set_tray_index(wxString t_index) { m_tray_index = t_index; };
@@ -207,13 +246,38 @@ protected:
void doRender(wxDC& dc);
};
class DevIconLabel : public wxPanel
{
public:
DevIconLabel(wxWindow* parent, const wxString& icon, const wxString& label);
public:
void SetAllBackgroundColor(const wxColour& color);
Label* GetLabelItem() const { return m_label; }
void SetLabel(const wxString& label);
void SetIcon(const wxString& icon);
void Rescale();
private:
void CreateGui();
private:
Label* m_label{ nullptr };
wxString m_icon_str;
wxStaticBitmap* m_icon{ nullptr };
};
class AmsMapingPopup : public PopupWindow
{
bool m_use_in_sync_dialog = false;
bool m_ams_remain_detect_flag = false;
bool m_ext_mapping_filatype_check = true;
wxStaticText* m_title_text{ nullptr };
wgtDeviceNozzleRackSelect *m_rack_nozzle_select{nullptr};
DevIconLabel* m_flush_warning_panel;
public:
AmsMapingPopup(wxWindow *parent,bool use_in_sync_dialog = false);
~AmsMapingPopup() {};
@@ -230,7 +294,10 @@ public:
int m_current_filament_id;
ShowType m_show_type{ShowType::RIGHT};
std::string m_tag_material;
wxScrolledWindow *m_scrolled_window{nullptr};
wxBoxSizer *m_sizer_main{nullptr};
wxBoxSizer *m_sizer_main_h{nullptr};
wxBoxSizer *m_sizer_ams_v{nullptr};
wxBoxSizer *m_sizer_ams{nullptr};
wxBoxSizer *m_sizer_ams_left{nullptr};
wxBoxSizer *m_sizer_ams_right{nullptr};
@@ -244,28 +311,34 @@ public:
MappingItem* m_right_extra_slot{nullptr};
wxPanel * m_left_marea_panel{nullptr};
wxPanel * m_right_marea_panel{nullptr};
wxPanel* m_right_marea_panel{ nullptr }; // used as right if both left and right sides shown. used as single panel if only one side shown.
wxPanel * m_left_first_text_panel{nullptr};
wxPanel * m_right_first_text_panel{nullptr};
wgtMsgPanel* m_ams_tips_msg_panel{nullptr};
wxPanel * m_split_line_panel{nullptr};
wxBoxSizer * m_left_split_ams_sizer{nullptr};
wxBoxSizer * m_right_split_ams_sizer{nullptr};
wxBoxSizer * m_right_split_ext_sizer{ nullptr };
Label * m_left_tips{nullptr};
Label * m_right_tips{nullptr};
ScalableButton* m_reset_btn{nullptr};
wxString m_single_tip_text;
wxString m_left_tip_text;
wxString m_right_tip_text;
wxString m_ams_tips_panel_text;
wxBoxSizer* m_sizer_split_ams_left;
wxBoxSizer* m_sizer_split_ams_right;
bool m_mapping_from_multi_machines {false};
bool get_use_in_sync_dialog() { return m_use_in_sync_dialog; }
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_title(MachineObject* obj);
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_items_check_state(const std::vector<FilamentInfo>& ams_mapping_result);
void update_ams_data_multi_machines();
void add_ams_mapping(std::vector<TrayData> tray_data, bool remain_detect_flag, wxWindow *container, wxBoxSizer *sizer);
@@ -279,15 +352,24 @@ public:
void paintEvent(wxPaintEvent &evt);
void set_parent_item(MaterialItem* item) {m_parent_item = item;};
void set_show_type(ShowType type) { m_show_type = type; };
// Orca: kept as a no-op for SelectMachine — its only caller passes false, which
// equals the update path's default (all slots shown).
void set_only_show_ext_spool(bool /*flag*/) {}
// Orca: dropped by the reference but still consumed by SelectMachine.
std::vector<TrayData> parse_ams_mapping(const std::map<std::string, DevAms*, NumericStrCompare>& amsList);
#ifdef __APPLE__
void on_mouse_move(wxMouseEvent &evt);
wxPopupWindow * m_tip_popup{nullptr};
Label* m_tip_label{nullptr};
#endif
using ResetCallback = std::function<void(const std::string&)>;
void reset_ams_info();
void set_reset_callback(ResetCallback callback);
void show_reset_button();
void set_material_index_str(std::string str) { m_material_index = str; }
const std::string &get_material_index_str() { return m_material_index; }
void set_only_show_ext_spool(bool flag);
public:
void msw_rescale();
@@ -295,9 +377,20 @@ public:
void EnableExtMappingFilaTypeCheck(bool to_check = true) { m_ext_mapping_filatype_check = to_check;} ;
private:
// update
void update_title(MachineObject* obj);
void update_ams_tips(MachineObject* obj);
void update_mapping_items(MachineObject* obj, const std::vector<FilamentInfo>& ams_mapping_result, bool use_dynamic_switch);
// events
void OnNozzleMappingSelected(wxCommandEvent& evt);
void update_flush_waste(MachineObject* obj);
private:
std::weak_ptr<DevNozzleRack> m_rack;
ResetCallback m_reset_callback{nullptr};
std::string m_material_index;
bool m_only_show_ext_spool{false};
};
class AmsMapingTipPopup : public PopupWindow
+708
View File
@@ -0,0 +1,708 @@
/*****************************************************************//**
* \file AmsMappingPopupUpdate.cpp
* \brief do update for AmsMappingPopup
* \note moved from AmsMappingPopup.cpp to here to reduce the code size of AmsMappingPopup.cpp
*
* \author xin.zhang
* \date February 2026
*********************************************************************/
#include "AmsMappingPopup.hpp"
#include "I18N.hpp"
#include "DeviceCore/DevConfigUtil.h"
#include "libslic3r/Utils.hpp"
#include "slic3r/Utils/WxFontUtils.hpp"
#include "GUI.hpp"
#include "GUI_App.hpp"
#include "GUI_Preview.hpp"
#include "MainFrame.hpp"
#include "format.hpp"
#include "Widgets/ProgressDialog.hpp"
#include "Widgets/RoundedRectangle.hpp"
#include "Widgets/StaticBox.hpp"
#include <wx/progdlg.h>
#include <wx/clipbrd.h>
#include <wx/dcgraph.h>
#include <wx/graphics.h>
#include <miniz.h>
#include <algorithm>
#include <optional>
#include "Plater.hpp"
#include "BitmapCache.hpp"
#include "DeviceCore/DevFilaSystem.h"
#include "DeviceCore/DevFilaSwitch.h"
#include "DeviceCore/DevMappingNozzle.h"
#include "DeviceCore/DevNozzleRack.h" // Orca: full type for GetNozzleSystem()->GetNozzleRack()->IsSupported()
#include "DeviceTab/wgtDeviceNozzleSelect.h"
#include "DeviceTab/wgtMsgPanel.h"
namespace Slic3r::GUI {
static void _add_containers(const AmsMapingPopup* win,
std::list<MappingContainer*>& one_slot_containers,
const std::vector<MappingContainer*>& four_slots_containers,
wxBoxSizer* target_sizer)
{
for (auto container : four_slots_containers) { target_sizer->Add(container, 0, wxTOP, win->FromDIP(5)); }
while (!one_slot_containers.empty()) {
wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
for (int i = 0; i < 3; i++) {
if (one_slot_containers.empty()) { break; }
sizer->Add(one_slot_containers.front(), 0, wxLEFT, (i == 0) ? 0 : win->FromDIP(5));
one_slot_containers.pop_front();
}
target_sizer->Add(sizer, 0, wxTOP, win->FromDIP(5));
}
}
void AmsMapingPopup::update(MachineObject* obj,
const std::vector<FilamentInfo>& ams_mapping_result,
bool use_dynamic_switch,
std::optional<PrintFromType> print_type)
{
if (!obj) { return; } // Orca: guard before use (REF logs before its null check)
BOOST_LOG_TRIVIAL(info) << "ams_mapping total count " << obj->GetFilaSystem()->GetAmsCount();
for (auto& ams_container : m_amsmapping_container_list) {
ams_container->Destroy();
}
m_amsmapping_container_list.clear();
m_amsmapping_container_sizer_list.clear();
m_mapping_item_list.clear();
/*title*/
update_title(obj);
/*ams mapping*/
update_ams_tips(obj);
update_mapping_items(obj, ams_mapping_result, use_dynamic_switch);
/*rack*/
update_rack_select(obj, use_dynamic_switch, print_type);
update_flush_waste(obj);
if (wxGetApp().dark_mode() && m_reset_btn->GetName() != "erase_dark") {
m_reset_btn->SetName("erase_dark");
m_reset_btn->SetBitmap(ScalableBitmap(m_right_first_text_panel, "erase_dark", 14).bmp());
} else if (!wxGetApp().dark_mode() && m_reset_btn->GetName() != "erase") {
m_reset_btn->SetName("erase");
m_reset_btn->SetBitmap(ScalableBitmap(m_right_first_text_panel, "erase", 14).bmp());
}
size_t extruder_num = obj->GetExtderSystem()->GetTotalExtderCount();
if (extruder_num == 1) {
m_left_marea_panel->Hide();
m_left_extra_slot->Hide();
m_split_line_panel->Hide();
m_right_marea_panel->Show();
m_right_marea_panel->Enable(true);
m_right_extra_slot->Show();
m_right_extra_slot->Enable(true);
m_right_split_ext_sizer->Show(true);
set_sizer_title(m_right_split_ams_sizer, _L("AMS"));
} else if (extruder_num > 1) {
if (m_show_type == ShowType::LEFT_AND_RIGHT_DYNAMIC) {
m_left_marea_panel->Show(false);
m_left_extra_slot->Show(false);
m_right_extra_slot->Show(false);
m_left_split_ams_sizer->Show(false);
m_right_split_ams_sizer->Show(false);
m_right_split_ext_sizer->Show(false);
m_split_line_panel->Hide();
m_right_marea_panel->Show();
m_right_marea_panel->Enable(true);
} else {
m_left_marea_panel->Show();
m_left_extra_slot->Show();
m_split_line_panel->Show();
m_right_marea_panel->Show();
m_right_extra_slot->Show();
m_right_split_ext_sizer->Show(true);
m_left_tips->SetLabel(m_left_tip_text);
m_right_tips->SetLabel(m_right_tip_text);
set_sizer_title(m_left_split_ams_sizer, _L("Left AMS"));
set_sizer_title(m_right_split_ams_sizer, _L("Right AMS"));
if (m_show_type == ShowType::LEFT) {
m_left_marea_panel->Enable(true);
m_left_extra_slot->Enable(true);
m_right_marea_panel->Enable(false);
m_right_extra_slot->Enable(false);
if (m_use_in_sync_dialog) {
m_left_tips->SetLabel(m_single_tip_text);
m_right_tips->SetLabel("");
}
} else if (m_show_type == ShowType::RIGHT) {
m_left_marea_panel->Enable(false);
m_left_extra_slot->Enable(false);
m_right_marea_panel->Enable(true);
m_right_extra_slot->Enable(true);
if (m_use_in_sync_dialog) {
// m_right_tips->SetLabel(m_single_tip_text);
m_left_tips->SetLabel("");
}
} else if (m_show_type == ShowType::LEFT_AND_RIGHT) {
m_left_marea_panel->Enable(true);
m_left_extra_slot->Enable(true);
m_right_marea_panel->Enable(true);
m_right_extra_slot->Enable(true);
if (m_use_in_sync_dialog) {
m_left_tips->SetLabel(m_single_tip_text);
m_right_tips->SetLabel("");
}
} else if (m_show_type == ShowType::LEFT_AND_RIGHT_DYNAMIC) {
m_left_marea_panel->Show(false);
m_left_extra_slot->Show(false);
m_right_extra_slot->Show(false);
m_left_split_ams_sizer->Show(false);
m_right_split_ams_sizer->Show(false);
m_right_split_ext_sizer->Show(false);
m_split_line_panel->Hide();
m_right_marea_panel->Show();
m_right_marea_panel->Enable(true);
}
}
}
Layout();
Fit();
Refresh();
}
// Orca: the kept fila model has no tray-level ams_type; the owning AMS type is passed in
// (DevAms::GetAmsType() for AMS trays, DevAmsType::EXT_SPOOL for the virtual/ext slots).
static std::optional<TrayData> sGetTrayData(DevAmsTray* tray,
const std::string& ams_id_str,
DevAmsType ams_type)
{
int ams_id = 0;
int tray_id = 0;
try {
ams_id = std::stoi(ams_id_str);
tray_id = atoi(tray->id.c_str());
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":[error] invalid ams_id or tray_id: " << e.what();
return std::nullopt;
}
TrayData td;
td.ams_id = ams_id;
if (ams_type != DevAmsType::EXT_SPOOL) {
td.slot_id = tray_id;
} else {
td.slot_id = 0;
}
if (ams_type == DevAmsType::AMS ||
ams_type == DevAmsType::AMS_LITE ||
ams_type == DevAmsType::N3F) {
td.id = ams_id * 4 + tray_id;
} else if (ams_type == DevAmsType::N3S) {
td.id = ams_id + tray_id;
} else if(ams_type == DevAmsType::EXT_SPOOL){
td.id = tray_id;
}
if (ams_type != EXT_SPOOL && !tray->is_exists) {
td.type = EMPTY;
} else {
if (!tray->is_tray_info_ready()) {
td.type = THIRD;
} else {
td.type = NORMAL;
td.remain = tray->remain;
td.colour = DevAmsTray::decode_color(tray->color);
td.name = tray->get_display_filament_type();
td.filament_type = tray->get_filament_type();
td.ctype = tray->ctype;
for (const auto& col : tray->cols) {
td.material_cols.push_back(DevAmsTray::decode_color(col));
}
}
}
return td;
}
void AmsMapingPopup::update_mapping_items(MachineObject* obj, const std::vector<FilamentInfo>& ams_mapping_result, bool use_dynamic_switch)
{
std::list<MappingContainer*> left_one_slot_containers;
std::list<MappingContainer*> right_one_slot_containers;
std::vector<MappingContainer*> left_four_slots_containers;
std::vector<MappingContainer*> right_four_slot_containers;
const auto& ams_list = obj->GetFilaSystem()->GetAmsList();
for (auto ams_iter = ams_list.begin(); ams_iter != ams_list.end(); ams_iter++) {
const auto& extruder_set = ams_iter->second->GetBindedExtruderSet();
if (extruder_set.size() <= 0) {
continue;
}
wxPanel* target_panel = nullptr;
if (m_show_type == ShowType::LEFT_AND_RIGHT_DYNAMIC) {
target_panel = m_right_marea_panel;
} else {
int extruder_id = *extruder_set.begin();
if (extruder_id == MAIN_EXTRUDER_ID) {
target_panel = m_right_marea_panel;
} else if (extruder_id == DEPUTY_EXTRUDER_ID) {
target_panel = m_left_marea_panel;
}
}
if (!target_panel) {
continue;
}
auto sizer_mapping_list = new wxBoxSizer(wxHORIZONTAL);
auto ams_mapping_item_container = new MappingContainer(target_panel, ams_iter->second->GetDisplayName(), ams_iter->second->GetSlotCount());
ams_mapping_item_container->SetName(target_panel->GetName());
ams_mapping_item_container->SetSizer(sizer_mapping_list);
ams_mapping_item_container->Layout();
BOOST_LOG_TRIVIAL(trace) << "ams_mapping ams id " << ams_iter->first.c_str();
DevAms* ams_group = ams_iter->second;
auto ams_type = ams_group->GetAmsType();
std::vector<TrayData> tray_datas;
std::map<std::string, DevAmsTray*>::const_iterator tray_iter;
for (tray_iter = ams_group->GetTrays().cbegin(); tray_iter != ams_group->GetTrays().cend(); tray_iter++) {
const auto& td_opt = sGetTrayData(tray_iter->second, ams_group->GetAmsId(), ams_type);
if (td_opt.has_value()) {
tray_datas.push_back(td_opt.value());
}
}
ams_mapping_item_container->Show();
add_ams_mapping(tray_datas, obj->GetFilaSystem()->IsDetectRemainEnabled(), ams_mapping_item_container, sizer_mapping_list);
m_amsmapping_container_sizer_list.push_back(sizer_mapping_list);
m_amsmapping_container_list.push_back(ams_mapping_item_container);
if (target_panel == m_right_marea_panel) {
if (ams_mapping_item_container->get_slots_num() == 1) {
right_one_slot_containers.push_back(ams_mapping_item_container);
} else {
right_four_slot_containers.push_back(ams_mapping_item_container);
}
} else if (target_panel == m_left_marea_panel) {
if (ams_mapping_item_container->get_slots_num() == 1) {
left_one_slot_containers.push_back(ams_mapping_item_container);
} else {
left_four_slots_containers.push_back(ams_mapping_item_container);
}
}
}
for (int i = obj->vt_slot.size() - 1; i >= 0; i--) {
DevAmsTray* tray_data = &obj->vt_slot[i];
const auto& td_opt = sGetTrayData(tray_data, tray_data->id, DevAmsType::EXT_SPOOL);
if (!td_opt.has_value()) {
continue;
}
if (m_show_type == ShowType::LEFT_AND_RIGHT_DYNAMIC) {
auto sizer_mapping_list = new wxBoxSizer(wxHORIZONTAL);
const auto& shown_name = td_opt->ams_id == VIRTUAL_TRAY_MAIN_ID ? "Ext-R" : "Ext-L";
auto ams_mapping_item_container = new MappingContainer(m_right_marea_panel, shown_name, 1);
ams_mapping_item_container->SetName(m_right_marea_panel->GetName());
ams_mapping_item_container->SetSizer(sizer_mapping_list);
ams_mapping_item_container->Layout();
std::vector<TrayData> tray_datas;
tray_datas.push_back(td_opt.value());
add_ams_mapping(tray_datas, false, ams_mapping_item_container, sizer_mapping_list);
m_amsmapping_container_sizer_list.push_back(sizer_mapping_list);
m_amsmapping_container_list.push_back(ams_mapping_item_container);
right_one_slot_containers.push_back(ams_mapping_item_container);
} else {
if (td_opt->ams_id == VIRTUAL_TRAY_MAIN_ID) {
m_right_extra_slot->send_win = send_win;
add_ext_ams_mapping(td_opt.value(), m_right_extra_slot);
} else if (td_opt->ams_id == VIRTUAL_TRAY_DEPUTY_ID) {
m_left_extra_slot->send_win = send_win;
add_ext_ams_mapping(td_opt.value(), m_left_extra_slot);
}
}
}
_add_containers(this, left_one_slot_containers, left_four_slots_containers, m_sizer_ams_basket_left);
_add_containers(this, right_one_slot_containers, right_four_slot_containers, m_sizer_ams_basket_right);
if (m_show_type != ShowType::LEFT_AND_RIGHT_DYNAMIC){
m_left_split_ams_sizer->Show(!left_one_slot_containers.empty() || !left_four_slots_containers.empty());
m_right_split_ams_sizer->Show(!right_one_slot_containers.empty() || !right_four_slot_containers.empty());
} else {
m_left_split_ams_sizer->Show(false);
m_right_split_ams_sizer->Show(false);
}
update_items_check_state(ams_mapping_result);
}
void AmsMapingPopup::update_ams_data_multi_machines()
{
m_mapping_from_multi_machines = true;
std::vector<TrayData> tray_datas;
for (int i = 0; i < 4; ++i) {
TrayData td;
td.id = i;
td.type = EMPTY;
td.colour = wxColour(166, 169, 170);
td.name = "";
td.filament_type = "";
td.ctype = 0;
tray_datas.push_back(td);
}
for (auto& ams_container : m_amsmapping_container_list) {
ams_container->Destroy();
}
m_amsmapping_container_list.clear();
m_amsmapping_container_sizer_list.clear();
m_mapping_item_list.clear();
if (wxGetApp().dark_mode() && m_reset_btn->GetName() != "erase_dark") {
m_reset_btn->SetName("erase_dark");
m_reset_btn->SetBitmap(ScalableBitmap(m_right_first_text_panel, "erase_dark", 14).bmp());
} else if (!wxGetApp().dark_mode() && m_reset_btn->GetName() != "erase") {
m_reset_btn->SetName("erase");
m_reset_btn->SetBitmap(ScalableBitmap(m_right_first_text_panel, "erase", 14).bmp());
}
size_t nozzle_nums = 1;
m_show_type = ShowType::RIGHT;
m_left_marea_panel->Hide();
m_left_extra_slot->Hide();
// m_left_marea_panel->Show();
m_right_marea_panel->Show();
set_sizer_title(m_right_split_ams_sizer, _L("AMS"));
// m_right_tips->SetLabel(m_single_tip_text);
m_right_extra_slot->Hide();
m_left_extra_slot->Hide();
/*ams*/
bool has_left_ams = false, has_right_ams = false;
std::list<MappingContainer*> left_one_slot_containers;
std::list<MappingContainer*> right_one_slot_containers;
std::vector<MappingContainer*> left_four_slots_containers;
std::vector<MappingContainer*> right_four_slot_containers;
for (int i = 0; i < 1; i++) {
int ams_indx = 0;
int ams_type = 1;
int nozzle_id = 0;
if (ams_type >= 1 || ams_type <= 3) { // 1:ams 2:ams-lite 3:n3f
auto sizer_mapping_list = new wxBoxSizer(wxHORIZONTAL);
auto ams_mapping_item_container = new MappingContainer(nozzle_id == 0 ? m_right_marea_panel : m_left_marea_panel, "AMS-1", 4);
ams_mapping_item_container->SetName(nozzle_id == 0 ? m_right_marea_panel->GetName() : m_left_marea_panel->GetName());
ams_mapping_item_container->SetSizer(sizer_mapping_list);
ams_mapping_item_container->Layout();
m_has_unmatch_filament = false;
ams_mapping_item_container->Show();
add_ams_mapping(tray_datas, false, ams_mapping_item_container, sizer_mapping_list);
m_amsmapping_container_sizer_list.push_back(sizer_mapping_list);
m_amsmapping_container_list.push_back(ams_mapping_item_container);
if (nozzle_id == 0) {
has_right_ams = true;
if (ams_mapping_item_container->get_slots_num() == 1) {
right_one_slot_containers.push_back(ams_mapping_item_container);
} else {
right_four_slot_containers.push_back(ams_mapping_item_container);
}
} else if (nozzle_id == 1) {
has_left_ams = true;
if (ams_mapping_item_container->get_slots_num() == 1) {
left_one_slot_containers.push_back(ams_mapping_item_container);
} else {
left_four_slots_containers.push_back(ams_mapping_item_container);
}
}
} else if (ams_type == 4) { // 4:n3s
}
}
_add_containers(this, left_one_slot_containers, left_four_slots_containers, m_sizer_ams_basket_left);
_add_containers(this, right_one_slot_containers, right_four_slot_containers, m_sizer_ams_basket_right);
m_left_split_ams_sizer->Show(has_left_ams);
m_right_split_ams_sizer->Show(has_right_ams);
//update_items_check_state(ams_mapping_result);
Layout();
Fit();
}
void AmsMapingPopup::update_title(MachineObject* obj)
{
const auto& full_config = wxGetApp().preset_bundle->full_config();
size_t nozzle_nums = full_config.option<ConfigOptionFloatsNullable>("nozzle_diameter")->values.size();
if (nozzle_nums > 1) {
m_split_line_panel->Show();
if (m_show_type == ShowType::LEFT) {
wxString nozzle_name = obj ? _L(DevPrinterConfigUtil::get_toolhead_display_name(
obj->printer_type, DEPUTY_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::LowerCase)) : _L("left nozzle");
m_title_text->SetLabelText(wxString::Format(_L("Please select the filament installed on the %s."), nozzle_name));
return;
} else if (m_show_type == ShowType::RIGHT) {
wxString nozzle_name = obj ? _L(DevPrinterConfigUtil::get_toolhead_display_name(
obj->printer_type, MAIN_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::LowerCase)) : _L("right nozzle");
m_title_text->SetLabelText(wxString::Format(_L("Please select the filament installed on the %s."), nozzle_name));
return;
} else if (m_show_type == ShowType::LEFT_AND_RIGHT_DYNAMIC) {
m_title_text->SetLabelText(_L("Please select the filament."));
return;
}
} else if (nozzle_nums == 1) {
m_split_line_panel->Hide();
}
m_title_text->SetLabelText(_L("Nozzle"));
}
void AmsMapingPopup::update_ams_tips(MachineObject* obj)
{
const auto& full_config = wxGetApp().preset_bundle->full_config();
size_t nozzle_nums = full_config.option<ConfigOptionFloatsNullable>("nozzle_diameter")->values.size();
if (m_ams_tips_msg_panel) {
m_ams_tips_msg_panel->Clear();
if (nozzle_nums == 2 && m_show_type != ShowType::LEFT_AND_RIGHT) {
m_ams_tips_msg_panel->AddMessage(_L("To learn about the filaments matching rules."),
"#FF6F00", ""); // Orca: dropped vendor help link
}
if (obj && obj->GetFilaSwitch()->IsInstalled()) {
const auto& msg = _L("External spools is not supported since Filament Track Switch has been installed. If you want to use external spool, please uninstall it.");
m_ams_tips_msg_panel->AddMessage(msg, "#FF6F00", "");
}
m_ams_tips_msg_panel->Layout();
m_ams_tips_msg_panel->Fit();
m_ams_tips_msg_panel->Show(m_ams_tips_msg_panel->GetMessageCount() > 0);
}
}
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_items_check_state(const std::vector<FilamentInfo>& ams_mapping_result)
{
/*update check states*/
if (m_parent_item) {
auto update_item_check_state = [&ams_mapping_result, this](MappingItem* item) {
if (item) {
for (const auto& mapping_res : ams_mapping_result) {
if (mapping_res.id == this->m_current_filament_id) {
if (mapping_res.ams_id == std::to_string(item->m_ams_id) &&
mapping_res.slot_id == std::to_string(item->m_slot_id)) {
item->set_checked(true);
} else {
item->set_checked(false);
}
return;
}
}
item->set_checked(false);
}
};
update_item_check_state(m_left_extra_slot);
update_item_check_state(m_right_extra_slot);
for (auto mapping_item : m_mapping_item_list) {
update_item_check_state(mapping_item);
}
}
}
void AmsMapingPopup::add_ams_mapping(std::vector<TrayData> tray_data,
bool remain_detect_flag,
wxWindow* container,
wxBoxSizer* sizer)
{
sizer->Add(0, 0, 0, wxLEFT, FromDIP(6));
for (auto i = 0; i < tray_data.size(); i++) {
// set button
MappingItem* m_mapping_item = new MappingItem(container);
m_mapping_item->send_win = send_win;
m_mapping_item->m_ams_id = tray_data[i].ams_id;
m_mapping_item->m_slot_id = tray_data[i].slot_id;
// Orca: transition_tridid here has no total-extruder-count overload; label the virtual (ext)
// slots Ext-R / Ext-L inline in the two-nozzle left+right views (matches that overload's result).
if ((m_show_type == ShowType::LEFT_AND_RIGHT || m_show_type == ShowType::LEFT_AND_RIGHT_DYNAMIC)
&& (tray_data[i].id == VIRTUAL_TRAY_MAIN_ID || tray_data[i].id == VIRTUAL_TRAY_DEPUTY_ID)) {
m_mapping_item->set_tray_index(tray_data[i].id == VIRTUAL_TRAY_MAIN_ID ? wxString("Ext-R") : wxString("Ext-L"));
} else {
m_mapping_item->set_tray_index(wxGetApp().transition_tridid(tray_data[i].id));
}
m_mapping_item->SetSize(wxSize(FromDIP(48), FromDIP(60)));
m_mapping_item->SetMinSize(wxSize(FromDIP(48), FromDIP(60)));
m_mapping_item->SetMaxSize(wxSize(FromDIP(48), FromDIP(60)));
// traversal if can pick the item or not
bool can_pick_the_item = true;
std::optional<wxString> item_tooltip_msg;
// check filament area
if (m_use_in_sync_dialog) {
if (can_pick_the_item) {
if (m_show_type == ShowType::LEFT_AND_RIGHT_DYNAMIC) {
can_pick_the_item = !devPrinterUtil::IsVirtualSlot(m_mapping_item->m_ams_id);
if (!can_pick_the_item) {
item_tooltip_msg = _L(
"External spools is not supported since Filament Track Switch has been installed. If you want to use external spool, please uninstall it.");
}
}
}
}
else {
if (can_pick_the_item) {
// LEFT_AND_RIGHT — the fallback when a filament has no entry in the
// nozzle map — keeps both panels pickable.
auto parent = container->GetParent();
if (parent == m_left_marea_panel) {
can_pick_the_item = (m_show_type == ShowType::LEFT || m_show_type == ShowType::LEFT_AND_RIGHT);
} else if (parent == m_right_marea_panel) {
if (m_show_type == ShowType::LEFT_AND_RIGHT_DYNAMIC) {
can_pick_the_item = !devPrinterUtil::IsVirtualSlot(m_mapping_item->m_ams_id);
if (!can_pick_the_item) {
item_tooltip_msg = _L(
"External spools is not supported since Filament Track Switch has been installed. If you want to use external spool, please uninstall it.");
}
} else if (m_show_type != ShowType::RIGHT && m_show_type != ShowType::LEFT_AND_RIGHT) {
can_pick_the_item = false;
}
}
}
}
// check filament type
if (can_pick_the_item) {
if (tray_data[i].type == NORMAL && !is_match_material(tray_data[i].filament_type)){
can_pick_the_item = false;
} else if(tray_data[i].type == EMPTY){
// Mapping to an empty slot is only allowed from the multi-machine send page,
// which maps onto EMPTY placeholder trays.
can_pick_the_item = m_mapping_from_multi_machines;
}
}
wxColour display_color;
if (can_pick_the_item) {
display_color = tray_data[i].type == NORMAL ? tray_data[i].colour : wxColour(0xCE, 0xCE, 0xCE);
} else {
display_color = wxColour(0xEE, 0xEE, 0xEE);
}
wxString display_name;
if (tray_data[i].type == THIRD) {
display_name = "?";
} else if (tray_data[i].type == EMPTY) {
display_name = "-";
} else {
display_name = tray_data[i].name;
}
m_mapping_item->set_data(m_tag_material, display_color, display_name, remain_detect_flag, tray_data[i], !can_pick_the_item, item_tooltip_msg);
m_mapping_item->Bind(wxEVT_LEFT_DOWN, [this, can_pick_the_item, m_mapping_item](wxMouseEvent& e) {
if (can_pick_the_item) {
m_mapping_item->send_event(m_current_filament_id);
Dismiss();
}
});
sizer->Add(0, 0, 0, wxRIGHT, FromDIP(6));
sizer->Add(m_mapping_item, 0, wxTOP, FromDIP(1));
m_mapping_item_list.push_back(m_mapping_item);
}
}
void AmsMapingPopup::add_ext_ams_mapping(TrayData tray_data, MappingItem* item)
{
#ifdef __APPLE__
m_mapping_item_list.push_back(item);
#endif
// set button
if (tray_data.type == NORMAL) {
if (is_match_material(tray_data.filament_type)) {
item->set_data(m_tag_material, tray_data.colour, tray_data.name, false, tray_data);
} else {
item->set_data(m_tag_material, m_ext_mapping_filatype_check ? wxColour(0xEE, 0xEE, 0xEE) : tray_data.colour, tray_data.name, false, tray_data, true);
m_has_unmatch_filament = true;
}
item->Bind(wxEVT_LEFT_DOWN, [this, tray_data, item](wxMouseEvent& e) {
if (!item->GetParent() || !item->GetParent()->IsEnabled()) return;
if (m_ext_mapping_filatype_check && !is_match_material(tray_data.filament_type)) return;
item->send_event(m_current_filament_id);
Dismiss();
});
}
// temp
if (tray_data.type == EMPTY) {
item->set_data(m_tag_material, wxColour(0xCE, 0xCE, 0xCE), "-", false, tray_data);
item->Bind(wxEVT_LEFT_DOWN, [this, tray_data, item](wxMouseEvent& e) {
if (!item->GetParent() || !item->GetParent()->IsEnabled()) return;
item->send_event(m_current_filament_id);
Dismiss();
});
}
// third party
if (tray_data.type == THIRD) {
item->set_data(m_tag_material, tray_data.colour, "?", false, tray_data);
//item->set_data(wxColour(0xCE, 0xCE, 0xCE), "?", tray_data);
item->Bind(wxEVT_LEFT_DOWN, [this, tray_data, item](wxMouseEvent& e) {
if (!item->GetParent() || !item->GetParent()->IsEnabled()) return;
item->send_event(m_current_filament_id);
Dismiss();
});
}
item->set_tray_index("Ext");
}
} // namespace Slic3r::GUI
File diff suppressed because it is too large Load Diff
+26 -22
View File
@@ -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();
+6 -6
View File
@@ -192,7 +192,7 @@ PingCodeBindDialog::PingCodeBindDialog(Plater* plater /*= nullptr*/)
SetSizer(sizer_main);
SetSizerAndFit(sizer_main);
Layout();
Fit();
@@ -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);
@@ -670,7 +670,7 @@ PingCodeBindDialog::~PingCodeBindDialog() {
m_sizer_main->Add(m_sw_bind_failed_info, 0, wxALIGN_CENTER, 0);
m_sizer_main->Add(m_simplebook, 0, wxALIGN_RIGHT | wxRIGHT | wxBOTTOM, ButtonProps::ChoiceButtonGap());
SetSizer(m_sizer_main);
SetSizerAndFit(m_sizer_main);
Layout();
Fit();
Centre(wxBOTH);
@@ -813,7 +813,7 @@ PingCodeBindDialog::~PingCodeBindDialog() {
m_simplebook->SetSelection(0);
auto m_bind_job = std::make_unique<BindJob>(
m_machine_info->get_dev_id(), m_machine_info->get_dev_ip(), m_machine_info->bind_sec_link, m_machine_info->bind_ssdp_version);
m_machine_info->get_dev_id(), m_machine_info->get_dev_ip(), m_machine_info->get_show_printer_type(), m_machine_info->bind_sec_link, m_machine_info->bind_ssdp_version);
if (m_machine_info && (m_machine_info->get_printer_series() == PrinterSeries::SERIES_X1)) {
m_bind_job->set_improved(false);
@@ -992,7 +992,7 @@ UnBindMachineDialog::UnBindMachineDialog(Plater *plater /*= nullptr*/)
m_sizer_main->Add(m_sizer_button, 0, wxALIGN_RIGHT | wxRIGHT, ButtonProps::ChoiceButtonGap());
m_sizer_main->Add(0, 0, 0, wxTOP, FromDIP(20));
SetSizer(m_sizer_main);
SetSizerAndFit(m_sizer_main);
Layout();
Fit();
Centre(wxBOTH);
+14 -12
View File
@@ -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);
+143 -28
View File
@@ -13,6 +13,9 @@
#include "Plater.hpp"
#include "DeviceCore/DevExtruderSystem.h"
#include "DeviceCore/DevManager.h"
#include "DeviceCore/DevConfigUtil.h"
#include "DeviceCore/DevNozzleSystem.h"
#include "DeviceCore/DevNozzleRack.h"
namespace Slic3r {
namespace GUI {
@@ -26,6 +29,7 @@ namespace GUI {
enum CaliColumnType : int {
Cali_Name = 0,
Cali_Filament,
Cali_Nozzle_ID,
Cali_Nozzle,
Cali_K_Value,
Cali_Delete,
@@ -33,6 +37,9 @@ enum CaliColumnType : int {
Cali_Type_Count
};
// Orca: derive nozzle-volume support from the machine preset's nozzle_volume array
// rather than a live device-capability query, to avoid flipping the Nozzle-Flow column
// visibility on shipping printers (H2D/X1/P1/A1).
bool support_nozzle_volume(const MachineObject* obj)
{
if (!obj)
@@ -49,14 +56,17 @@ bool support_nozzle_volume(const MachineObject* obj)
return false;
}
int get_colume_idx(CaliColumnType type, MachineObject* obj)
// Rack hotend position code -> label: 0 = fixed ("R"); >=0x10 = rack slot number; else N/A.
static wxString nozzle_id_code_to_string(int code)
{
if (!support_nozzle_volume(obj)
&& (type > CaliColumnType::Cali_Nozzle)) {
return type - 1;
if (code == 0) {
return "R";
} else if (code >= 0x10) {
return wxString::Format("%d", code - 0x10 + 1);
} else {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << "Nozzle position id is -1 or invalid.";
return "N/A";
}
return type;
}
static wxString get_preset_name_by_filament_id(std::string filament_id, bool follow_succession = true)
@@ -250,6 +260,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
@@ -326,7 +340,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();
@@ -337,24 +365,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"));
@@ -363,7 +402,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) {
@@ -379,13 +423,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();
@@ -398,6 +442,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);
});
@@ -424,17 +470,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;
}
@@ -529,13 +581,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;
@@ -577,9 +638,8 @@ EditCalibrationHistoryDialog::EditCalibrationHistoryDialog(wxWindow
main_sizer->Add(top_panel, 1, wxEXPAND | wxALL, FromDIP(20));
SetSizer(main_sizer);
SetSizerAndFit(main_sizer);
Layout();
Fit();
CenterOnParent();
wxGetApp().UpdateDlgDarkUI(this);
@@ -774,14 +834,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);
@@ -829,9 +915,8 @@ NewCalibrationHistoryDialog::NewCalibrationHistoryDialog(wxWindow *parent, const
main_sizer->Add(top_panel, 1, wxEXPAND | wxALL, FromDIP(20));
SetSizer(main_sizer);
SetSizerAndFit(main_sizer);
Layout();
Fit();
CenterOnParent();
wxGetApp().UpdateDlgDarkUI(this);
@@ -845,6 +930,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();
@@ -903,6 +1017,7 @@ void NewCalibrationHistoryDialog::on_ok(wxCommandEvent &event)
m_new_result.nozzle_diameter = nozzle_value;
m_new_result.filament_id = filament_id;
m_new_result.setting_id = setting_id;
m_new_result.nozzle_pos_id = get_nozzle_combo_id_code();
// Check for duplicate names from history
{
+4
View File
@@ -27,6 +27,7 @@ protected:
void enbale_action_buttons(bool enable);
float get_nozzle_value();
int get_extruder_id();
bool support_nozzle_id_column();
void on_click_new_button(wxCommandEvent &event);
@@ -76,10 +77,12 @@ public:
protected:
virtual void on_ok(wxCommandEvent &event);
virtual void on_cancel(wxCommandEvent &event);
void on_select_nozzle_pos(wxCommandEvent &event);
wxArrayString get_all_filaments(const MachineObject *obj);
int get_extruder_id(int extruder_index); // extruder_index 0 : left, 1 : right
int get_nozzle_combo_id_code() const; // rack hotend position code, -1 if none
protected:
PACalibResult m_new_result;
@@ -93,6 +96,7 @@ protected:
ComboBox *m_comboBox_filament;
ComboBox *m_comboBox_extruder;
ComboBox *m_comboBox_nozzle_type;
ComboBox *m_comboBox_nozzle_id{nullptr};
struct FilamentInfos
{
+1 -1
View File
@@ -162,7 +162,7 @@ CalibrationDialog::CalibrationDialog(Plater *plater)
body_panel->Layout();
m_sizer_main->Add(body_panel, 0, wxEXPAND | wxALL, FromDIP(25));
SetSizer(m_sizer_main);
SetSizerAndFit(m_sizer_main);
Layout();
Fit();
+34 -21
View File
@@ -9,7 +9,9 @@
#include "DeviceCore/DevExtruderSystem.h"
#include "DeviceCore/DevFilaBlackList.h"
#include "DeviceCore/DevFilaSystem.h"
#include "DeviceCore/DevFilaSwitch.h"
#include "DeviceCore/DevManager.h"
#include "DeviceCore/DevNozzleSystem.h"
#include "DeviceCore/DevStorage.h"
#define CALIBRATION_LABEL_SIZE wxSize(FromDIP(150), FromDIP(24))
@@ -1457,31 +1459,40 @@ bool CalibrationPresetPage::is_filament_in_blacklist(int tray_id, Preset* preset
get_tray_ams_and_slot_id(curr_obj, tray_id, ams_id, slot_id, out_tray_id);
if (wxGetApp().app_config->get("skip_ams_blacklist_check") != "true") {
bool in_blacklist = false;
std::string action;
wxString info;
std::string filamnt_type;
preset->get_filament_type(filamnt_type);
DevFilaBlacklist::CheckFilamentInfo check_info;
check_info.dev_id = curr_obj->get_dev_id();
check_info.model_id = curr_obj->printer_type;
check_info.fila_id = preset->filament_id;
preset->get_filament_type(check_info.fila_type);
check_info.ams_id = ams_id;
check_info.slot_id = slot_id;
check_info.has_filament_switch = curr_obj->GetFilaSwitch()->IsInstalled();
// fila_name intentionally left empty: the engine recovers it from the selected AMS slot,
// preserving the name-match behavior.
auto vendor = dynamic_cast<ConfigOptionStrings*> (preset->config.option("filament_vendor"));
if (vendor && (vendor->values.size() > 0)) {
std::string vendor_name = vendor->values[0];
DevFilaBlacklist::check_filaments_in_blacklist(curr_obj->printer_type, vendor_name, filamnt_type, preset->filament_id, ams_id, slot_id, "", in_blacklist, action, info);
check_info.fila_vendor = vendor->values[0];
}
if (in_blacklist) {
error_tips = info.ToUTF8().data();
if (action == "prohibition") {
return false;
}
else if (action == "warning") {
return true;
}
const auto &result = DevFilaBlacklist::check_filaments_in_blacklist(check_info);
if (const auto &prohibition_items = result.get_items_by_action("prohibition"); !prohibition_items.empty()) {
wxString combined_msg;
for (const auto &item : prohibition_items) { combined_msg += item.info_msg + "\n"; }
error_tips = combined_msg.ToUTF8().data();
return false;
}
else {
error_tips = "";
if (const auto &warning_items = result.get_items_by_action("warning"); !warning_items.empty()) {
wxString combined_msg;
for (const auto &item : warning_items) { combined_msg += item.info_msg + "\n"; }
error_tips = combined_msg.ToUTF8().data();
return true;
}
error_tips = "";
return true;
}
if (devPrinterUtil::IsVirtualSlot(ams_id)) {
if (m_cali_mode == CalibMode::Calib_PA_Line && (m_cali_method == CalibrationMethod::CALI_METHOD_AUTO || m_cali_method == CalibrationMethod::CALI_METHOD_NEW_AUTO)) {
@@ -1663,7 +1674,9 @@ bool CalibrationPresetPage::is_nozzle_info_synced() const
if (curr_obj->is_nozzle_flow_type_supported()) {
if (extruder.GetNozzleFlowType() == NozzleFlowType::NONE_FLOWTYPE)
return false;
if (int(extruder.GetNozzleFlowType()) - 1 != int(get_nozzle_volume_type(extruder_id)))
// Map device flow -> volume type via DevNozzle::ToNozzleVolumeType so U_FLOW resolves to
// nvtTPUHighFlow(3); the naive flow-1 would yield nvtHybrid(2). Identical to flow-1 for S/H flow.
if (int(DevNozzle::ToNozzleVolumeType(extruder.GetNozzleFlowType())) != int(get_nozzle_volume_type(extruder_id)))
return false;
}
}
@@ -2124,7 +2137,7 @@ void CalibrationPresetPage::init_with_machine(MachineObject* obj)
}
if (obj->GetExtderSystem()->GetNozzleFlowType(i) != NozzleFlowType::NONE_FLOWTYPE) {
m_left_comboBox_nozzle_volume->SetSelection(obj->GetExtderSystem()->GetNozzleFlowType(i) - 1);
m_left_comboBox_nozzle_volume->SetSelection(int(DevNozzle::ToNozzleVolumeType(obj->GetExtderSystem()->GetNozzleFlowType(i))));
} else {
m_left_comboBox_nozzle_volume->SetSelection(0);
}
@@ -2144,7 +2157,7 @@ void CalibrationPresetPage::init_with_machine(MachineObject* obj)
}
if (obj->GetExtderSystem()->GetNozzleFlowType(i) != NozzleFlowType::NONE_FLOWTYPE) {
m_right_comboBox_nozzle_volume->SetSelection(obj->GetExtderSystem()->GetNozzleFlowType(i) - 1);
m_right_comboBox_nozzle_volume->SetSelection(int(DevNozzle::ToNozzleVolumeType(obj->GetExtderSystem()->GetNozzleFlowType(i))));
} else {
m_right_comboBox_nozzle_volume->SetSelection(0);
}
@@ -2182,7 +2195,7 @@ void CalibrationPresetPage::init_with_machine(MachineObject* obj)
else {
if ((obj->GetExtderSystem()->GetTotalExtderCount() > 0) && (obj->GetExtderSystem()->GetNozzleFlowType(0) != NozzleFlowType::NONE_FLOWTYPE))
{
m_comboBox_nozzle_volume->SetSelection(obj->GetExtderSystem()->GetNozzleFlowType(0) - 1);
m_comboBox_nozzle_volume->SetSelection(int(DevNozzle::ToNozzleVolumeType(obj->GetExtderSystem()->GetNozzleFlowType(0))));
} else {
m_comboBox_nozzle_volume->SetSelection(0);
}
+132 -89
View File
@@ -2,6 +2,9 @@
#include "I18N.hpp"
#include "Widgets/Label.hpp"
#include "MsgDialog.hpp"
#include "DeviceCore/DevConfigUtil.h"
#include "DeviceCore/DevNozzleSystem.h"
#include "DeviceCore/DevNozzleRack.h"
namespace Slic3r { namespace GUI {
@@ -95,35 +98,43 @@ enum class GridTextInputType {
class GridTextInput : public TextInput
{
public:
GridTextInput(wxWindow* parent, wxString text, wxString label, wxSize size, int col_idx, GridTextInputType type);
GridTextInput(wxWindow* parent, wxString text, wxString label, wxSize size, int col_idx, GridTextInputType type, int extruder_id = MAIN_EXTRUDER_ID);
int get_col_idx() { return m_col_idx; }
void set_col_idx(int idx) { m_col_idx = idx; }
int get_extruder_id() { return m_extruder_id; }
void set_extruder_id(int id) { m_extruder_id = id; }
GridTextInputType get_type() { return m_type; }
void set_type(GridTextInputType type) { m_type = type; }
private:
int m_col_idx;
int m_extruder_id{MAIN_EXTRUDER_ID};
GridTextInputType m_type;
};
GridTextInput::GridTextInput(wxWindow* parent, wxString text, wxString label, wxSize size, int col_idx, GridTextInputType type)
GridTextInput::GridTextInput(wxWindow* parent, wxString text, wxString label, wxSize size, int col_idx, GridTextInputType type, int extruder_id)
: TextInput(parent, text, label, "", wxDefaultPosition, size, wxTE_PROCESS_ENTER)
, m_col_idx(col_idx)
, m_extruder_id(extruder_id)
, m_type(type)
{
}
class GridComboBox : public ComboBox {
public:
GridComboBox(wxWindow* parent, wxSize size, int col_idx);
GridComboBox(wxWindow* parent, wxSize size, int col_idx, int extruder_id = MAIN_EXTRUDER_ID);
int get_col_idx() { return m_col_idx; }
void set_col_idx(int idx) { m_col_idx = idx; }
int get_extruder_id() { return m_extruder_id; }
void set_extruder_id(int id) { m_extruder_id = id; }
private:
int m_col_idx;
int m_extruder_id{MAIN_EXTRUDER_ID};
};
GridComboBox::GridComboBox(wxWindow* parent, wxSize size, int col_idx)
GridComboBox::GridComboBox(wxWindow* parent, wxSize size, int col_idx, int extruder_id)
: ComboBox(parent, wxID_ANY, "", wxDefaultPosition, size, 0, nullptr)
, m_col_idx(col_idx)
, m_extruder_id(extruder_id)
{
}
@@ -227,7 +238,7 @@ void CaliPASaveAutoPanel::sync_cali_result(const std::vector<PACalibResult>& cal
m_calib_results.clear();
for (auto& item : cali_result) {
if (item.confidence == 0)
m_calib_results[item.tray_id] = item;
m_calib_results.push_back(item);
}
m_grid_panel->DestroyChildren();
auto grid_sizer = new wxBoxSizer(wxHORIZONTAL);
@@ -281,14 +292,14 @@ void CaliPASaveAutoPanel::sync_cali_result(const std::vector<PACalibResult>& cal
wxString tray_name = get_tray_name_by_tray_id(item.tray_id);
tray_title->SetLabel(tray_name);
auto k_value = new GridTextInput(m_grid_panel, "", "", CALIBRATION_SAVE_INPUT_SIZE, item.tray_id, GridTextInputType::K);
auto n_value = new GridTextInput(m_grid_panel, "", "", CALIBRATION_SAVE_INPUT_SIZE, item.tray_id, GridTextInputType::N);
auto k_value = new GridTextInput(m_grid_panel, "", "", CALIBRATION_SAVE_INPUT_SIZE, item.tray_id, GridTextInputType::K, item.extruder_id);
auto n_value = new GridTextInput(m_grid_panel, "", "", CALIBRATION_SAVE_INPUT_SIZE, item.tray_id, GridTextInputType::N, item.extruder_id);
k_value->GetTextCtrl()->SetValidator(wxTextValidator(wxFILTER_NUMERIC));
n_value->GetTextCtrl()->SetValidator(wxTextValidator(wxFILTER_NUMERIC));
auto k_value_failed = new Label(m_grid_panel, _L("Failed"));
auto n_value_failed = new Label(m_grid_panel, _L("Failed"));
auto comboBox_tray_name = new GridComboBox(m_grid_panel, CALIBRATION_SAVE_INPUT_SIZE, item.tray_id);
auto comboBox_tray_name = new GridComboBox(m_grid_panel, CALIBRATION_SAVE_INPUT_SIZE, item.tray_id, item.extruder_id);
auto tray_name_failed = new Label(m_grid_panel, " - ");
wxArrayString selections;
static std::vector<PACalibResult> filtered_results;
@@ -395,17 +406,26 @@ void CaliPASaveAutoPanel::save_to_result_from_widgets(wxWindow* window, bool* ou
return;
//operate
// The grid can hold two rows with the same tray_id under different extruders
// (rack / dual-address configs), so results must be keyed by (tray_id, extruder_id).
auto find_result = [this](int tray_id, int extruder_id) -> PACalibResult* {
auto it = std::find_if(m_calib_results.begin(), m_calib_results.end(), [tray_id, extruder_id](const PACalibResult& r) {
return r.tray_id == tray_id && r.extruder_id == extruder_id;
});
return it != m_calib_results.end() ? &(*it) : nullptr;
};
auto input = dynamic_cast<GridTextInput*>(window);
if (input && input->IsShown()) {
int tray_id = input->get_col_idx();
int tray_id = input->get_col_idx();
int extruder_id = input->get_extruder_id();
if (input->get_type() == GridTextInputType::K) {
float k = 0.0f;
if (!CalibUtils::validate_input_k_value(input->GetTextCtrl()->GetValue(), &k)) {
*out_msg = wxString::Format(_L("Please input a valid value (K in %.1f~%.1f)"), MIN_PA_K_VALUE, MAX_PA_K_VALUE);
*out_is_valid = false;
}
else
m_calib_results[tray_id].k_value = k;
else if (auto* result = find_result(tray_id, extruder_id))
result->k_value = k;
}
else if (input->get_type() == GridTextInputType::N) {
}
@@ -413,7 +433,8 @@ void CaliPASaveAutoPanel::save_to_result_from_widgets(wxWindow* window, bool* ou
auto comboBox = dynamic_cast<GridComboBox*>(window);
if (comboBox && comboBox->IsShown()) {
int tray_id = comboBox->get_col_idx();
int tray_id = comboBox->get_col_idx();
int extruder_id = comboBox->get_extruder_id();
wxString name = comboBox->GetTextCtrl()->GetValue().ToStdString();
if (name.IsEmpty()) {
*out_msg = _L("Please enter the name you want to save to printer.");
@@ -423,7 +444,8 @@ void CaliPASaveAutoPanel::save_to_result_from_widgets(wxWindow* window, bool* ou
*out_msg = _L("The name cannot exceed 40 characters.");
*out_is_valid = false;
}
m_calib_results[tray_id].name = into_u8(name);
if (auto* result = find_result(tray_id, extruder_id))
result->name = into_u8(name);
}
auto childern = window->GetChildren();
@@ -441,52 +463,7 @@ bool CaliPASaveAutoPanel::get_result(std::vector<PACalibResult>& out_result) {
else
save_to_result_from_widgets(m_grid_panel, &is_valid, &err_msg);
if (is_valid) {
/*
std::vector<PACalibResult> to_save_result;
for (auto &result : m_calib_results) {
auto iter = std::find_if(to_save_result.begin(), to_save_result.end(), [this, &result](const PACalibResult &item) {
bool has_same_name = (item.name == result.second.name && item.filament_id == result.second.filament_id);
if (m_obj && m_obj->is_multi_extruders()) {
has_same_name &= (item.extruder_id == result.second.extruder_id && item.nozzle_volume_type == result.second.nozzle_volume_type);
}
return has_same_name;
});
if (iter != to_save_result.end()) {
MessageDialog msg_dlg(nullptr, _L("Only one of the results with the same name will be saved. Are you sure you want to overwrite the other results?"),
wxEmptyString, wxICON_WARNING | wxYES_NO);
if (msg_dlg.ShowModal() != wxID_YES) {
return false;
} else {
break;
}
}
}
for (auto &result : m_history_results) {
auto iter = std::find_if(m_history_results.begin(), m_history_results.end(), [this, &result](const PACalibResult &item) {
bool has_same_name = (item.name == result.name && item.filament_id == result.filament_id);
if (m_obj && m_obj->is_multi_extruders()) {
has_same_name &= (item.extruder_id == result.extruder_id && item.nozzle_volume_type == result.nozzle_volume_type);
}
return has_same_name;
});
if (iter != m_history_results.end()) {
MessageDialog msg_dlg(nullptr,
wxString::Format(_L("There is already a historical calibration result with the same name: %s. Are you sure you want to override the historical result?"),
result.name),
wxEmptyString, wxICON_WARNING | wxYES_NO);
if (msg_dlg.ShowModal() != wxID_YES) {
return false;
}
}
}
*/
for (auto& result : m_calib_results) {
out_result.push_back(result.second);
}
out_result = m_calib_results;
return true;
}
else {
@@ -517,11 +494,12 @@ void CaliPASaveAutoPanel::sync_cali_result_for_multi_extruder(const std::vector<
m_calib_results.clear();
for (auto &item : cali_result) {
if (item.confidence == 0) {
int tray_id = 4 * item.ams_id + item.slot_id;
PACalibResult result = item;
result.tray_id = 4 * item.ams_id + item.slot_id;
if (item.ams_id == VIRTUAL_TRAY_MAIN_ID || item.ams_id == VIRTUAL_TRAY_DEPUTY_ID) {
tray_id = item.ams_id;
result.tray_id = item.ams_id;
}
m_calib_results[tray_id] = item;
m_calib_results.push_back(result);
}
}
m_multi_extruder_grid_panel->DestroyChildren();
@@ -532,14 +510,21 @@ void CaliPASaveAutoPanel::sync_cali_result_for_multi_extruder(const std::vector<
m_multi_extruder_grid_panel->SetSizer(grid_sizer, true);
m_multi_extruder_grid_panel->Bind(wxEVT_LEFT_DOWN, [this](auto &e) { SetFocusIgnoringChildren(); });
wxStaticBoxSizer *left_sizer = new wxStaticBoxSizer(wxVERTICAL, m_multi_extruder_grid_panel, _L("Left extruder"));
wxStaticBoxSizer *right_sizer = new wxStaticBoxSizer(wxVERTICAL, m_multi_extruder_grid_panel, _L("Right extruder"));
const std::string& cwsp_pt = m_obj->printer_type;
wxStaticBoxSizer *left_sizer = new wxStaticBoxSizer(wxVERTICAL, m_multi_extruder_grid_panel, _L(DevPrinterConfigUtil::get_toolhead_display_name(cwsp_pt, DEPUTY_EXTRUDER_ID, ToolHeadComponent::Extruder, ToolHeadNameCase::SentenceCase)));
wxStaticBoxSizer *right_sizer = new wxStaticBoxSizer(wxVERTICAL, m_multi_extruder_grid_panel, _L(DevPrinterConfigUtil::get_toolhead_display_name(cwsp_pt, MAIN_EXTRUDER_ID, ToolHeadComponent::Extruder, ToolHeadNameCase::SentenceCase)));
grid_sizer->Add(left_sizer);
grid_sizer->AddSpacer(COLUMN_GAP);
grid_sizer->Add(right_sizer);
wxFlexGridSizer *left_grid_sizer = new wxFlexGridSizer(3, COLUMN_GAP, ROW_GAP);
wxFlexGridSizer *right_grid_sizer = new wxFlexGridSizer(3, COLUMN_GAP, ROW_GAP);
// The main extruder can carry a nozzle rack (H2C); non-rack printers keep the 3-column layout.
auto ns = m_obj->GetNozzleSystem();
auto rack = ns ? ns->GetNozzleRack() : nullptr;
bool has_rack = rack && rack->IsSupported();
wxFlexGridSizer *right_grid_sizer = new wxFlexGridSizer(has_rack ? 4 : 3, COLUMN_GAP, ROW_GAP);
left_sizer->Add(left_grid_sizer);
right_sizer->Add(right_grid_sizer);
@@ -567,29 +552,46 @@ void CaliPASaveAutoPanel::sync_cali_result_for_multi_extruder(const std::vector<
auto k_title = new Label(m_multi_extruder_grid_panel, _L("Factor K"), 0, CALIBRATION_SAVE_NUMBER_INPUT_SIZE);
k_title->SetFont(Label::Head_14);
right_grid_sizer->Add(k_title, 1, wxALIGN_CENTER);
if (has_rack) {
auto nozzle_title = new Label(m_multi_extruder_grid_panel, _L("Nozzle ID"), 0, CALIBRATION_SAVE_NUMBER_INPUT_SIZE);
nozzle_title->SetFont(Label::Head_14);
right_grid_sizer->Add(nozzle_title, 1, wxALIGN_CENTER);
}
}
std::vector<std::pair<int, std::string>> preset_names;
// key: (extruder_id, tray_id)
std::vector<std::pair<std::pair<int, int>, std::string>> preset_names;
int i = 1;
std::unordered_set<std::string> set;
for (auto &info : m_obj->selected_cali_preset) {
std::string default_name;
// extruder _id
// Derive extruder_id from the tray so the extruder/tray key stays consistent
// with the calibration result's extruder_id.
int extruder_id = 0;
if (info.tray_id == VIRTUAL_TRAY_MAIN_ID) {
extruder_id = 0;
} else if (info.tray_id == VIRTUAL_TRAY_DEPUTY_ID) {
extruder_id = 1;
} else {
int ams_id = info.tray_id / 4;
extruder_id = m_obj->get_extruder_id_by_ams_id(std::to_string(ams_id));
}
{
int extruder_id = 0;
if (info.tray_id == VIRTUAL_TRAY_MAIN_ID) {
extruder_id = 0;
} else if (info.tray_id == VIRTUAL_TRAY_DEPUTY_ID) {
extruder_id = 1;
} else {
int ams_id = info.tray_id / 4;
extruder_id = m_obj->get_extruder_id_by_ams_id(std::to_string(ams_id));
}
if (extruder_id == 0) {
default_name += L("Right Nozzle");
} else if (extruder_id == 1){
default_name += L("Left Nozzle");
if (extruder_id == MAIN_EXTRUDER_ID) {
default_name += DevPrinterConfigUtil::get_toolhead_display_name(cwsp_pt, MAIN_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::TitleCase);
if (has_rack) {
default_name += "_";
if (info.nozzle_pos_id == 0) {
default_name += "R";
} else if (info.nozzle_pos_id >= 0x10) {
default_name += std::to_string((info.nozzle_pos_id & 0x0f) + 1);
} else {
default_name += "N/A";
}
}
} else if (extruder_id == DEPUTY_EXTRUDER_ID) {
default_name += DevPrinterConfigUtil::get_toolhead_display_name(cwsp_pt, DEPUTY_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::TitleCase);
}
}
@@ -614,7 +616,7 @@ void CaliPASaveAutoPanel::sync_cali_result_for_multi_extruder(const std::vector<
}
}
preset_names.push_back({info.tray_id, default_name});
preset_names.push_back({{extruder_id, info.tray_id}, default_name});
}
bool left_first_add_item = true;
@@ -648,14 +650,19 @@ void CaliPASaveAutoPanel::sync_cali_result_for_multi_extruder(const std::vector<
tray_title->SetBitmap(*get_extruder_color_icon(full_filament_ams_list[item.tray_id].opt_string("filament_colour", 0u), tray_name.ToStdString(), FromDIP(20), FromDIP(20)));
tray_title->SetToolTip("");
auto k_value = new GridTextInput(m_multi_extruder_grid_panel, "", "", CALIBRATION_SAVE_NUMBER_INPUT_SIZE, item.tray_id, GridTextInputType::K);
auto n_value = new GridTextInput(m_multi_extruder_grid_panel, "", "", CALIBRATION_SAVE_NUMBER_INPUT_SIZE, item.tray_id, GridTextInputType::N);
auto k_value = new GridTextInput(m_multi_extruder_grid_panel, "", "", CALIBRATION_SAVE_NUMBER_INPUT_SIZE, item.tray_id, GridTextInputType::K, item.extruder_id);
auto n_value = new GridTextInput(m_multi_extruder_grid_panel, "", "", CALIBRATION_SAVE_NUMBER_INPUT_SIZE, item.tray_id, GridTextInputType::N, item.extruder_id);
k_value->GetTextCtrl()->SetValidator(wxTextValidator(wxFILTER_NUMERIC));
n_value->GetTextCtrl()->SetValidator(wxTextValidator(wxFILTER_NUMERIC));
auto k_value_failed = new Label(m_multi_extruder_grid_panel, _L("Failed"));
auto n_value_failed = new Label(m_multi_extruder_grid_panel, _L("Failed"));
auto comboBox_tray_name = new GridComboBox(m_multi_extruder_grid_panel, CALIBRATION_SAVE_INPUT_SIZE, item.tray_id);
auto nozzle_id_value = new Label(m_multi_extruder_grid_panel, wxEmptyString);
nozzle_id_value->SetMinSize(wxSize(FromDIP(180), FromDIP(24)));
nozzle_id_value->Wrap(-1);
auto nozzle_id_failed = new Label(m_multi_extruder_grid_panel, _L("Failed"));
auto comboBox_tray_name = new GridComboBox(m_multi_extruder_grid_panel, CALIBRATION_SAVE_INPUT_SIZE, item.tray_id, item.extruder_id);
auto tray_name_failed = new Label(m_multi_extruder_grid_panel, " - ");
wxArrayString selections;
static std::vector<PACalibResult> filtered_results;
@@ -671,22 +678,26 @@ void CaliPASaveAutoPanel::sync_cali_result_for_multi_extruder(const std::vector<
}
comboBox_tray_name->Set(selections);
auto set_edit_mode = [this, k_value, n_value, k_value_failed, n_value_failed, comboBox_tray_name, tray_name_failed](std::string str) {
auto set_edit_mode = [this, k_value, n_value, k_value_failed, n_value_failed, nozzle_id_value, nozzle_id_failed, comboBox_tray_name, tray_name_failed](std::string str, bool display_nozzle) {
if (str == "normal") {
comboBox_tray_name->Show();
tray_name_failed->Show(false);
k_value->Show();
n_value->Show();
nozzle_id_value->Show(display_nozzle);
k_value_failed->Show(false);
n_value_failed->Show(false);
nozzle_id_failed->Show(false);
}
if (str == "failed") {
comboBox_tray_name->Show(false);
tray_name_failed->Show();
k_value->Show(false);
n_value->Show(false);
nozzle_id_value->Show(false);
k_value_failed->Show();
n_value_failed->Show();
nozzle_id_failed->Show(display_nozzle);
}
// hide n value
@@ -698,15 +709,39 @@ void CaliPASaveAutoPanel::sync_cali_result_for_multi_extruder(const std::vector<
};
if (!result_failed) {
set_edit_mode("normal");
set_edit_mode("normal", has_rack && item.extruder_id == MAIN_EXTRUDER_ID);
auto k_str = wxString::Format("%.3f", item.k_value);
auto n_str = wxString::Format("%.3f", item.n_coef);
k_value->GetTextCtrl()->SetValue(k_str);
n_value->GetTextCtrl()->SetValue(n_str);
if (has_rack && item.extruder_id == MAIN_EXTRUDER_ID) {
wxString nozzle_id_str;
if (item.nozzle_pos_id == 0) {
nozzle_id_str += "R | ";
} else if (item.nozzle_pos_id >= 0x10) {
nozzle_id_str += wxString::Format("%d | ", (item.nozzle_pos_id & 0x0f) + 1);
} else {
nozzle_id_str += "N/A | ";
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << "Nozzle position id is -1 or invalid.";
}
nozzle_id_str += wxString::Format("%.1f mm ", item.nozzle_diameter);
switch (item.nozzle_volume_type) {
case NozzleVolumeType::nvtStandard: nozzle_id_str += _L("Standard Flow"); break;
case NozzleVolumeType::nvtHighFlow: nozzle_id_str += _L("High Flow"); break;
case NozzleVolumeType::nvtTPUHighFlow: nozzle_id_str += _L("TPU High Flow"); break;
default: break;
}
nozzle_id_value->SetLabel(nozzle_id_str);
}
for (auto &name : preset_names) {
if (item.tray_id == name.first) { comboBox_tray_name->SetValue(from_u8(name.second)); }
int extruder_id = name.first.first;
int tray_id = name.first.second;
if (item.tray_id == tray_id && item.extruder_id == extruder_id) {
comboBox_tray_name->SetValue(from_u8(name.second));
}
}
comboBox_tray_name->Bind(wxEVT_COMBOBOX, [this, comboBox_tray_name, k_value, n_value](auto &e) {
@@ -714,7 +749,7 @@ void CaliPASaveAutoPanel::sync_cali_result_for_multi_extruder(const std::vector<
auto history = filtered_results[selection];
});
} else {
set_edit_mode("failed");
set_edit_mode("failed", has_rack && item.extruder_id == MAIN_EXTRUDER_ID);
}
if ((m_obj->is_main_extruder_on_left() && item.extruder_id == 0)
@@ -762,6 +797,14 @@ void CaliPASaveAutoPanel::sync_cali_result_for_multi_extruder(const std::vector<
} else {
right_grid_sizer->Add(k_value_failed, 1, wxEXPAND);
}
if (has_rack) {
if (nozzle_id_value->IsShown()) {
right_grid_sizer->Add(nozzle_id_value, 1, wxEXPAND);
} else {
right_grid_sizer->Add(nozzle_id_failed, 1, wxEXPAND);
}
}
}
}
+4 -1
View File
@@ -107,7 +107,10 @@ protected:
wxPanel* m_part_failed_panel;
wxPanel* m_grid_panel{ nullptr };
wxPanel* m_multi_extruder_grid_panel{ nullptr };
std::map<int, PACalibResult> m_calib_results;// map<tray_id, PACalibResult>
// keyed by (tray_id, extruder_id): the same tray_id can appear under two
// extruders (rack / dual-address configs), so a tray_id-only map would
// silently overwrite one extruder's K/N.
std::vector<PACalibResult> m_calib_results;
std::vector<PACalibResult> m_history_results;
bool m_is_all_failed{ true };
MachineObject* m_obj{ nullptr };
+3 -3
View File
@@ -102,7 +102,7 @@ CameraPopup::CameraPopup(wxWindow *parent)
top_sizer->Add(0, 0, wxALL, 0);
}
// custom IP camera
// Orca: custom IP camera source — lets the user point Live Video at any camera URL (Orca feature; not in the reference)
m_custom_camera_input_confirm = new Button(m_panel, _L("Enable"));
m_custom_camera_input_confirm->SetBackgroundColor(wxColour(38, 166, 154));
m_custom_camera_input_confirm->SetBorderColor(wxColour(38, 166, 154));
@@ -132,7 +132,7 @@ CameraPopup::CameraPopup(wxWindow *parent)
top_sizer->Add(m_custom_camera_input_confirm, 1, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, FromDIP(5));
main_sizer->Add(top_sizer, 0, wxALL, FromDIP(10));
auto url = wxString::Format(L"https://wiki.bambulab.com/%s/software/bambu-studio/virtual-camera", L"en");
auto url = wxString(L"https://www.orcaslicer.com/wiki/"); // Orca: neutral wiki link (vendor URL removed)
auto text = _L("Show \"Live Video\" guide page.");
wxBoxSizer* link_sizer = new wxBoxSizer(wxVERTICAL);
@@ -479,7 +479,7 @@ void CameraPopup::OnLeftUp(wxMouseEvent &event)
//hyper link
auto h_rect = vcamera_guide_link->ClientToScreen(wxPoint(0, 0));
if (mouse_pos.x > h_rect.x && mouse_pos.y > h_rect.y && mouse_pos.x < (h_rect.x + vcamera_guide_link->GetSize().x) && mouse_pos.y < (h_rect.y + vcamera_guide_link->GetSize().y)) {
auto url = wxString::Format(L"https://wiki.bambulab.com/%s/software/bambu-studio/virtual-camera", L"en");
auto url = wxString(L"https://www.orcaslicer.com/wiki/"); // Orca: neutral wiki link (vendor URL removed)
wxLaunchDefaultBrowser(url);
}
}
+1 -2
View File
@@ -112,9 +112,8 @@ CloneDialog::CloneDialog(wxWindow *parent)
v_sizer->Add(bottom_sizer, 0, wxEXPAND);
this->SetSizer(v_sizer);
this->SetSizerAndFit(v_sizer);
this->Layout();
v_sizer->Fit(this);
wxGetApp().UpdateDlgDarkUI(this);
+113 -27
View File
@@ -12,6 +12,7 @@
#include "libslic3r/GCode/AdaptivePAProcessor.hpp"
#include "Plater.hpp"
#include <algorithm>
#include <sstream>
#include <wx/msgdlg.h>
@@ -70,6 +71,12 @@ void ConfigManipulation::toggle_line(const std::string& opt_key, const bool togg
cb_toggle_line(opt_key, toggle, opt_index);
}
void ConfigManipulation::set_option_label(const std::string& opt_key, const wxString& label, int opt_index)
{
if (cb_set_option_label)
cb_set_option_label(opt_key, label, opt_index);
}
void ConfigManipulation::check_nozzle_recommended_temperature_range(DynamicPrintConfig *config) {
if (is_msg_dlg_already_exist)
return;
@@ -244,6 +251,59 @@ void ConfigManipulation::check_chamber_minimal_temperature(DynamicPrintConfig* c
}
}
void ConfigManipulation::layer_height_limits(double& min_layer_height, double& max_layer_height) const
{
const DynamicPrintConfig& printer_config = GUI::wxGetApp().preset_bundle->printers.get_edited_preset().config;
const std::vector<double>& min_limits = printer_config.option<ConfigOptionFloats>("min_layer_height")->values;
const std::vector<double>& max_limits = printer_config.option<ConfigOptionFloats>("max_layer_height")->values;
min_layer_height = *std::min_element(min_limits.begin(), min_limits.end());
max_layer_height = *std::max_element(max_limits.begin(), max_limits.end());
}
bool ConfigManipulation::check_layer_height(DynamicPrintConfig* config)
{
double min_layer_height = 0., max_layer_height = 0.;
layer_height_limits(min_layer_height, max_layer_height);
const double layer_height = config->opt_float("layer_height");
if (min_layer_height > EPSILON && layer_height < EPSILON) {
const wxString msg_text = wxString::Format(_L("Layer height is too small. It will be set to the minimum (%g mm)."), min_layer_height);
MessageDialog dialog(wxGetApp().plater(), msg_text, "", wxICON_WARNING | wxOK);
dialog.SetButtonLabel(wxID_OK, _L("OK"));
is_msg_dlg_already_exist = true;
dialog.ShowModal();
is_msg_dlg_already_exist = false;
DynamicPrintConfig new_conf = *config;
new_conf.set_key_value("layer_height", new ConfigOptionFloat(min_layer_height));
apply(config, &new_conf);
return true;
}
if (max_layer_height > EPSILON && layer_height > max_layer_height + EPSILON)
return layer_height_out_of_range_dialog(config, max_layer_height);
if (min_layer_height > EPSILON && layer_height < min_layer_height - EPSILON)
return layer_height_out_of_range_dialog(config, min_layer_height);
return false;
}
bool ConfigManipulation::layer_height_out_of_range_dialog(DynamicPrintConfig* config, double clamp_to)
{
wxString msg_text = _(L("Layer height is outside the limits set in Printer Settings -> Extruder -> Layer height limits, "
"this may cause printing quality issues."));
msg_text += "\n\n" + wxString::Format(_L("Adjust it to the limit (%g mm) automatically?"), clamp_to);
MessageDialog dialog(wxGetApp().plater(), msg_text, "", wxICON_WARNING | wxYES | wxNO);
dialog.SetButtonLabel(wxID_YES, _L("Adjust"));
dialog.SetButtonLabel(wxID_NO, _L("Ignore"));
is_msg_dlg_already_exist = true;
const bool adjust = dialog.ShowModal() == wxID_YES;
if (adjust) {
DynamicPrintConfig new_conf = *config;
new_conf.set_key_value("layer_height", new ConfigOptionFloat(clamp_to));
apply(config, &new_conf);
}
is_msg_dlg_already_exist = false;
return adjust;
}
void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, const bool is_global_config, const bool is_plate_config)
{
// #ys_FIXME_to_delete
@@ -258,7 +318,6 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
// layer_height shouldn't be equal to zero
auto layer_height = config->opt_float("layer_height");
auto gpreset = GUI::wxGetApp().preset_bundle->printers.get_edited_preset();
if (layer_height < EPSILON)
{
const wxString msg_text = _(L("Layer height too small\nIt has been reset to 0.2"));
@@ -271,20 +330,6 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
is_msg_dlg_already_exist = false;
}
//BBS: limite the max layer_herght
auto max_lh = gpreset.config.opt_float("max_layer_height",0);
if (max_lh > 0.2 && layer_height > max_lh+ EPSILON)
{
const wxString msg_text = wxString::Format(L"Too large layer height.\nReset to %0.3f.", max_lh);
MessageDialog dialog(nullptr, msg_text, "", wxICON_WARNING | wxOK);
DynamicPrintConfig new_conf = *config;
is_msg_dlg_already_exist = true;
dialog.ShowModal();
new_conf.set_key_value("layer_height", new ConfigOptionFloat(max_lh));
apply(config, &new_conf);
is_msg_dlg_already_exist = false;
}
//BBS: ironing_spacing shouldn't be too small or equal to zero
if (config->opt_float("ironing_spacing") < 0.05)
{
@@ -703,16 +748,48 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
toggle_line("spiral_mode_max_xy_smoothing", has_spiral_vase && config->opt_bool("spiral_mode_smooth"));
toggle_line("spiral_starting_flow_ratio", has_spiral_vase);
toggle_line("spiral_finishing_flow_ratio", has_spiral_vase);
bool has_top_shell = config->opt_int("top_shell_layers") > 0 || (has_spiral_vase && config->opt_int("bottom_shell_layers") > 1);
bool has_top_shell_layers = config->opt_int("top_shell_layers") > 0 || (has_spiral_vase && config->opt_int("bottom_shell_layers") > 1);
bool has_top_shell = has_top_shell_layers && config->option<ConfigOptionPercent>("top_surface_density")->value > 0;
bool has_bottom_shell = config->opt_int("bottom_shell_layers") > 0;
bool has_solid_infill = has_top_shell || has_bottom_shell;
bool has_solid_infill = has_top_shell_layers || has_bottom_shell;
toggle_line("sparse_infill_smooth_factor", pattern == ipHilbertCurve);
toggle_field("top_surface_pattern", has_top_shell);
toggle_field("bottom_surface_pattern", has_bottom_shell);
toggle_field("top_surface_density", has_top_shell);
toggle_field("top_surface_density", has_top_shell_layers);
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",
"solid_infill_direction", "solid_infill_rotate_template", "internal_solid_infill_pattern", "internal_solid_filament_id", "top_surface_filament_id", "bottom_surface_filament_id",
@@ -721,7 +798,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
for (auto el : { "sparse_infill_speed", "bridge_speed", "internal_bridge_speed"})
toggle_field(el, have_infill || has_solid_infill, variant_index);
toggle_field("top_shell_thickness", ! has_spiral_vase && has_top_shell);
toggle_field("top_shell_thickness", ! has_spiral_vase && has_top_shell_layers);
toggle_field("bottom_shell_thickness", ! has_spiral_vase && has_bottom_shell);
// Gap fill is newly allowed in between perimeter lines even for empty infill (see GH #1476).
@@ -776,14 +853,19 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
toggle_field("outer_wall_filament_id", have_perimeters || have_brim);
toggle_field("inner_wall_filament_id", have_perimeters || have_brim);
bool have_brim_ear = (config->opt_enum<BrimType>("brim_type") == btEar);
const BrimType brim_type = config->opt_enum<BrimType>("brim_type");
const bool have_auto_brim_ear = brim_type == btEar;
const bool have_painted_brim_ear = brim_type == btPainted;
set_option_label("brim_width", have_auto_brim_ear ? _L("Brim ear radius") : _L("Brim width"));
const auto brim_width = config->opt_float("brim_width");
// disable brim_ears_max_angle and brim_ears_detection_length if brim_width is 0
// Automatic brim ear settings require a non-zero brim width.
toggle_field("brim_ears_max_angle", brim_width > 0.0f);
toggle_field("brim_ears_detection_length", brim_width > 0.0f);
// hide brim_ears_max_angle and brim_ears_detection_length if brim_ear is not selected
toggle_line("brim_ears_max_angle", have_brim_ear);
toggle_line("brim_ears_detection_length", have_brim_ear);
// Painted ears carry their own radius and do not depend on brim_width.
toggle_field("brim_ears_outer_only", have_painted_brim_ear || brim_width > 0.0f);
toggle_line("brim_ears_max_angle", have_auto_brim_ear);
toggle_line("brim_ears_detection_length", have_auto_brim_ear);
toggle_line("brim_ears_outer_only", have_auto_brim_ear || have_painted_brim_ear);
// Hide Elephant foot compensation layers if elefant_foot_compensation is not enabled
toggle_line("elefant_foot_compensation_layers", config->opt_float("elefant_foot_compensation") > 0 || config->option<ConfigOptionPercent>("elefant_foot_layers_density")->get_abs_value(1.0f) < 1.0f);
@@ -916,7 +998,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);
@@ -978,9 +1060,13 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
toggle_line("make_overhang_printable_angle", have_make_overhang_printable);
toggle_line("make_overhang_printable_hole_size", have_make_overhang_printable);
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
// Orca: the one-wall options act on top/bottom surfaces, which exist only with a shell. An unfilled surface
// (0% surface density) is still a surface, so these are gated on the layer counts alone.
toggle_line("only_one_wall_first_layer", has_bottom_shell);
toggle_line("only_one_wall_top", has_top_shell_layers);
toggle_line("min_width_top_surface", (has_top_shell_layers && 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");
+9 -1
View File
@@ -29,6 +29,7 @@ class ConfigManipulation
std::function<void()> load_config = nullptr;
std::function<void (const std::string&, bool toggle, int opt_index)> cb_toggle_field = nullptr;
std::function<void(const std::string &, bool toggle, int opt_index)> cb_toggle_line = nullptr;
std::function<void(const std::string &, const wxString &, int opt_index)> cb_set_option_label = nullptr;
// callback to propagation of changed value, if needed
std::function<void(const std::string&, const boost::any&)> cb_value_change = nullptr;
//BBS: change local config to const DynamicPrintConfig
@@ -45,10 +46,12 @@ public:
std::function<void(const std::string&, const boost::any&)> cb_value_change,
//BBS: change local config to DynamicPrintConfig
const DynamicPrintConfig* local_config = nullptr,
wxWindow* msg_dlg_parent = nullptr) :
wxWindow* msg_dlg_parent = nullptr,
std::function<void(const std::string &, const wxString &, int opt_index)> cb_set_option_label = nullptr) :
load_config(load_config),
cb_toggle_field(cb_toggle_field),
cb_toggle_line(cb_toggle_line),
cb_set_option_label(cb_set_option_label),
cb_value_change(cb_value_change),
m_msg_dlg_parent(msg_dlg_parent),
local_config(local_config) {}
@@ -58,6 +61,7 @@ public:
load_config = nullptr;
cb_toggle_field = nullptr;
cb_toggle_line = nullptr;
cb_set_option_label = nullptr;
cb_value_change = nullptr;
}
@@ -67,6 +71,7 @@ public:
t_config_option_keys const &applying_keys() const;
void toggle_field(const std::string& field_key, const bool toggle, int opt_index = -1);
void toggle_line(const std::string& field_key, const bool toggle, int opt_index = -1);
void set_option_label(const std::string& field_key, const wxString& label, int opt_index = -1);
// FFF print
void update_print_fff_config(DynamicPrintConfig* config, const bool is_global_config = false, const bool is_plate_config = false);
@@ -81,6 +86,9 @@ public:
void check_filament_max_volumetric_speed(DynamicPrintConfig *config);
void check_chamber_temperature(DynamicPrintConfig* config);
void check_chamber_minimal_temperature(DynamicPrintConfig* config);
bool check_layer_height(DynamicPrintConfig* config);
bool layer_height_out_of_range_dialog(DynamicPrintConfig* config, double clamp_to);
void layer_height_limits(double& min_layer_height, double& max_layer_height) const;
void set_is_BBL_Printer(bool is_bbl_printer) { is_BBL_Printer = is_bbl_printer; };
bool get_is_BBL_Printer() { return is_BBL_Printer; };
// SLA print
+2 -2
View File
@@ -1440,7 +1440,7 @@ PageTemperatures::PageTemperatures(ConfigWizard *parent)
spin_bed->SetValue(default_bed != nullptr && default_bed->size() > 0 ? default_bed->get_at(0) : 0);
append_text(_L("Enter the nozzle_temperature needed for extruding your filament."));
append_text(_L("A rule of thumb is 160 to 230 °C for PLA, and 215 to 250 °C for ABS."));
append_text(_L("A rule of thumb is 160 to 230℃ for PLA, and 215 to 250℃ for ABS."));
#endif
auto *sizer_extr = new wxFlexGridSizer(3, 5, 5);
@@ -1455,7 +1455,7 @@ PageTemperatures::PageTemperatures(ConfigWizard *parent)
append_spacer(VERTICAL_SPACING);
append_text(_L("Enter the bed temperature needed for getting your filament to stick to your heated bed."));
append_text(_L("A rule of thumb is 60 °C for PLA and 110 °C for ABS. Leave zero if you have no heated bed."));
append_text(_L("A rule of thumb is 60℃ for PLA and 110℃ for ABS. Leave zero if you have no heated bed."));
auto *sizer_bed = new wxFlexGridSizer(3, 5, 5);
auto *text_bed = new wxStaticText(this, wxID_ANY, _L("Bed Temperature:"));
+1 -2
View File
@@ -80,9 +80,8 @@ ConnectPrinterDialog::ConnectPrinterDialog(wxWindow *parent, wxWindowID id, cons
main_sizer->Add(sizer_top);
this->SetSizer(main_sizer);
this->SetSizerAndFit(main_sizer);
this->Layout();
this->Fit();
CentreOnParent();
m_textCtrl_code->Bind(wxEVT_TEXT, &ConnectPrinterDialog::on_input_enter, this);
+22
View File
@@ -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,30 @@ 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
GUI/DeviceCore/DevAxis.h
GUI/DeviceCore/DevAxis.cpp
GUI/DeviceCore/DevAxisCtrl.cpp
GUI/DeviceCore/DevCalib.h
GUI/DeviceCore/DevCalib.cpp
GUI/DeviceCore/DevChamber.h
GUI/DeviceCore/DevChamber.cpp
GUI/DeviceCore/DevChamberCtrl.cpp
GUI/DeviceCore/DevStatus.h
GUI/DeviceCore/DevStatus.cpp
GUI/DeviceCore/DevUpgrade.h
GUI/DeviceCore/DevUpgrade.cpp
GUI/DeviceCore/DevUpgradeCtrl.cpp
)
set(SLIC3R_GUI_SOURCES ${SLIC3R_GUI_SOURCES} PARENT_SCOPE)
+25
View File
@@ -0,0 +1,25 @@
#include "DevAxis.h"
#include "DevUtil.h"
#include "slic3r/GUI/DeviceManager.hpp"
namespace Slic3r
{
void DevAxis::ParseAxis(const json& print_json)
{
DevJsonValParser::ParseVal(print_json, "home_flag", m_home_flag);
if (print_json.contains("fun")) {
const std::string& fun = print_json["fun"].get<std::string>();
m_is_support_mqtt_homing = DevUtil::get_flag_bits(fun, 32);
m_is_support_mqtt_axis_ctrl = DevUtil::get_flag_bits(fun, 38);
}
}
bool DevAxis::IsArchCoreXY() const
{
return m_owner->get_printer_arch() == PrinterArch::ARCH_CORE_XY;
}
} // namespace Slic3r
+39
View File
@@ -0,0 +1,39 @@
#pragma once
#include <nlohmann/json.hpp>
#include "slic3r/Utils/json_diff.hpp"
namespace Slic3r {
class MachineObject;
class DevAxis
{
public:
static std::shared_ptr<DevAxis> Create(MachineObject *obj) { return std::shared_ptr<DevAxis>( new DevAxis(obj)); }
virtual ~DevAxis() = default;
public:
bool IsAxisAtHomeX() const { return m_home_flag == 0 ? true : (m_home_flag & 1) == 1; }
bool IsAxisAtHomeY() const { return m_home_flag == 0 ? true : ((m_home_flag >> 1) & 1) == 1; }
bool IsAxisAtHomeZ() const { return m_home_flag == 0 ? true : ((m_home_flag >> 2) & 1) == 1; }
bool IsArchCoreXY() const;
public:
void ParseAxis(const json &print_json);
int Ctrl_GoHome();
int Ctrl_Axis(std::string axis, double unit = 1.0f, double input_val = 1.0f, int speed = 3000); // xyz e
protected:
DevAxis(MachineObject *obj) noexcept : m_owner(obj) {}
private:
MachineObject *m_owner = nullptr;
int m_home_flag = 0; // bit0:X, bit1:Y, bit2:Z
bool m_is_support_mqtt_axis_ctrl = false;
bool m_is_support_mqtt_homing = false;
};
} // namespace Slic3r
+60
View File
@@ -0,0 +1,60 @@
#include "DevAxis.h"
#include "slic3r/GUI/DeviceManager.hpp"
#include "slic3r/Utils/NetworkAgent.hpp"
namespace Slic3r
{
int DevAxis::Ctrl_GoHome()
{
if (m_is_support_mqtt_homing) {
json j;
j["print"]["command"] = "back_to_center";
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
return m_owner->publish_json(j);
}
// gcode command
return m_owner->is_in_printing() ? m_owner->publish_gcode("G28 X\n") : m_owner->publish_gcode("G28 \n");
}
int DevAxis::Ctrl_Axis(std::string axis, double unit, double input_val, int speed)
{
if (m_is_support_mqtt_axis_ctrl) {
int dir = input_val > 0 ? 1 : -1;
if (!IsArchCoreXY()) {
if (axis.compare("Y") == 0 || axis.compare("Z") == 0) {
dir = -dir;
}
}
json j;
j["print"]["command"] = "xyz_ctrl";
j["print"]["axis"] = axis;
j["print"]["dir"] = dir;
j["print"]["mode"] = (std::abs(input_val) >= 10) ? 1 : 0;
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
return m_owner->publish_json(j);
}
double value = input_val;
if (!IsArchCoreXY()) {
if (axis.compare("Y") == 0 || axis.compare("Z") == 0) {
value = -1.0 * input_val;
}
}
char cmd[256];
if (axis.compare("X") == 0 || axis.compare("Y") == 0 || axis.compare("Z") == 0) {
sprintf(cmd, "M211 S \nM211 X1 Y1 Z1\nM1002 push_ref_mode\nG91 \nG1 %s%0.1f F%d\nM1002 pop_ref_mode\nM211 R\n", axis.c_str(), value * unit, speed);
} else if (axis.compare("E") == 0) {
sprintf(cmd, "M83 \nG0 %s%0.1f F%d\n", axis.c_str(), value * unit, speed);
} else {
return -1;
}
// Orca: strip analytics telemetry (matches MachineObject::command_axis_control)
return m_owner->publish_gcode(cmd);
}
} // namespace Slic3r
+337
View File
@@ -0,0 +1,337 @@
#include <boost/log/trivial.hpp>
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/UserNotification.hpp"
#include "libslic3r/PrintConfig.hpp"
#include <wx/dir.h>
#include "fast_float/fast_float.h"
#include "DevCalib.h"
#include "DevDefs.h"
#include "DevFilaSystem.h"
#include "DevConfig.h"
namespace Slic3r {
static float string_to_float(const std::string &str_value)
{
float value = 0.0;
fast_float::from_chars(str_value.c_str(), str_value.c_str() + str_value.size(), value);
return value;
}
static NozzleVolumeType convert_to_nozzle_type(const std::string &str)
{
if (str.size() < 8) {
assert(false && "invalid nozzle info");
return NozzleVolumeType::nvtStandard;
}
if (str[1] == 'S')
return NozzleVolumeType::nvtStandard;
else if (str[1] == 'H')
return NozzleVolumeType::nvtHighFlow;
else if (str[1] == 'U')
return NozzleVolumeType::nvtTPUHighFlow;
// Orca: no nvtE3DHighFlow in Orca's NozzleVolumeType; map 'B' to Standard
else
return NozzleVolumeType::nvtStandard;
}
static float get_number_flexible(const json& j, const std::string& key, float def = 0.0f)
{
if (!j.contains(key)) return def;
const auto& v = j[key];
if (v.is_number_float()) return v.get<float>();
if (v.is_number_integer()) return static_cast<float>(v.get<int>());
if (v.is_string()) return string_to_float(v.get<std::string>());
return def;
}
void from_json(const json& j, PACalibResult& cali) {
cali.extruder_id = j.value("extruder_id", 0);
cali.nozzle_volume_type = convert_to_nozzle_type(j.value("nozzle_id", "HS00-0.4"));
cali.tray_id = j.value("tray_id",0);
cali.ams_id = j.value("ams_id",0);
cali.slot_id = j.value("slot_id",0);
cali.cali_idx = j.value("cali_idx",-1);
cali.nozzle_pos_id = j.value("nozzle_pos",-1);
cali.nozzle_diameter = get_number_flexible(j, "nozzle_diameter", 0.4f);
cali.nozzle_sn = j.value("nozzle_sn","");
cali.filament_id = j.value("filament_id","");
cali.setting_id = j.value("setting_id","");
cali.name = j.value("name","");
cali.k_value = get_number_flexible(j, "k_value", 0.0f);
cali.n_coef = get_number_flexible(j, "n_coef", 0.0f);
cali.confidence = j.value("confidence", 0);
}
void from_json(const json& j, FlowRatioCalibResult& cali)
{
cali.tray_id = j.value("tray_id", 0);
cali.nozzle_diameter = string_to_float(j.value("nozzle_diameter", ""));
cali.filament_id = j.value("filament_id", "");
cali.setting_id = j.value("setting_id", "");
cali.flow_ratio = string_to_float(j.value("flow_ratio", ""));
cali.confidence = j.value("confidence", 0);
}
void DevCalib::ParseCalibVersion(const json& j, DevCalib* system)
{
if(system) system->m_calib_version = j.value("cali_version", -1);
}
bool DevCalib::IsVersionExpired() const
{
if (m_last_calib_version.has_value())
return m_last_calib_version.value() != m_calib_version;
else
return true;
}
void DevCalib::ParseSupportNewAutoCalib(int flag, DevCalib* system)
{
if(system) system->m_support_new_auto_cali = flag;
}
void DevCalib::RequestPAResult()
{
m_pa_results_status = CalibStatus::REQUEST;
}
void DevCalib::ResetPAResult()
{
m_pa_calib_results.clear();
m_pa_results_status = CalibStatus::IDLE;
}
int DevCalib::RequestPAHistory(const PACalibExtruderInfo &calib_info)
{
m_pa_calib_tab.clear();
m_pa_table_status = CalibStatus::REQUEST;
return GetOwner()->command_get_pa_calibration_tab(calib_info);
}
void DevCalib::ResetPAHistory()
{
m_pa_calib_tab.clear();
m_pa_table_status = CalibStatus::IDLE;
}
void DevCalib::RequestFlowRateResult()
{
m_flow_results_status = CalibStatus::REQUEST;
}
void DevCalib::ResetFlowRateResult()
{
m_flow_ratio_results.clear();
m_flow_results_status = CalibStatus::IDLE;
}
void calib_fail_message(MachineObject* obj, std::string cali_mode, std::string reason){
wxString info;
if (reason == "invalid nozzle_diameter" || reason == "nozzle_diameter is not supported") {
info = _L("This calibration does not support the currently selected nozzle diameter");
} else if (reason == "invalid handle_flowrate_cali param") {
info = _L("Current flowrate cali param is invalid");
} else if (reason == "nozzle_diameter is not matched") {
info = _L("Selected diameter and machine diameter do not match");
} else if (reason == "generate auto filament cali gcode failure") {
info = _L("Failed to generate cali gcode");
} else {
info = wxString(reason);
}
GUI::wxGetApp().push_notification(obj, info, _L("Calibration error"), UserNotificationStyle::UNS_WARNING_CONFIRM);
BOOST_LOG_TRIVIAL(info) << cali_mode << " result fail, reason = " << reason;
}
void DevCalib::ExtrusionCalibSetParse(const json & jj){
int tray_id = jj.value("tray_id", -1);
auto tray_ams_slot_map = GetOwner()->GetFilaSystem()->GetTrayIndexMap();
int ams_id = tray_ams_slot_map.find(tray_id) != tray_ams_slot_map.end() ? tray_ams_slot_map[tray_id].first : -1;
int slot_id = tray_ams_slot_map.find(tray_id) != tray_ams_slot_map.end() ? tray_ams_slot_map[tray_id].second : -1;
if(tray_id == VIRTUAL_TRAY_MAIN_ID) {
GetOwner()->vt_slot[MAIN_EXTRUDER_ID].k = jj.value("k_value", GetOwner()->vt_slot[MAIN_EXTRUDER_ID].k);
GetOwner()->vt_slot[MAIN_EXTRUDER_ID].n = jj.value("n_value", GetOwner()->vt_slot[MAIN_EXTRUDER_ID].n);
}else{
auto tray_item = GetOwner()->GetFilaSystem()->GetAmsTray(std::to_string(ams_id), std::to_string(slot_id));
if (tray_item) {
tray_item->k = jj.value("k_value", tray_item->k);
tray_item->n = jj.value("n_coef", tray_item->n);
}
}
GetOwner()->extrusion_cali_set_tray_id = tray_id;
GetOwner()->extrusion_cali_set_hold_start = std::chrono::system_clock::now();
}
/* calib select ack parse */
void DevCalib::ExtrusionCalibSelectParse(const json &jj){
try{
int tray_id = jj.value("tray_id", -1);
auto tray_ams_slot_map = GetOwner()->GetFilaSystem()->GetTrayIndexMap();
int default_ams_id = tray_ams_slot_map.find(tray_id) != tray_ams_slot_map.end() ? tray_ams_slot_map[tray_id].first : -1;
int default_slot_id = tray_ams_slot_map.find(tray_id) != tray_ams_slot_map.end() ? tray_ams_slot_map[tray_id].second : -1;
int ams_id = jj.value("ams_id", default_ams_id);
int slot_id = jj.value("slot_id", default_slot_id);
BOOST_LOG_TRIVIAL(trace) << "extrusion_cali_sel: illegal ams_id = " << ams_id << "slot_id = " << slot_id;
std::vector<DevAmsTray> &vt_slot = GetOwner()->vt_slot;
if (ams_id == VIRTUAL_TRAY_MAIN_ID && vt_slot.size() > 0) {
vt_slot[MAIN_EXTRUDER_ID].cali_idx = jj.value("cali_idx", vt_slot[MAIN_EXTRUDER_ID].cali_idx);
vt_slot[MAIN_EXTRUDER_ID].set_hold_count();
} else if (ams_id == VIRTUAL_TRAY_DEPUTY_ID && vt_slot.size() > 1) {
vt_slot[DEPUTY_EXTRUDER_ID].cali_idx = jj.value("cali_idx", vt_slot[DEPUTY_EXTRUDER_ID].cali_idx);
vt_slot[DEPUTY_EXTRUDER_ID].set_hold_count();
} else {
auto tray_item = GetOwner()->GetFilaSystem()->GetAmsTray(std::to_string(ams_id), std::to_string(slot_id));
if (tray_item) {
tray_item->cali_idx = jj.value("cali_idx", tray_item->cali_idx);
tray_item->set_hold_count();
}
}
} catch(...){
}
}
void DevCalib::ExtrusionCalibGetTableParse(const json &jj){
if (GetPAHistoryStatus() == CalibStatus::REQUEST ) {
m_pa_table_status = CalibStatus::WAITING;
/* request success */
if (!(jj.contains("result") && jj.contains("reason") && jj["result"].get<std::string>() == "fail")) {
SyncCalibVersion();
m_pa_table_status = CalibStatus::FINISHED;
}
try{
json filaments_json;
if(jj.contains("filaments"))
{
/* fill item->nozzle_diameter with command->nozzle_diameter */
filaments_json = jj["filaments"];
for (auto &f : filaments_json) {
if (!f.contains("nozzle_diameter") && jj.contains("nozzle_diameter")) {
f["nozzle_diameter"] = jj["nozzle_diameter"];
}
}
}
m_pa_calib_tab = filaments_json.get<std::vector<PACalibResult>>();
/* filter invalid pa_calib_tab */
m_pa_calib_tab.erase(std::remove_if(m_pa_calib_tab.begin(), m_pa_calib_tab.end(), [](auto &res) { return res.k_value < 0.0f || res.k_value > 10.0f; }), m_pa_calib_tab.end());
if (m_pa_calib_tab.empty()) { BOOST_LOG_TRIVIAL(info) << "empty pa calib history"; }
}catch(...){
m_pa_calib_tab.clear();
BOOST_LOG_TRIVIAL(error) << "pa calib history missing fields, current json:\n "<< jj.dump();
}
// notify cali history to update
}
}
void DevCalib::ExtrusionCalibGetResultParse(const json &jj)
{
m_pa_results_status = CalibStatus::WAITING;
if (!(jj.contains("result") && jj.contains("reason") && jj["result"].get<std::string>() == "fail" && jj.contains("err_code"))) {
m_pa_results_status = CalibStatus::FINISHED;
}
try {
json filaments_json;
if(jj.contains("filaments"))
{
/* fill item->nozzle_diameter with command->nozzle_diameter */
filaments_json = jj["filaments"];
for (auto &f : filaments_json) {
if (!f.contains("nozzle_diameter") && jj.contains("nozzle_diameter") ) {
f["nozzle_diameter"] = jj["nozzle_diameter"];
}
if (IsSupportNewAutoCali()) {
auto ams_id = f.value("ams_id", 0);
auto slot_id = f.value("slot_id", 0);
if(f.contains("tray_id")){
// Orca: Orca's DevFilaSystem has no GetTrayIdByAmsSlotId; mirror its
// semantics via a reverse lookup over GetTrayIndexMap() (correct for
// non-4-slot AMS layouts, unlike a fixed ams_id*4+slot_id formula).
int mapped_tray_id = -1;
const auto tray_ams_slot_map = GetOwner()->GetFilaSystem()->GetTrayIndexMap();
for (const auto& item : tray_ams_slot_map) {
if (item.second.first == ams_id && item.second.second == slot_id) {
mapped_tray_id = item.first;
break;
}
}
f["tray_id"] = mapped_tray_id;
}
}
}
}
m_pa_calib_results = filaments_json.get<std::vector<PACalibResult>>();
m_pa_calib_results.erase(std::remove_if(m_pa_calib_results.begin(), m_pa_calib_results.end(), [](auto &res) { return res.k_value < 0.0f || res.k_value > 10.0f; }), m_pa_calib_results.end());
if (m_pa_calib_results.empty()) { BOOST_LOG_TRIVIAL(info) << "empty pa calib result"; }
} catch (...) {
m_pa_calib_results.clear();
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "pa calibration results missing fileds, current json: \n"<<jj.dump();
}
}
void DevCalib::FlowrateGetResultParse(const json &jj){
m_flow_results_status = CalibStatus::FINISHED;
m_flow_ratio_results.clear();
if(!jj.contains("filaments")) return;
try {
m_flow_ratio_results = jj["filaments"].get<std::vector<FlowRatioCalibResult>>();
} catch (...) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "flow ratio calibration results missing fileds, current json:\n"<<jj.dump();
}
}
void DevCalib::ParseV1_0(const json &jj, DevCalib *system, bool key_field_only)
{
if(!jj.contains("command")) return;
if (jj["command"].get<std::string>() == "extrusion_cali" || jj["command"].get<std::string>() == "flowrate_cali") {
if (jj.contains("result")) {
if (jj["result"].get<std::string>() == "success") {
} else if (jj["result"].get<std::string>() == "fail") {
std::string cali_mode = jj["command"].get<std::string>();
std::string reason = jj["reason"].get<std::string>();
calib_fail_message(system->GetOwner(), cali_mode, reason);
}
}
} else if (jj["command"].get<std::string>() == "extrusion_cali_set") {
system->ExtrusionCalibSetParse(jj);
} else if (jj["command"].get<std::string>() == "extrusion_cali_sel") {
system->ExtrusionCalibSelectParse(jj);
} else if (jj["command"].get<std::string>() == "extrusion_cali_get") {
system->ExtrusionCalibGetTableParse(jj);
} else if (jj["command"].get<std::string>() == "extrusion_cali_get_result") {
system->ExtrusionCalibGetResultParse(jj);
} else if (jj["command"].get<std::string>() == "flowrate_get_result" && !key_field_only) {
system->FlowrateGetResultParse(jj);
}
}
} // namespace Slic3r
+127
View File
@@ -0,0 +1,127 @@
#pragma once
#include <nlohmann/json.hpp>
#include "slic3r/Utils/json_diff.hpp"
#include "DevDefs.h"
#include "libslic3r/calib.hpp" // Orca: lowercase filename (Linux case-sensitive)
namespace Slic3r {
class MachineObject;
enum class CalibStatus{
IDLE = 0,
REQUEST,
WAITING,
FINISHED,
};
enum class ManualPaCaliMethod {
PA_LINE = 0,
PA_PATTERN,
};
class DevCalib
{
protected:
bool m_support_new_auto_cali{false};
int m_calib_version {-1};
std::optional<int> m_last_calib_version;
public:
DevCalib(MachineObject *obj) : m_owner(obj){};
MachineObject* GetOwner() const {return m_owner; };
void RequestPAResult();
CalibStatus GetPAResultStatus() const {return m_pa_results_status;}
bool IsPAResultReady() const { return m_pa_results_status == CalibStatus::FINISHED;}
void ResetPAResult();
/* calib history */
int RequestPAHistory(const PACalibExtruderInfo &calib_info);
CalibStatus GetPAHistoryStatus() const {return m_pa_table_status;}
bool IsPAHistoryReady() const { return m_pa_table_status == CalibStatus::FINISHED;}
void ResetPAHistory();
void RequestFlowRateResult();
CalibStatus GetFlowRateResultStatus() const {return m_flow_results_status;}
bool IsFlowRateReady() const { return m_flow_results_status == CalibStatus::FINISHED;}
void ResetFlowRateResult();
int GetCalibVersion() const {return m_calib_version;}
void SyncCalibVersion() { if (IsVersionInited()) { m_last_calib_version = m_calib_version; } }
void ResetCalibVersion() {m_last_calib_version.reset();}
bool IsVersionExpired() const;
bool IsVersionInited() const { return m_calib_version > -1;}
bool IsSupportNewAutoCali() const {return m_support_new_auto_cali;}
public:
void SetStashCalibFinished(bool finished) {m_calib_finished = finished;}
bool GetStashCalibFinished() { return m_calib_finished;}
void SetStashFlowRatio(float ratio) { m_flow_ratio = ratio;}
float GetStashFlowRatio() const {return m_flow_ratio;}
FlowRatioCalibrationType GetFlowRatioCalibType() {return m_flow_ratio_calibration_type;}
void SetFlowRatioCalibType(const FlowRatioCalibrationType &type) { m_flow_ratio_calibration_type = type; }
ManualPaCaliMethod GetManualPaCalibMethod() {return m_manual_pa_cali_method;}
void SetManualPaCalibMethod(const ManualPaCaliMethod& method) { m_manual_pa_cali_method = method;}
NozzleDiameterType GetSelectedNozzleDiameter() {return m_selected_nozzle_diameter;}
void SetSelectedNozzleDiameter(const NozzleDiameterType& diameter) { m_selected_nozzle_diameter = diameter;}
std::vector<CaliPresetInfo> GetSelectedCalibPreset() {return m_selected_calib_preset;}
void ResetSelectedCalibPreset() { m_selected_calib_preset.clear();}
void SetSelectedCalibPreset(const std::vector<CaliPresetInfo>& preset) { m_selected_calib_preset = preset;}
std::vector<PACalibResult> GetPAHistory() const {return m_pa_calib_tab; }
std::vector<PACalibResult> GetPAResult() const {return m_pa_calib_results; }
std::vector<FlowRatioCalibResult> GetFlowRatioResult() const {return m_flow_ratio_results; }
protected:
void ExtrusionCalibSetParse(const json &jj);
void ExtrusionCalibSelectParse(const json &jj);
void ExtrusionCalibGetTableParse(const json &jj);
void ExtrusionCalibGetResultParse(const json &jj);
void FlowrateGetResultParse(const json &jj);
private:
MachineObject* m_owner{nullptr};
std::vector<PACalibResult> m_pa_calib_tab;
std::vector<PACalibResult> m_pa_calib_results;
std::vector<FlowRatioCalibResult> m_flow_ratio_results;
CalibStatus m_pa_results_status{CalibStatus::IDLE};
CalibStatus m_pa_table_status{CalibStatus::IDLE};
CalibStatus m_flow_results_status{CalibStatus::IDLE};
FlowRatioCalibrationType m_flow_ratio_calibration_type{FlowRatioCalibrationType::COMPLETE_CALIBRATION};
ManualPaCaliMethod m_manual_pa_cali_method{ManualPaCaliMethod::PA_LINE};
// calibration page selected info
// 1: record when start calibration in preset page
// 2: reset when start calibration in start page
// 3: save tray_id, filament_id, setting_id, and name, nozzle_dia
// std::vector<CaliPresetInfo> selected_cali_preset;
NozzleDiameterType m_selected_nozzle_diameter{NozzleDiameterType::NONE_DIAMETER_TYPE};
std::vector<CaliPresetInfo> m_selected_calib_preset;
// stash calibrating info
float m_flow_ratio { 0.0 };
bool m_calib_finished{false};
public:
static void ParseCalibVersion(const json& j, DevCalib* system);
static void ParseSupportNewAutoCalib(int flag, DevCalib* system);
static void ParseV1_0(const json& print_json, DevCalib* system, bool key_field_only);
};
} // namespace Slic3r
+50
View File
@@ -0,0 +1,50 @@
#include "DevChamber.h"
#include "DevConfig.h"
#include "DevUtil.h"
#include "slic3r/GUI/DeviceManager.hpp"
namespace Slic3r {
bool DevChamber::HasChamber() const { return m_owner->GetConfig()->HasChamber(); }
bool DevChamber::SupportChamberTempDisplay() const { return m_owner->GetConfig()->SupportChamberTempDisplay();}
bool DevChamber::SupportChamberEdit() const { return m_owner->GetConfig()->SupportChamberEdit(); }
int DevChamber::GetChamberTempEditMin() const { return m_owner->GetConfig()->GetChamberTempEditMin(); }
int DevChamber::GetChamberTempEditMax() const { return m_owner->GetConfig()->GetChamberTempEditMax(); }
int DevChamber::GetChamberTempSwitchHeat() const { return m_owner->GetConfig()->GetChamberTempSwitchHeat(); }
void DevChamber::ParseChamber(const json &print_json)
{
ParseChamberV1_0(print_json);
ParseChamberV2_0(print_json);
}
void DevChamber::ParseChamberV1_0(const json &print_json)
{
DevJsonValParser::ParseVal(print_json, "chamber_temper", m_temp);
DevJsonValParser::ParseVal(print_json, "ctt", m_temp_target);
}
void DevChamber::ParseChamberV2_0(const json &print_json)
{
if (print_json.contains("device")) {
const json &device_jj = print_json["device"];
if (device_jj.contains("ctc")) {
const json &ctc = device_jj["ctc"];
int state = DevUtil::get_flag_bits(ctc["state"].get<int>(), 0, 4);
if (ctc.contains("info")) {
const json &info = ctc["info"];
m_temp = DevUtil::get_flag_bits(info["temp"].get<int>(), 0, 16);
m_temp_target = DevUtil::get_flag_bits(info["temp"].get<int>(), 16, 16);
}
}
}
}
} // namespace Slic3r
+44
View File
@@ -0,0 +1,44 @@
#pragma once
#include <nlohmann/json.hpp>
#include "slic3r/Utils/json_diff.hpp"
namespace Slic3r {
class MachineObject;
class DevChamber
{
public:
static std::shared_ptr<DevChamber> Create(MachineObject *obj) { return std::shared_ptr<DevChamber>( new DevChamber(obj)); }
public: // getter
bool HasChamber() const;
bool SupportChamberTempDisplay() const;
bool SupportChamberEdit() const;
int GetChamberTempEditMin() const;
int GetChamberTempEditMax() const;
int GetChamberTempSwitchHeat() const;
float GetChamberTemp() const { return m_temp; };
float GetChamberTempTarget() const { return m_temp_target; };
public:
// setter
void ParseChamber(const json &print_json);
void ParseChamberV1_0(const json& print_json);
void ParseChamberV2_0(const json& print_json);
// control
int CtrlSetChamberTemp(int temp);
protected:
DevChamber(MachineObject *obj) : m_owner(obj) {}
private:
float m_temp = 0.0f;
float m_temp_target = 0.0f;
MachineObject* m_owner = nullptr;
};
} // namespace Slic3r
@@ -0,0 +1,16 @@
#include "DevChamber.h"
#include "slic3r/GUI/DeviceManager.hpp"
namespace Slic3r {
int DevChamber::CtrlSetChamberTemp(int temp)
{
json j;
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
j["print"]["command"] = "set_ctt";
j["print"]["ctt_val"] = temp;
return m_owner->publish_json(j, 1);
}
} // namespace Slic3r
+14 -1
View File
@@ -8,17 +8,30 @@ using namespace nlohmann;
namespace Slic3r
{
bool DevConfig::SupportChamberTempDisplay() const
{
if (!m_support_chamber_temp_display.has_value()) {
return m_has_chamber;
}
return m_support_chamber_temp_display.value();
}
void DevConfig::ParseConfig(const json& print_json)
{
ParseChamberConfig(print_json);
ParsePrintOptionsConfig(print_json);
ParseCalibrationConfig(print_json);
}
void DevConfig::ParseChamberConfig(const json& print_json)
{
DevJsonValParser::ParseVal(print_json, "support_chamber", m_has_chamber);
if (print_json.contains("support_chamber_temp_display")) {
m_support_chamber_temp_display = DevJsonValParser::GetVal<bool>(print_json, "support_chamber_temp_display");
}
DevJsonValParser::ParseVal(print_json, "support_chamber_temp_edit", m_support_chamber_edit);
if (m_support_chamber_edit)
{
+4 -1
View File
@@ -3,8 +3,9 @@
#include "slic3r/Utils/json_diff.hpp"
#include <wx/string.h>
#include <limits>
#include <limits>
#include <optional>
namespace Slic3r
{
@@ -22,6 +23,7 @@ public:
public:
// chamber
bool HasChamber() const { return m_has_chamber; }
bool SupportChamberTempDisplay() const;
bool SupportChamberEdit() const { return m_support_chamber_edit; }
int GetChamberTempEditMin() const { return m_chamber_temp_edit_min; }
int GetChamberTempEditMax() const { return m_chamber_temp_edit_max; }
@@ -57,6 +59,7 @@ private:
/*configure vals*/
// chamber
bool m_has_chamber = false; // whether the machine has a chamber
std::optional<bool> m_support_chamber_temp_display;
bool m_support_chamber_edit = false;
int m_chamber_temp_edit_min = 0;
int m_chamber_temp_edit_max = 60;
+120 -12
View File
@@ -1,16 +1,41 @@
#include "DevConfigUtil.h"
#include <algorithm>
#include <cctype>
#include <wx/dir.h>
#include <boost/filesystem.hpp>
#include <boost/filesystem/operations.hpp>
#include "../I18N.hpp"
using namespace nlohmann;
namespace Slic3r
{
// Translation markers for xgettext extraction.
// These strings are configured in printers/*.json (tool_head_display_names)
// and passed to _L() at runtime. xgettext cannot scan dynamic strings,
// so we mark them here with L() for extraction into .pot file.
// This block is never executed at runtime.
static void _toolhead_translation_markers()
{
// Dynamic toolhead display names from JSON config — xgettext cannot scan these
L("Main Extruder"); L("Main extruder"); L("main extruder");
L("Auxiliary Extruder"); L("Auxiliary extruder"); L("auxiliary extruder");
L("Left Extruder"); L("Left extruder"); L("left extruder");
L("Right Extruder"); L("Right extruder"); L("right extruder");
L("Main Nozzle"); L("Main nozzle"); L("main nozzle");
L("Auxiliary Nozzle"); L("Auxiliary nozzle"); L("auxiliary nozzle");
L("Left Nozzle"); L("Left nozzle"); L("left nozzle");
L("Right Nozzle"); L("Right nozzle"); L("right nozzle");
L("Main Hotend"); L("Main hotend"); L("main hotend");
L("Auxiliary Hotend"); L("Auxiliary hotend"); L("auxiliary hotend");
L("Left Hotend"); L("Left hotend"); L("left hotend");
L("Right Hotend"); L("Right hotend"); L("right hotend");
// standalone position words (short_name=true runtime results)
L("main"); L("auxiliary");
L("Main"); L("Auxiliary");
L("left"); L("right");
L("Left"); L("Right");
}
std::string DevPrinterConfigUtil::m_resource_file_path = "";
@@ -87,9 +112,30 @@ 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;
std::string config_file = m_resource_file_path + "/printers/" + type_str + ".json";
boost::nowide::ifstream json_file(config_file.c_str());
try
@@ -112,6 +158,25 @@ std::string DevPrinterConfigUtil::get_fan_text(const std::string& type_str, cons
return std::string();
}
std::vector<std::string> DevPrinterConfigUtil::get_fan_text_params(const std::string& type_str, const std::string& key)
{
std::string config_file = m_resource_file_path + "/printers/" + type_str + ".json";
boost::nowide::ifstream json_file(config_file.c_str());
try {
json jj;
if (json_file.is_open()) {
json_file >> jj;
if (jj.contains("00.00.00.00")) {
json const& printer = jj["00.00.00.00"];
if (printer.contains("fan") && printer["fan"].contains(key)) {
return printer["fan"][key].get<std::vector<std::string>>();
}
}
}
} catch (...) {}
return std::vector<std::string>();
}
std::string DevPrinterConfigUtil::get_fan_text(const std::string& type_str, int airduct_mode, int airduct_func, int submode)
{
std::vector<std::string> filaments;
@@ -159,6 +224,36 @@ std::string DevPrinterConfigUtil::get_fan_text(const std::string& type_str, int
return std::string();
}
std::string DevPrinterConfigUtil::get_fan_mode_text(const std::string& type_str, int airduct_mode, const std::string& key)
{
std::vector<std::string> filaments;
std::string config_file = m_resource_file_path + "/printers/" + type_str + ".json";
boost::nowide::ifstream json_file(config_file.c_str());
try {
json jj;
if (json_file.is_open()) {
json_file >> jj;
if (jj.contains("00.00.00.00")) {
json const& printer = jj["00.00.00.00"];
if (!printer.contains("fan")) {
return std::string();
}
json const& fan_item = printer["fan"];
const auto& airduct_mode_str = std::to_string(airduct_mode);
if (!fan_item.contains(airduct_mode_str)) {
return std::string();
}
if (fan_item[airduct_mode_str].contains(key)) {
return fan_item[airduct_mode_str][key].get<std::string>();
}
}
}
} catch (...) {}
return std::string();
}
std::map<std::string, std::vector<std::string>> DevPrinterConfigUtil::get_all_subseries(std::string type_str)
{
std::map<std::string, std::vector<std::string>> subseries;
@@ -262,18 +357,29 @@ std::string DevPrinterConfigUtil::get_toolhead_display_name(
{ ToolHeadComponent::Hotend, "hotend" }
};
const int case_index = static_cast<int>(name_case);
const std::string role_key = std::to_string(ext_id);
int case_index = static_cast<int>(name_case); // 0, 1, 2
// Try to read from printer config json
auto names_json = get_value_from_config<json>(type_str, "tool_head_display_names");
std::string role_key = std::to_string(ext_id); // "0" or "1"
const std::string& comp_key = comp_keys.at(component);
std::string result;
auto names_json = get_value_from_config<json>(type_str, "tool_head_display_names");
if (!names_json.is_null() && names_json.contains(role_key) && names_json[role_key].contains(comp_key)) {
if (!names_json.is_null()
&& names_json.contains(role_key)
&& names_json[role_key].contains(comp_key))
{
auto& arr = names_json[role_key][comp_key];
if (arr.is_array() && case_index < static_cast<int>(arr.size()))
if (arr.is_array() && case_index < static_cast<int>(arr.size())) {
result = arr[case_index].get<std::string>();
}
}
// Orca: models that ship no tool_head_display_names (e.g. H2D/H2S) must still get distinct
// per-extruder labels, so fall back to the previous programmatic Left/Right construction.
// Engages only when the config lookup above yields nothing, so models that ship the key
// behave exactly like the reference.
if (result.empty()) {
const std::string side = ext_id == DEPUTY_EXTRUDER_ID ? "Left" : "Right";
const std::string component_name = component == ToolHeadComponent::Extruder ? "Extruder" :
@@ -285,13 +391,15 @@ std::string DevPrinterConfigUtil::get_toolhead_display_name(
std::transform(result.begin(), result.end(), result.begin(), [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
}
// short_name: return only the role prefix (e.g. "Main" from "Main Nozzle")
if (short_name) {
auto sp = result.find(' ');
if (sp != std::string::npos)
if (sp != std::string::npos) {
result = result.substr(0, sp);
}
}
return result;
}
};
};
+50 -40
View File
@@ -11,6 +11,8 @@
#include <string>
#include <map>
#include <vector>
#include <algorithm>
#include <cctype>
#include <boost/log/trivial.hpp>
#include <boost/nowide/fstream.hpp>
@@ -23,6 +25,20 @@
namespace Slic3r
{
/// Toolhead component type (extruder / nozzle / hotend)
enum class ToolHeadComponent {
Extruder,
Nozzle,
Hotend
};
/// Name case style for toolhead display names
enum class ToolHeadNameCase {
TitleCase = 0, // [0] "Main Extruder" — panel titles, section headers
SentenceCase = 1, // [1] "Main extruder" — static box labels
LowerCase = 2 // [2] "main extruder" — inline text, sentence concatenation
};
class dePrinterConfigFactory
{
public:
@@ -30,17 +46,6 @@ public:
~dePrinterConfigFactory() = default;
};
enum class ToolHeadComponent {
Extruder,
Nozzle,
Hotend
};
enum class ToolHeadNameCase {
TitleCase = 0,
SentenceCase = 1,
LowerCase = 2
};
class DevPrinterConfigUtil
{
@@ -74,13 +79,19 @@ 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 bool support_ams_fila_change_abort(std::string type_str) { return get_value_from_config<bool>(type_str, "print", "support_ams_filament_change_abort"); }
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);
static std::string get_fan_text(const std::string& type_str, const std::string& key);
static std::vector<std::string> get_fan_text_params(const std::string& type_str, const std::string& key);
static std::string get_fan_text(const std::string& type_str, int airduct_mode, int airduct_func, int submode);
static std::string get_fan_mode_text(const std::string& type_str, int airduct_mode, const std::string& key);
/*extruder*/
static bool get_printer_can_set_nozzle(std::string type_str) { return get_value_from_config<bool>(type_str, "enable_set_nozzle_info"); }// can set nozzle from studio
/*toolhead display names (extruder / nozzle / hotend)*/
static std::string get_toolhead_display_name(
const std::string& type_str,
int ext_id,
@@ -89,10 +100,15 @@ public:
bool short_name = false);
/*print job*/
static bool support_print_check_firmware_for_tpu_left(std::string type_str){ return get_value_from_config<bool>(type_str, "print", "support_print_check_firmware_for_tpu_left"); }
static bool support_user_first_setup_tpu_check(std::string type_str){ return get_value_from_config<bool>(type_str, "print", "support_user_first_setup_tpu_check"); }
static std::string support_user_first_setup_tpu_check_url(std::string type_str){ return get_value_from_config<std::string>(type_str, "print", "support_user_first_setup_tpu_check_url"); }
static bool support_ams_ext_mix_print(std::string type_str) { return get_value_from_config<bool>(type_str, "print", "support_ams_ext_mix_print"); }
static bool support_print_time_estimate_warning(std::string type_str) { return get_value_from_config<bool>(type_str, "print", "support_print_time_estimate_warning"); }
/*calibration*/
static std::vector<std::string> get_unsupport_auto_cali_filaments(std::string type_str) { return get_value_from_config<std::vector<std::string>>(type_str, "auto_cali_not_support_filaments"); }
static bool support_disable_cali_flow_type(std::string type_str) { return get_value_from_config<bool>(type_str,"support_disable_cali_flow_type"); }
/*detection*/
static bool support_wrapping_detection(const std::string& type_str) { return get_value_from_config<bool>(type_str, "support_wrapping_detection"); }
@@ -102,6 +118,10 @@ public:
/*print check*/
static bool support_print_check_extension_fan_f000_mounted(const std::string& type_str) { return get_value_from_config<bool>(type_str, "print", "support_print_check_extension_fan_f000_mounted"); }
static std::string air_print_detection_position(const std::string &type_str) { return get_value_from_config<std::string>(type_str, "air_print_detection_position"); }
/*bed*/
static int get_bed_temperature_limit(const std::string &type_str) { return get_value_from_config<int>(type_str, "print", "bed_temperature_limit"); }
public:
template<typename T>
@@ -125,7 +145,7 @@ public:
}
}
}
catch (...) { assert(0 && "get_value_from_config failed"); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed"; }// there are file errors
catch (...) { assert(0 && "get_value_from_config failed"); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed"; }// there are file errors
return T();
};
@@ -177,7 +197,7 @@ public:
}
}
}
catch (...) { assert(0 && "get_json_from_config failed"); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed"; }// there are file errors
catch (...) { assert(0 && "get_json_from_config failed"); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed"; }// there are file errors
return nlohmann::json();
}
@@ -186,35 +206,25 @@ private:
};
/*special transform*/
static std::string _parse_printer_type(const std::string& type_str)
static std::string _parse_printer_type(const std::string &type_str)
{
if (type_str.compare("3DPrinter-X1") == 0)
{
return "BL-P002";
}
else if (type_str.compare("3DPrinter-X1-Carbon") == 0)
{
return "BL-P001";
}
else if (type_str.compare("BL-P001") == 0)
{
return type_str;
}
else if (type_str.compare("BL-P002") == 0)
{
return type_str;
}
else
{
std::string result = DevPrinterConfigUtil::get_printer_type(type_str);
if (!result.empty())
{
return result;
}
}
static std::unordered_map<std::string, std::string> s_printer_type_lazy_cache;
if (type_str == "3DPrinter-X1") return "BL-P002";
if (type_str == "3DPrinter-X1-Carbon") return "BL-P001";
if (type_str == "BL-P001" || type_str == "BL-P002") return type_str;
auto cache_it = s_printer_type_lazy_cache.find(type_str);
if (cache_it != s_printer_type_lazy_cache.end()) {
return cache_it->second;
}
std::string result = DevPrinterConfigUtil::get_printer_type(type_str);
if (!result.empty()) {
s_printer_type_lazy_cache[type_str] = result;
return result;
}
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " Unsupported printer type: " << type_str;
return type_str;
}
};// namespace Slic3r
};// namespace Slic3r
+106 -6
View File
@@ -10,11 +10,21 @@
#pragma once
#include <string>
#include <nlohmann/json.hpp>
// Reserved for future usage
#define DEV_RESERVED_FOR_FUTURE(...) /* stripped */
// Previous definitions
namespace Slic3r
{
class MachineObject;
}
enum PrinterArch
{
ARCH_CORE_XY,
ARCH_I3,
ARCH_CORE_XY,// move hotbed
ARCH_I3,//move z
};
enum PrinterSeries
@@ -35,11 +45,43 @@ enum AmsStatusMain
AMS_STATUS_MAIN_RFID_IDENTIFYING = 0x02,
AMS_STATUS_MAIN_ASSIST = 0x03,
AMS_STATUS_MAIN_CALIBRATION = 0x04,
AMS_STATUS_MAIN_COLD_PULL = 0x07,
AMS_STATUS_MAIN_SELF_CHECK = 0x10,
AMS_STATUS_MAIN_DEBUG = 0x20,
AMS_STATUS_MAIN_UNKNOWN = 0xFF,
};
enum DevAmsType : int
{
EXT_SPOOL = 0, // EXT
AMS = 1, // AMS1
AMS_LITE = 2, // AMS-Lite
N3F = 3, // N3F, AMS 2PRO
N3S = 4, // N3S, AMS HT
AMS_LITE_MIXED = 5, // AMS-Lite for N9
};
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,6 +89,9 @@ enum AmsStatusMain
#define VIRTUAL_AMS_MAIN_ID_STR "255"
#define VIRTUAL_AMS_DEPUTY_ID_STR "254"
// Tray index offsets for different AMS types
#define AMS_LITE_MIXED_TRAY_INDEX_OFFSET 24 // Offset for AMS_LITE_MIXED tray index (24)
#define INVALID_AMS_TEMPERATURE std::numeric_limits<float>::min()
/* Extruder*/
@@ -55,13 +100,31 @@ enum AmsStatusMain
#define UNIQUE_EXTRUDER_ID MAIN_EXTRUDER_ID
#define INVALID_EXTRUDER_ID -1
// see PartPlate::get_physical_extruder_by_logical_extruder
#define LOGIC_UNIQUE_EXTRUDER_ID 0
#define LOGIC_L_EXTRUDER_ID 0
#define LOGIC_R_EXTRUDER_ID 1
// <ams_id, slot_id>
using DevAmsSlotId = std::pair<int, int>;
/* Nozzle*/
enum NozzleFlowType
enum NozzleFlowType : int
{
NONE_FLOWTYPE,
S_FLOW,
H_FLOW
H_FLOW,
U_FLOW, // TPU 1.75 High Flow
E_FLOW, // E3D High Flow
};
/* 0.2mm 0.4mm 0.6mm 0.8mm */
enum NozzleDiameterType : int
{
NONE_DIAMETER_TYPE,
NOZZLE_DIAMETER_0_2,
NOZZLE_DIAMETER_0_4,
NOZZLE_DIAMETER_0_6,
NOZZLE_DIAMETER_0_8
};
/*Print speed*/
@@ -76,7 +139,7 @@ enum DevPrintingSpeedLevel
};
/*Upgrade*/
enum class DevFirmwareUpgradingState : int
enum class DevFirmwareUpgradeState : int
{
DC = -1,
UpgradingUnavaliable = 0,
@@ -85,6 +148,10 @@ enum class DevFirmwareUpgradingState : int
UpgradingFinished = 3
};
// Orca: DeviceManager/UpgradePanel keep the Orca spelling of this enum; the alias to the
// reference name is a permanent compatibility surface.
using DevFirmwareUpgradingState = DevFirmwareUpgradeState;
class devPrinterUtil
{
public:
@@ -96,4 +163,37 @@ 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
{
enum PrintFromType
{
FROM_NORMAL,
FROM_SDCARD_VIEW,
};
}
};// namespace Slic3r
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));
};
};
// key(extruder_id) -> { key1(nozzle type info), val1( number of the nozzle type)}
using ExtruderNozzleInfos = std::unordered_map<int, std::unordered_map<NozzleDef, int>>;
@@ -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)
@@ -226,7 +271,7 @@ namespace Slic3r
system->m_current_loading_extder_id = INVALID_EXTRUDER_ID;
if (!system->m_extders[MAIN_EXTRUDER_ID].m_star.ams_id.empty())
{
system->m_current_busy_for_loading = (system->m_extders[MAIN_EXTRUDER_ID].m_snow == system->m_extders[MAIN_EXTRUDER_ID].m_star);
system->m_current_busy_for_loading = (system->m_extders[MAIN_EXTRUDER_ID].m_snow != system->m_extders[MAIN_EXTRUDER_ID].m_star);
if (system->m_current_busy_for_loading)
{
system->m_current_loading_extder_id = MAIN_EXTRUDER_ID;
@@ -1,5 +1,6 @@
#pragma once
#include <optional>
#include <unordered_map>
#include "libslic3r/CommonDefs.hpp"
#include "slic3r/Utils/json_diff.hpp"
@@ -21,6 +22,7 @@ struct DevAmsSlotInfo
std::string slot_id;
public:
bool operator!=(const DevAmsSlotInfo& other) const { return !(*this == other); }
bool operator==(const DevAmsSlotInfo& other) const { return ams_id == other.ams_id && slot_id == other.slot_id;}
};
@@ -66,6 +68,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 +174,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; };
+2 -14
View File
@@ -46,20 +46,10 @@ void Slic3r::DevFan::converse_to_duct(bool is_suppt_part_fun, bool is_suppt_aux_
}
}
static std::string _get_string_from_fantype(int type)
{
switch (type) {
case 1: return "cooling_fan";
case 2: return "big_cooling_fan";
case 3: return "chamber_fan";
default: return "";
}
return "";
}
// Old protocol
int Slic3r::DevFan::command_control_fan(int fan_type, int val)
{
// Orca: strip analytics telemetry
std::string gcode = (boost::format("M106 P%1% S%2% \n") % (int) fan_type % (val)).str();
return m_owner->publish_gcode(gcode);
}
@@ -166,8 +156,6 @@ void Slic3r::DevFan::ParseV3_0(const json &device)
m_air_duct_data.modes.clear();
m_air_duct_data.parts.clear();
m_air_duct_data.curren_mode = device["airduct"]["modeCur"].get<int>();
const json &airduct = device["airduct"];
if (airduct.contains("modeCur")) { m_air_duct_data.curren_mode = airduct["modeCur"].get<int>(); }
if (airduct.contains("subMode")) { m_air_duct_data.m_sub_mode = airduct["subMode"].get<int>(); }
@@ -193,7 +181,7 @@ void Slic3r::DevFan::ParseV3_0(const json &device)
}
}
if (AIR_DUCT(mode.id) == AIR_DUCT::AIR_DUCT_EXHAUST) { continue; } /*STUDIO-12796*/
if (AIR_DUCT(mode.id) == AIR_DUCT::AIR_DUCT_EXHAUST) { continue; }
m_air_duct_data.modes[mode.id] = mode;
}
}
+7 -2
View File
@@ -11,8 +11,6 @@ class MachineObject;
enum AirDuctType { AIR_FAN_TYPE, AIR_DOOR_TYPE };
typedef std::function<void(const json &)> CommandCallBack;
enum AIR_FUN : int {
FAN_HEAT_BREAK_0_IDX = 0,
FAN_COOLING_0_AIRDOOR = 1,
@@ -91,6 +89,13 @@ public:
bool IsSupportCoolingFilter() const { return m_support_cooling_filter; }
bool IsCoolingFilerOn() const { return m_sub_mode == 1; }
bool IsExaustFanExit() const
{
for (auto &p : parts) {
if (p.id == int(AIR_FUN::FAN_CHAMBER_0_IDX)) return true;
}
return false;
}
};
class DevFan
@@ -1,5 +1,6 @@
#pragma once
#include <optional>
#include <unordered_map> // Orca: for m_firmwares / GetSuppotedFirmwares() — the unordered_map AMSSetting.cpp mirrors and iterates
#include <nlohmann/json.hpp>
#include "DevCtrl.h"
@@ -75,7 +76,7 @@ public:
DevAmsSystemIdx GetCurrentFirmwareIdxSel() const { return m_current_firmware_sel.m_firmare_idx; };
DevAmsSystemIdx GetCurrentFirmwareIdxRun() const { return m_current_firmware_run.m_firmare_idx; };
std::unordered_map<int, DevAmsSystemFirmware> GetSuppotedFirmwares() const { return m_firmwares;};
std::unordered_map<int, DevAmsSystemFirmware> GetSuppotedFirmwares() const { return m_firmwares;}; // Orca: unordered (mirrored by AMSSetting.cpp)
bool IsSwitching() const { return m_status == "SWITCHING";};
bool IsIdle() const { return m_status == "IDLE";};
@@ -94,7 +95,7 @@ private:
DevAmsSystemFirmware m_current_firmware_run;
DevAmsSystemFirmware m_current_firmware_sel;
std::unordered_map<int, DevAmsSystemFirmware> m_firmwares;
std::unordered_map<int, DevAmsSystemFirmware> m_firmwares; // Orca: unordered (mirrored by AMSSetting.cpp)
DevCtrlInfo m_ctrl_switching;
};
+238 -83
View File
@@ -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;
}
}
}
+55 -3
View File
@@ -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
+136
View File
@@ -0,0 +1,136 @@
#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; }
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;
}
bool DevFilaSwitch::IsReady() const
{
if (!m_is_installed) {
BOOST_LOG_TRIVIAL(debug) << "[FilaSwitch] IsReady: not installed";
return false;
}
const auto& ams_list = m_owner->GetFilaSystem()->GetAmsList();
for (const auto& ams_item : ams_list) {
if (ams_item.second->GetBindedExtruderSet().size() == 0) { return false; }
if (!ams_item.second->GetSwitcherPos().has_value()) {
return false;
}
}
return true;
}
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
+81
View File
@@ -0,0 +1,81 @@
#pragma once
#include "DevDefs.h"
#include <optional>
namespace Slic3r
{
// Previous definitions
class MachineObject;
class DevExtder;
class DevAmsTray;
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; };
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;
};
};
+310 -15
View File
@@ -1,11 +1,13 @@
#include <nlohmann/json.hpp>
#include "DevFilaSystem.h"
#include "DevNozzleSystem.h" // DevNozzle / DevNozzleSystem for GetNozzleFlowStringByAmsId
// TODO: remove this include
#include "slic3r/GUI/DeviceManager.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "DevUtil.h"
#include "DevUtilBackend.h"
using namespace nlohmann;
@@ -98,6 +100,11 @@ std::string DevAmsTray::get_filament_type()
return m_fila_type;
}
std::optional<Slic3r::DevFilamentDryingPreset> DevAmsTray::get_ams_drying_preset() const
{
return DevUtilBackend::GetFilamentDryingPreset(setting_id);
}
DevAms::DevAms(const std::string& ams_id, int extruder_id, AmsType type)
{
@@ -111,7 +118,7 @@ DevAms::DevAms(const std::string& ams_id, int nozzle_id, int type)
m_ams_id = ams_id;
m_ext_id = nozzle_id;
m_ams_type = (AmsType)type;
assert(DUMMY < type && m_ams_type <= N3S);
assert(EXT_SPOOL < type && m_ams_type <= AMS_LITE_MIXED);
}
DevAms::~DevAms()
@@ -137,7 +144,8 @@ static unordered_map<int, wxString> s_ams_display_formats = {
wxString DevAms::GetDisplayName() const
{
wxString ams_display_format;
auto iter = s_ams_display_formats.find(m_ams_type);
// GetAmsType() maps AMS_LITE_MIXED -> AMS_LITE so N9 shows the AMS-Lite name.
auto iter = s_ams_display_formats.find(GetAmsType());
if (iter != s_ams_display_formats.end())
{
ams_display_format = iter->second;
@@ -166,11 +174,13 @@ wxString DevAms::GetDisplayName() const
int DevAms::GetSlotCount() const
{
if (m_ams_type == AMS || m_ams_type == AMS_LITE || m_ams_type == N3F)
// GetAmsType() maps AMS_LITE_MIXED -> AMS_LITE, so N9 reports 4 slots like AMS-Lite.
auto ams_type = GetAmsType();
if (ams_type == AMS || ams_type == AMS_LITE || ams_type == N3F)
{
return 4;
}
else if (m_ams_type == N3S)
else if (ams_type == N3S)
{
return 1;
}
@@ -189,6 +199,27 @@ DevAmsTray* DevAms::GetTray(const std::string& tray_id) const
return nullptr;
}
bool DevAms::IsSupportRemoteDry(const MachineObject* obj) const
{
if (obj && obj->is_support_remote_dry) {
return SupportDrying();
}
return false;
}
bool DevAms::AmsIsDrying()
{
if (!GetDryStatus().has_value()) {
return false;
}
return GetDryStatus().value() == DevAms::DryStatus::Checking
|| GetDryStatus().value() == DevAms::DryStatus::Drying
|| GetDryStatus().value() == DevAms::DryStatus::Error
|| GetDryStatus().value() == DevAms::DryStatus::CannotStopHeatOutofControl;
}
DevFilaSystem::~DevFilaSystem()
{
for (auto it = amsList.begin(); it != amsList.end(); it++)
@@ -258,6 +289,44 @@ int DevFilaSystem::GetExtruderIdByAmsId(const std::string& ams_id) const
return 0; // not found
}
std::string DevFilaSystem::GetNozzleFlowStringByAmsId(const std::string& ams_id) const
{
auto extruder_id = GetExtruderIdByAmsId(ams_id);
auto nozzle = GetOwner()->GetNozzleSystem()->GetExtNozzle(extruder_id);
return DevNozzle::GetNozzleFlowTypeString(nozzle.GetNozzleFlowType());
}
std::map<int, DevAmsSlotId> DevFilaSystem::GetTrayIndexMap()
{
std::map<int, DevAmsSlotId> tray_id_map;
tray_id_map[VIRTUAL_TRAY_MAIN_ID] = DevAmsSlotId{VIRTUAL_TRAY_MAIN_ID, 0};
tray_id_map[VIRTUAL_TRAY_DEPUTY_ID] = DevAmsSlotId{VIRTUAL_TRAY_DEPUTY_ID, 0};
for (auto& [ams_id, ams_item] : GetAmsList()) {
for (auto &[slot_id, slot_item] : ams_item->GetTrays()) {
if (ams_item && slot_item) {
try {
int ams_id_int = stoi(ams_id);
int slot_id_int = stoi(slot_id);
int tray_index = -1;
if (ams_item->GetAmsType() == DevAms::N3S) {
tray_index = ams_id_int;
} else if(ams_item->GetAmsType() == DevAms::AMS_LITE && ams_item->IsAmsLiteMixed()) {
tray_index = 24 + slot_id_int;
} else {
tray_index = (ams_id_int * 4 + slot_id_int);
}
tray_id_map[tray_index] = {ams_id_int, slot_id_int};
} catch(...) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << "invalid ams id: " << ams_id << " or slot id: " << slot_id;
}
}
}
}
return tray_id_map;
}
bool DevFilaSystem::IsAmsSettingUp() const
{
int setting_up_stat = DevUtil::get_flag_bits(m_ams_cali_stat, 0, 8);
@@ -304,6 +373,15 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
if (!key_field_only)
{
// Newer firmware sends the exact filament-change step sequence in ams.cfs, so the
// client no longer hardcodes the steps per model. Absent on current firmware, which
// keeps the list empty and the legacy hardcoded/ams_status_sub step path in effect.
if (jj["ams"].contains("cfs")) {
system->m_filament_change_steps = DevJsonValParser::GetVal<std::vector<DevFilamentStep>>(jj["ams"], "cfs");
} else {
system->m_filament_change_steps.clear();
}
if (jj["ams"].contains("tray_read_done_bits"))
{
obj->tray_read_done_bits = stol(jj["ams"]["tray_read_done_bits"].get<std::string>(), nullptr, 16);
@@ -361,17 +439,43 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
int type_id = 1; // 0:dummy 1:ams 2:ams-lite 3:n3f 4:n3s
/*ams info*/
std::set<int> binded_extruder_set;
std::optional<DevFilaSwitch::SwitchPos> binded_switcher_pos;
if (it->contains("info")) {
const std::string& info = (*it)["info"].get<std::string>();
type_id = DevUtil::get_flag_bits(info, 0, 4);
extuder_id = DevUtil::get_flag_bits(info, 8, 4);
if (extuder_id == 0xE && obj->GetFilaSwitch()->IsInstalled()) {
int bind_switch_in = DevUtil::get_flag_bits(info, 24, 4);
if (bind_switch_in == 0 || bind_switch_in == 1) {
binded_extruder_set = { MAIN_EXTRUDER_ID, DEPUTY_EXTRUDER_ID };
}
if (bind_switch_in == 0) {
binded_switcher_pos = DevFilaSwitch::SwitchPos::POS_IN_B;
} else if (bind_switch_in == 1) {
binded_switcher_pos = DevFilaSwitch::SwitchPos::POS_IN_A;
}
// Orca: the switch feeds every AMS to both extruders; pin a deterministic
// single-extruder id so legacy GetExtruderId() consumers keep a valid value
// while switch-aware code reads the binding set instead.
extuder_id = MAIN_EXTRUDER_ID;
} else if (extuder_id != 0xE) {
binded_extruder_set = { extuder_id };
}
} else {
if (!obj->is_enable_ams_np && obj->get_printer_ams_type() == "f1") {
type_id = DevAms::AMS_LITE;
}
binded_extruder_set = { MAIN_EXTRUDER_ID };
}
/*AMS without initialization*/
// Orca: an AMS reporting extruder 0xE without a Filament Track Switch installed has
// no usable extruder binding; drop it, preserving the existing display for
// half-initialized printers. With the switch installed the 0xE case is remapped
// above to both extruders and falls through to normal handling.
if (extuder_id == 0xE)
{
ams_id_set.erase(ams_id);
@@ -403,6 +507,13 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
/*set ams type flag*/
curr_ams->SetAmsType(type_id);
// Refresh the switch-aware extruder binding on every push (both create and
// update paths) so it can't go stale after an AMS is re-homed to another
// extruder. Without the switch the set holds the single bound extruder and
// the track position stays empty.
curr_ams->m_binded_extruder_set = binded_extruder_set;
curr_ams->m_binded_switcher_pos = binded_switcher_pos;
/*set ams exist flag*/
try
@@ -415,6 +526,11 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
{
curr_ams->m_exist = (obj->ams_exist_bits & (1 << ams_id_int)) != 0 ? true : false;
}
else if (type_id == DevAms::AMS_LITE_MIXED)
{
// Mixed AMS-Lite (A2L / N9) exist flag lives at bit 12.
curr_ams->m_exist = DevUtil::get_flag_bits(obj->ams_exist_bits, 12);
}
else
{
curr_ams->m_exist = DevUtil::get_flag_bits(obj->ams_exist_bits, 4 + (ams_id_int - 128));
@@ -426,9 +542,18 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
;
}
if (it->contains("dry_time") && (*it)["dry_time"].is_number())
if (it->contains("temp"))
{
curr_ams->m_left_dry_time = (*it)["dry_time"].get<int>();
std::string temp = (*it)["temp"].get<std::string>();
try
{
curr_ams->m_current_temperature = DevUtil::string_to_float(temp);
}
catch (...)
{
curr_ams->m_current_temperature = INVALID_AMS_TEMPERATURE;
}
}
if (it->contains("humidity"))
@@ -457,17 +582,32 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
}
}
if (it->contains("temp"))
if (it->contains("dry_time") && (*it)["dry_time"].is_number())
{
std::string temp = (*it)["temp"].get<std::string>();
try
{
curr_ams->m_current_temperature = DevUtil::string_to_float(temp);
curr_ams->m_left_dry_time = (*it)["dry_time"].get<int>();
}
// Drying status — only parse if printer supports remote drying
if (obj->is_support_remote_dry) {
if (it->contains("info")) {
const std::string& info = (*it)["info"].get<std::string>();
curr_ams->m_dry_status = (DevAms::DryStatus)DevUtil::get_flag_bits(info, 4, 4);
curr_ams->m_dry_fan1_status = (DevAms::DryFanStatus)DevUtil::get_flag_bits(info, 18, 2);
curr_ams->m_dry_fan2_status = (DevAms::DryFanStatus)DevUtil::get_flag_bits(info, 20, 2);
curr_ams->m_dry_sub_status = (DevAms::DrySubStatus)DevUtil::get_flag_bits(info, 22, 2);
}
catch (...)
{
curr_ams->m_current_temperature = INVALID_AMS_TEMPERATURE;
if (it->contains("dry_setting")) {
const auto& j_dry_settings = (*it)["dry_setting"];
DevAms::DrySettings dry_settings;
DevJsonValParser::ParseVal(j_dry_settings, "dry_filament", dry_settings.dry_filament);
DevJsonValParser::ParseVal(j_dry_settings, "dry_temperature", dry_settings.dry_temp);
DevJsonValParser::ParseVal(j_dry_settings, "dry_duration", dry_settings.dry_hour);
curr_ams->m_dry_settings = dry_settings;
}
if (it->contains("dry_sf_reason")) {
curr_ams->m_dry_cannot_reasons = DevJsonValParser::GetVal<std::vector<DevAms::CannotDryReason>>((*it), "dry_sf_reason");
}
}
@@ -628,6 +768,11 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
{
curr_tray->is_exists = (obj->tray_exist_bits & (1 << (ams_id_int * 4 + tray_id_int))) != 0 ? true : false;
}
else if (type_id == DevAms::AMS_LITE_MIXED)
{
// Mixed AMS-Lite (A2L / N9) trays occupy tray-exist bits 24..27.
curr_tray->is_exists = DevUtil::get_flag_bits(obj->tray_exist_bits, AMS_LITE_MIXED_TRAY_INDEX_OFFSET + tray_id_int);
}
else
{
curr_tray->is_exists = DevUtil::get_flag_bits(obj->tray_exist_bits, 16 + (ams_id_int - 128));
@@ -694,4 +839,154 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
}
}
static DevAms::AmsType ams_type_from_string(const std::string& s)
{
if (s == "ams_lite" || s == "ams-lite") return DevAms::AMS_LITE;
if (s == "n3f") return DevAms::N3F;
if (s == "n3s") return DevAms::N3S;
return DevAms::AMS; // default
}
void DevFilaSystemParser::ParseAgentFilament(const json& data, MachineObject* obj, DevFilaSystem* system)
{
if (!system || !data.is_object())
return;
// --- AMS units ---
if (data.contains("units") && data["units"].is_array())
{
std::set<std::string> seen_units;
for (const auto& u : data["units"])
{
if (!u.is_object() || !u.contains("id"))
continue;
const std::string ams_id = u.value("id", std::string());
if (ams_id.empty())
continue;
seen_units.insert(ams_id);
const int ext_id = u.value("extruder", MAIN_EXTRUDER_ID);
const DevAms::AmsType type = ams_type_from_string(u.value("type", std::string("ams")));
DevAms* ams = nullptr;
auto it = system->amsList.find(ams_id);
if (it == system->amsList.end())
{
ams = new DevAms(ams_id, ext_id, type);
system->amsList.insert(std::make_pair(ams_id, ams));
}
else
{
ams = it->second;
ams->m_ext_id = ext_id;
ams->SetAmsType(type);
}
ams->m_exist = true;
ams->m_current_temperature = u.value("temperature", (float) INVALID_AMS_TEMPERATURE);
ams->m_humidity_percent = u.value("humidity_percent", -1);
ams->m_left_dry_time = u.value("dry_time_min", 0);
// --- slots / trays ---
std::set<std::string> seen_slots;
if (u.contains("slots") && u["slots"].is_array())
{
for (const auto& s : u["slots"])
{
if (!s.is_object())
continue;
const std::string tray_id = std::to_string(s.value("index", -1));
seen_slots.insert(tray_id);
DevAmsTray* tray = nullptr;
auto tit = ams->m_trays.find(tray_id);
if (tit == ams->m_trays.end())
{
tray = new DevAmsTray(tray_id);
ams->m_trays.insert(std::make_pair(tray_id, tray));
}
else
{
tray = tit->second;
}
tray->is_exists = s.value("loaded", false);
tray->m_fila_type = s.value("material", std::string());
tray->setting_id = s.value("preset_id", std::string());
tray->UpdateColorFromStr(s.value("color", std::string()));
tray->nozzle_temp_min = std::to_string(s.value("nozzle_temp_min", 0));
tray->nozzle_temp_max = std::to_string(s.value("nozzle_temp_max", 0));
tray->remain = s.value("remain_percent", -1);
tray->k = s.value("k", 0.0f);
if (s.contains("diameter_mm") && s["diameter_mm"].is_number())
tray->diameter = std::to_string(s["diameter_mm"].get<double>());
if (s.contains("weight_g") && s["weight_g"].is_number())
tray->weight = std::to_string(s["weight_g"].get<int>());
tray->cols.clear();
if (s.contains("colors") && s["colors"].is_array())
{
for (const auto& c : s["colors"])
if (c.is_string())
tray->cols.push_back(c.get<std::string>());
}
tray->ctype = tray->cols.size() > 1 ? 1 : 0;
}
}
// prune trays no longer reported
for (auto tit = ams->m_trays.begin(); tit != ams->m_trays.end();)
{
if (seen_slots.count(tit->first) == 0)
{
delete tit->second;
tit = ams->m_trays.erase(tit);
}
else
{
++tit;
}
}
}
// prune units no longer reported
for (auto it = system->amsList.begin(); it != system->amsList.end();)
{
if (seen_units.count(it->first) == 0)
{
delete it->second;
it = system->amsList.erase(it);
}
else
{
++it;
}
}
}
// --- external / direct spools -> obj->vt_slot ---
// extruder 0 -> main virtual slot, extruder >0 -> deputy.
if (obj && data.contains("external") && data["external"].is_array())
{
obj->vt_slot.clear();
for (const auto& e : data["external"])
{
if (!e.is_object())
continue;
const int ext = e.value("extruder", MAIN_EXTRUDER_ID);
const int vt_id = (ext == MAIN_EXTRUDER_ID) ? VIRTUAL_TRAY_MAIN_ID : VIRTUAL_TRAY_DEPUTY_ID;
DevAmsTray tray(std::to_string(vt_id));
tray.is_exists = e.value("loaded", false);
tray.m_fila_type = e.value("material", std::string());
tray.setting_id = e.value("preset_id", std::string());
tray.UpdateColorFromStr(e.value("color", std::string()));
tray.nozzle_temp_min = std::to_string(e.value("nozzle_temp_min", 0));
tray.nozzle_temp_max = std::to_string(e.value("nozzle_temp_max", 0));
tray.remain = e.value("remain_percent", -1);
obj->vt_slot.push_back(tray);
}
}
}
}
+151 -9
View File
@@ -4,11 +4,17 @@
#include "DevDefs.h"
#include "DevFilaAmsSetting.h"
#include "DevFilaSwitch.h"
#include "DevUtil.h"
#include <map>
#include <set>
#include <vector>
#include <optional>
#include <memory>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <wx/string.h>
#include <wx/colour.h>
@@ -17,6 +23,7 @@
namespace Slic3r
{
class MachineObject;
struct DevFilamentDryingPreset;
/**
* DevAmsTray - Represents a single filament tray/slot in an AMS unit or virtual tray.
@@ -103,6 +110,8 @@ public:
// static
static wxColour decode_color(const std::string& color);
std::optional<DevFilamentDryingPreset> get_ams_drying_preset() const;
};
/**
@@ -130,13 +139,67 @@ class DevAms
{
friend class DevFilaSystemParser;
public:
enum AmsType : int
using AmsType = DevAmsType;
static constexpr AmsType EXT_SPOOL = DevAmsType::EXT_SPOOL;
static constexpr AmsType AMS = DevAmsType::AMS;
static constexpr AmsType AMS_LITE = DevAmsType::AMS_LITE;
static constexpr AmsType N3F = DevAmsType::N3F;
static constexpr AmsType N3S = DevAmsType::N3S;
static constexpr AmsType AMS_LITE_MIXED = DevAmsType::AMS_LITE_MIXED;
public:
enum class DryCtrlMode : int
{
DUMMY = 0,
AMS = 1, // AMS
AMS_LITE = 2, // AMS-Lite
N3F = 3, // N3F
N3S = 4, // N3S
Off = 0,
OnTime = 1,
OnHumidity = 2,
};
enum class DryStatus : char
{
Off = 0,
Checking = 1,
Drying = 2,
Cooling = 3,
Stopping = 4,
Error = 5,
CannotStopHeatOutofControl = 6,
PrdTesting = 7,
};
enum class DrySubStatus
{
Off = 0,
Heating = 1,
Dehumidify = 2,
};
enum class DryFanStatus : char
{
Off = 0,
On = 1,
};
enum class CannotDryReason : int
{
TaskOccupied = 0,
InsufficientPower = 1,
AmsBusy = 2,
ConsumableAtAmsOutlet = 3,
InitiatingAmsDrying = 4,
NotSupportedIn2dMode = 5,
DryingInProgress = 6,
Upgrading = 7,
InsufficientPowerNeedPluginPower = 8,
FilamentAtAmsOutletManualUnload = 10,
};
struct DrySettings
{
std::string dry_filament;
int dry_temp = -1; // -1 means invalid
int dry_hour = -1; // -1 means invalid, hours
};
public:
@@ -150,11 +213,16 @@ public:
void SetAmsType(int type) { m_ams_type = (AmsType)type; }
void SetAmsType(AmsType type) { m_ams_type = type; }
AmsType GetAmsType() const { return m_ams_type; }
// AMS-Lite-mixed (N9) is a variant of AMS-Lite: report AMS_LITE to generic callers so all
// existing AMS_LITE handling applies. Mixed-specific code uses IsAmsLiteMixed().
AmsType GetAmsType() const { return m_ams_type == AMS_LITE_MIXED ? AMS_LITE : m_ams_type; }
// exist or not
bool IsExist() const { return m_exist; }
// AMS-Lite-mixed for N9 (A2L)
bool IsAmsLiteMixed() const { return m_ams_type == AMS_LITE_MIXED; }
// slots
int GetSlotCount() const;
DevAmsTray* GetTray(const std::string& tray_id) const;
@@ -163,6 +231,20 @@ public:
// installed on the extruder
int GetExtruderId() const { return m_ext_id; }
// Extruders this AMS can currently feed. Without a Filament Track Switch installed this
// is the single {m_ext_id}; with the switch installed the device binds it to both extruders.
const std::set<int>& GetBindedExtruderSet() const { return m_binded_extruder_set; }
// The bound extruder when exactly one is bound, empty otherwise. Used to attribute an AMS
// to a single extruder on printers without the switch.
std::optional<int> GetUniqueBindedExtruderId() const
{
return m_binded_extruder_set.size() == 1 ? std::optional<int>(*m_binded_extruder_set.begin()) : std::nullopt;
}
// Which switch input track (A/B) feeds this AMS, set only when a Filament Track Switch is installed.
std::optional<DevFilaSwitch::SwitchPos> GetSwitcherPos() const { return m_binded_switcher_pos; }
// temperature and humidity
float GetCurrentTemperature() const { return m_current_temperature; }
@@ -170,13 +252,30 @@ public:
int GetHumidityLevel() const { return m_humidity_level; }
int GetHumidityPercent() const { return m_humidity_percent; }
bool SupportDrying() const { return m_ams_type > AMS_LITE; }
bool SupportDrying() const { return m_ams_type == DevAmsType::N3F || m_ams_type == DevAmsType::N3S; }
int GetLeftDryTime() const { return m_left_dry_time; }
// remote drying control
bool IsSupportRemoteDry(const MachineObject* obj) const;
std::optional<DryStatus> GetDryStatus() const { return m_dry_status; };
std::optional<DrySubStatus> GetDrySubStatus() const { return m_dry_sub_status; }
std::optional<DryFanStatus> GetFan1Status() const { return m_dry_fan1_status; }
std::optional<DryFanStatus> GetFan2Status() const { return m_dry_fan2_status; }
std::optional<std::vector<CannotDryReason>> GetCannotDryReason() const { return m_dry_cannot_reasons; }
std::optional<DrySettings> GetDrySettings() const { return m_dry_settings; };
bool AmsIsDrying();
private:
AmsType m_ams_type = AmsType::AMS;
std::string m_ams_id;
int m_ext_id;//extruder id
// Orca: pull-mode / multi-vendor agent model — keeps the legacy single m_ext_id for existing
// consumers and carries the switch-aware binding alongside it. 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
@@ -187,6 +286,14 @@ private:
int m_humidity_level = 5; // AmsType::AMS
int m_humidity_percent = -1; // N3F N3S, the percentage, -1 means invalid. eg. 100 means 100%
int m_left_dry_time = 0;
// see is_support_remote_dry
std::optional<DryStatus> m_dry_status;
std::optional<DrySubStatus> m_dry_sub_status;
std::optional<DryFanStatus> m_dry_fan1_status;
std::optional<DryFanStatus> m_dry_fan2_status;
std::optional<std::vector<CannotDryReason>> m_dry_cannot_reasons;
std::optional<DrySettings> m_dry_settings;
};
/**
@@ -241,9 +348,16 @@ public:
DevAmsTray* GetAmsTray(const std::string& ams_id, const std::string& tray_id) const;
void CollectAmsColors(std::vector<wxColour>& ams_colors) const;
// Map a linear tray index -> {ams_id, slot_id}. Includes the two virtual (external-spool) trays,
// N3S single-slot units, and the A2L/N9 AMS-Lite-mixed layout (trays 24-27).
std::map<int, DevAmsSlotId> GetTrayIndexMap();
// extruder
int GetExtruderIdByAmsId(const std::string& ams_id) const;
// nozzle: untranslated flow-type string of the extruder bound to this ams (for blacklist matching)
std::string GetNozzleFlowStringByAmsId(const std::string& ams_id) const;
/* AMS settings*/
DevAmsSystemSetting& GetAmsSystemSetting() { return m_ams_system_setting; }
std::optional<bool> IsDetectOnInsertEnabled() const { return m_ams_system_setting.IsDetectOnInsertEnabled(); };
@@ -253,10 +367,19 @@ 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;
// crtl
int CtrlAmsStartDryingHour(int ams_id, std::string filament_type, int tag_temp, int tag_duration_hour, bool rotate_tray, int cooling_temp, bool close_power_conflict = false) const;
int CtrlAmsStopDrying(int ams_id) const;
public:
static bool IsBBL_Filament(std::string tag_uid);
@@ -266,6 +389,9 @@ private:
/* ams properties */
int m_ams_cali_stat = 0;
// Change-step sequence reported by the AMS (ams.cfs); empty when firmware doesn't send it.
std::vector<DevFilamentStep> m_filament_change_steps;
std::map<std::string, DevAms*, NumericStrCompare> amsList;// key: ams[id], start with 0
DevAmsSystemSetting m_ams_system_setting{ this };
@@ -289,6 +415,22 @@ class DevFilaSystemParser
{
public:
static void ParseV1_0(const json& print_json, MachineObject* obj, DevFilaSystem* system, bool key_field_only);
static void ParseAgentFilament(const json& data, MachineObject* obj, DevFilaSystem* system);
};
struct DevFilamentDryingPreset
{
std::string filament_id;
std::unordered_set<DevAmsType> ams_limitations; // only use ams types in the set
std::unordered_map<DevAmsType, float> filament_dev_ams_drying_time_on_idle; // hour
std::unordered_map<DevAmsType, float> filament_dev_ams_drying_temperature_on_idle;
std::unordered_map<DevAmsType, float> filament_dev_ams_drying_time_on_print; // hour
std::unordered_map<DevAmsType, float> filament_dev_ams_drying_temperature_on_print;
float filament_dev_drying_cooling_temperature = 0.0f;
float filament_dev_drying_softening_temperature = 0.0f;
float filament_dev_ams_drying_heat_distortion_temperature = 0.0f;
};
}// namespace Slic3r
@@ -16,4 +16,44 @@ int DevFilaSystem::CtrlAmsReset() const
return m_owner->publish_json(jj_command);
}
int DevFilaSystem::CtrlAmsStartDryingHour(int ams_id,
std::string filament_type,
int tag_temp,
int tag_duration_hour,
bool rotate_tray,
int cooling_temp,
bool close_power_conflict) const
{
json jj_command;
jj_command["print"]["command"] = "ams_filament_drying";
jj_command["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
jj_command["print"]["ams_id"] = ams_id;
jj_command["print"]["mode"] = static_cast<int>(DevAms::DryCtrlMode::OnTime); // Orca: cast scoped enum for json
jj_command["print"]["filament"] = filament_type; // Orca: fix comma-operator typo
jj_command["print"]["temp"] = tag_temp;
jj_command["print"]["duration"] = tag_duration_hour;
jj_command["print"]["humidity"] = 0;
jj_command["print"]["rotate_tray"] = rotate_tray;
jj_command["print"]["cooling_temp"] = cooling_temp;
jj_command["print"]["close_power_conflict"] = close_power_conflict;
return m_owner->publish_json(jj_command);
}
int DevFilaSystem::CtrlAmsStopDrying(int ams_id) const
{
json jj_command;
jj_command["print"]["command"] = "ams_filament_drying";
jj_command["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
jj_command["print"]["ams_id"] = ams_id;
jj_command["print"]["mode"] = static_cast<int>(DevAms::DryCtrlMode::Off); // Orca: cast scoped enum for json
jj_command["print"]["filament"] = ""; // Orca: fix comma-operator typo
jj_command["print"]["temp"] = 0;
jj_command["print"]["duration"] = 0;
jj_command["print"]["humidity"] = 0;
jj_command["print"]["rotate_tray"] = false;
jj_command["print"]["cooling_temp"] = 0;
jj_command["print"]["close_power_conflict"] = false;
return m_owner->publish_json(jj_command);
}
}
+13 -2
View File
@@ -1,4 +1,7 @@
#pragma once
#include "DevFirmware.h"
#include "slic3r/Utils/json_diff.hpp"
#include <nlohmann/json.hpp>
#include <wx/string.h>
#include "slic3r/Utils/json_diff.hpp"
@@ -8,6 +11,7 @@ namespace Slic3r {
//Previous definitions
class MachineObject;
// Orca: firmware-type enum consumed by DeviceManager/UpgradePanel; no reference-side equivalent yet
enum PrinterFirmwareType
{
FIRMWARE_TYPE_ENGINEER = 0,
@@ -15,7 +19,6 @@ enum PrinterFirmwareType
FIRMEARE_TYPE_UKNOWN,
};
class FirmwareInfo
{
public:
@@ -40,10 +43,18 @@ public:
public:
bool isValid() const { return !sn.empty(); }
/*type check*/
bool isAirPump() const { return product_name.Contains("Air Pump"); }
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 isRotary() const { return product_name.Contains("Rotary"); }// Rotary Attachment
bool isExtinguishSystem() const { return product_name.Contains("Extinguishing System"); }// Auto Fire Extinguishing System
bool isWTM() const { return name.find("wtm") != string::npos; } // nozzle
bool isExhaustFan() const { return product_name.Contains("Exhaust Fan"); }
bool isHmshub() const { return product_name.find("Filament Buffer") != string::npos; }
bool isFilaTrackSwitch() const { return product_name.find("Filament Track") != string::npos; }
};
+1
View File
@@ -29,6 +29,7 @@ bool DevHMSItem::parse_hms_info(unsigned attr, unsigned code)
std::string DevHMSItem::get_long_error_code() const
{
char buf[64];
// Orca: HMS long-error-code format excludes the reserved byte and keys the Orca HMS wiki lookups
::sprintf(buf, "%02X%02X%02X00000%1X%04X",
this->m_module_id,
this->m_module_num,
+3
View File
@@ -8,6 +8,9 @@ namespace Slic3r {
class MachineObject;
/* some static info of machine*/ /*TODO*/
// Orca: kept as a stub — connection_type/is_lan_mode_printer/is_cloud_mode_printer live inline on
// MachineObject (DeviceManager.hpp). Adopting the reference's full DevInfo would duplicate those
// definitions and require an m_dev_info member MachineObject does not have.
class DevInfo
{
public:
+92 -34
View File
@@ -24,27 +24,37 @@ namespace Slic3r
DevPrinterConfigUtil::InitFilePath(resources_dir());
// Load saved local machines
// Load saved local machines (needs an agent; when built without one the load is
// deferred to set_agent()).
if (agent) {
AppConfig* config = GUI::wxGetApp().app_config;
const auto local_machines = config->get_local_machines();
for (auto& it : local_machines) {
const auto& m = it.second;
MachineObject* obj = new MachineObject(this, m_agent, m.dev_name, m.dev_id, m.dev_ip);
obj->printer_type = m.printer_type;
obj->dev_connection_type = "lan";
obj->bind_state = "free";
obj->bind_sec_link = "secure";
obj->m_is_online = true;
obj->last_alive = Slic3r::Utils::get_current_time_utc();
obj->set_access_code(config->get("access_code", m.dev_id), false);
obj->set_user_access_code(config->get("user_access_code", m.dev_id), false);
if (obj->has_access_right()) {
localMachineList.insert(std::make_pair(m.dev_id, obj));
} else {
config->erase_local_machine(m.dev_id);
delete obj;
}
load_local_machines_from_config();
}
}
void DeviceManager::load_local_machines_from_config()
{
AppConfig* config = GUI::wxGetApp().app_config;
if (!config)
return;
const auto local_machines = config->get_local_machines();
for (auto& it : local_machines) {
const auto& m = it.second;
if (localMachineList.count(m.dev_id))
continue;
MachineObject* obj = new MachineObject(this, m_agent, m.dev_name, m.dev_id, m.dev_ip);
obj->printer_type = m.printer_type;
obj->dev_connection_type = "lan";
obj->bind_state = "free";
obj->bind_sec_link = "secure";
obj->m_is_online = true;
obj->last_alive = Slic3r::Utils::get_current_time_utc();
obj->set_access_code(config->get("access_code", m.dev_id), false);
obj->set_user_access_code(config->get("user_access_code", m.dev_id), false);
if (obj->has_access_right()) {
localMachineList.insert(std::make_pair(m.dev_id, obj));
} else {
config->erase_local_machine(m.dev_id);
delete obj;
}
}
}
@@ -98,6 +108,11 @@ namespace Slic3r
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": updating agent for "
<< localMachineList.size() << " local and "
<< userMachineList.size() << " user machines";
// First real agent after an agent-less construction (network plugin wasn't ready at
// startup): run the persisted-LAN-printer load the constructor had to skip. See
// load_local_machines_from_config().
const bool first_real_agent = (m_agent == nullptr && agent != nullptr);
m_agent = agent;
std::lock_guard<std::mutex> lock(listMutex);
@@ -111,6 +126,10 @@ namespace Slic3r
it.second->set_agent(agent);
}
}
if (first_real_agent) {
load_local_machines_from_config();
}
}
void DeviceManager::EnableMultiMachine(bool enable)
@@ -477,6 +496,26 @@ namespace Slic3r
OnSelectedMachineChanged(previous_selected_machine, selected_machine);
}
void DeviceManager::clear_other_devices()
{
// why: on agent swap, keep "My Devices" but drop the transient "Other Devices"
// Those belong to the previous agent's network scan; the new agent's start_discovery re-populates its own.
const auto my = get_my_machine_list();
for (auto it = localMachineList.begin(); it != localMachineList.end();)
{
if (my.find(it->first) == my.end())
{
// not a "My Device" -> an "Other Device"
delete it->second;
it = localMachineList.erase(it);
}
else
{
++it;
}
}
}
bool DeviceManager::set_selected_machine(std::string dev_id)
{
BOOST_LOG_TRIVIAL(info) << "set_selected_machine=" << dev_id
@@ -572,6 +611,7 @@ namespace Slic3r
}
selected_machine = dev_id;
record_user_last_machine(selected_machine);
return true;
}
@@ -830,26 +870,44 @@ namespace Slic3r
int result = m_agent->get_user_print_info(&http_code, &body, provider);
if (result == 0)
{
parse_user_print_info(body);
// parse_user_print_info and on_machine_alive (SSDP for discovery) both mutate the same userMachineList map.
// on_machine_alive mutates the map on the UI thread, do the same for parse_user_print_info.
Slic3r::GUI::wxGetApp().CallAfter([this, body]() { parse_user_print_info(body); });
}
}
void DeviceManager::record_user_last_machine(const std::string& dev_id)
{
if (Slic3r::GUI::wxGetApp().app_config) {
Slic3r::GUI::wxGetApp().app_config->set("user_last_selected_machine", dev_id);
}
}
std::string DeviceManager::get_user_last_machine() const
{
if (Slic3r::GUI::wxGetApp().app_config) {
const auto& user_last_machine = Slic3r::GUI::wxGetApp().app_config->get("user_last_selected_machine");
if (!user_last_machine.empty()) {
return user_last_machine;
} else if (m_agent) {
return m_agent->get_user_selected_machine();
}
}
return "";
}
void DeviceManager::load_last_machine()
{
// Get all available machines, include cloud machines and lan machines that have access right
auto all_machines = get_my_machine_list();
if (all_machines.empty())
// Only reconnect the remembered cloud machine. Do not select an arbitrary
// first machine: agent swaps intentionally leave the selection empty until
// the new agent explicitly selects its configured printer.
if (userMachineList.empty())
return;
// Then connect to the machine we last selected if available
const std::string last_monitor_machine = m_agent ? m_agent->get_user_selected_machine() : "";
const auto last_machine = all_machines.find(last_monitor_machine);
if (last_machine != all_machines.end()) {
this->set_selected_machine(last_machine->second->get_dev_id());
} else {
// If not, then select the first available one
this->set_selected_machine(all_machines.begin()->second->get_dev_id());
}
const auto& last_monitor_machine = get_user_last_machine();
if (userMachineList.find(last_monitor_machine) != userMachineList.end())
set_selected_machine(last_monitor_machine);
}
void DeviceManager::OnMachineBindStateChanged(MachineObject* obj, const std::string& new_state)
+16 -1
View File
@@ -48,6 +48,13 @@ public:
MachineObject* get_selected_machine();
bool set_selected_machine(std::string dev_id);
// why: clears stale sidebar sync-status / AMS visuals. Public so the printer-agent
// swap path can reuse it instead of duplicating the two sidebar calls.
void OnSelectedMachineLost();
void record_user_last_machine(const std::string& dev_id);
std::string get_user_last_machine() const;
// local machine
void set_local_selected_machine(std::string dev_id) { local_selected_machine = dev_id; };
MachineObject* get_local_selected_machine() const { return get_local_machine(local_selected_machine); }
@@ -67,6 +74,8 @@ public:
void erase_user_machine(std::string dev_id) { userMachineList.erase(dev_id); }
void clean_user_info(bool keep_local_selection = false);
void clear_other_devices();
void load_last_machine();
void update_user_machine_list_info(const std::string& provider);
void parse_user_print_info(std::string body);
@@ -96,11 +105,17 @@ public:
std::map<std::string, std::vector<std::string>> device_subseries;
private:
// Load the LAN printers persisted in AppConfig into localMachineList. Runs from the
// constructor when an agent is available and, for the case where the DeviceManager was
// first built without one (network plugin not yet installed at startup), from set_agent()
// once a real agent finally arrives - so paired printers survive a plugin install/hot
// reload without an app restart.
void load_local_machines_from_config();
void keep_alive();
void check_pushing();
void OnMachineBindStateChanged(MachineObject* obj, const std::string& new_state);
void OnSelectedMachineLost();
void OnSelectedMachineChanged(const std::string& pre_dev_id, const std::string& new_dev_id);
+16 -3
View File
@@ -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;
@@ -149,8 +162,8 @@ namespace Slic3r
}
//first: left,nozzle=1,map=1 second: right,nozzle=0,map=2
bool right_ams_valid = ams->second->GetExtruderId() == 0 && map_opt[MappingOption::USE_RIGHT_AMS];
bool left_ams_valid = ams->second->GetExtruderId() == 1 && map_opt[MappingOption::USE_LEFT_AMS];
bool right_ams_valid = (ams->second->GetBindedExtruderSet().count(MAIN_EXTRUDER_ID) != 0) && map_opt[MappingOption::USE_RIGHT_AMS];
bool left_ams_valid = (ams->second->GetBindedExtruderSet().count(DEPUTY_EXTRUDER_ID) != 0) && map_opt[MappingOption::USE_LEFT_AMS];
if (right_ams_valid || left_ams_valid)
{
tray_filaments.emplace(std::make_pair(tray_index, info));
@@ -186,7 +199,7 @@ namespace Slic3r
}
}
FilamentInfo info;
_parse_tray_info(atoi(tray.id.c_str()), 0, DevAms::DUMMY, tray, info);
_parse_tray_info(atoi(tray.id.c_str()), 0, DevAms::EXT_SPOOL, tray, info);
tray_filaments.emplace(std::make_pair(info.tray_id, info));
}
}
@@ -0,0 +1,457 @@
#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 <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();
}
}
// 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<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; // Orca: libslic3r/ProjectTask.hpp — forward-decl for CtrlGetAutoNozzleMappingV0 signature
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;
};
}
+112
View File
@@ -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);
}
};
+134
View File
@@ -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);
}
};
+468 -28
View File
@@ -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"]);
}
}
}
};
+150 -12
View File
@@ -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);
};
};
};
+194 -61
View File
@@ -1,11 +1,32 @@
#include "DevPrintOptions.h"
#include "DevUtil.h"
#include <cassert>
#include "slic3r/GUI/DeviceManager.hpp"
namespace Slic3r
{
DevPrintOptions::DevPrintOptions(MachineObject* obj) : m_obj(obj)
{
m_detection_list = {
{PrintOptionEnum::AI_Monitoring, &m_ai_monitoring_detection},
{PrintOptionEnum::First_Layer_Detection, &m_first_layer_detection},
{PrintOptionEnum::Buildplate_Mark_Detection, &m_buildplate_mark_detection},
{PrintOptionEnum::Buildplate_Align_Detection, &m_buildplate_align_detection},
{PrintOptionEnum::Auto_Recovery_Detection, &m_auto_recovery_detection},
{PrintOptionEnum::Allow_Prompt_Sound_Detection, &m_allow_prompt_sound_detection},
{PrintOptionEnum::Filament_Tangle_Detection, &m_filament_tangle_detection},
{PrintOptionEnum::Idle_Heating_Protect_Detection, &m_idel_heating_protect_detection},
{PrintOptionEnum::Purify_Air_At_Print_End, &m_purify_air_at_print_end},
{PrintOptionEnum::Snapshot_Detection, &m_snapshot_detection},
{PrintOptionEnum::FOD_Check_Detection, &m_fod_check_detection},
{PrintOptionEnum::Displacement_Detection, &m_displacement_detection},
{PrintOptionEnum::Smart_Nozzle_Blob_Detection, &m_smart_nozzle_blob_detection},
};
}
void DevPrintOptionsParser::Parse(DevPrintOptions* opts, const nlohmann::json& print_json)
{
try
@@ -20,6 +41,31 @@ void DevPrintOptionsParser::Parse(DevPrintOptions* opts, const nlohmann::json& p
const std::string& cfg = print_json["cfg"].get<std::string>();
opts->m_speed_level = (DevPrintingSpeedLevel)DevUtil::get_flag_bits(cfg, 8, 3);
}
// Orca: xcam.cfg is an integer bitfield (distinct from the top-level cfg string) that
// carries the plate-align/FOD/displacement current values.
if (print_json.contains("xcam") && print_json["xcam"].contains("cfg")) {
int xcam_cfg = print_json["xcam"]["cfg"].get<int>();
if (time(nullptr) - opts->m_buildplate_align_detection.detect_hold_start > HOLD_TIME_3SEC)
opts->m_buildplate_align_detection.current_detect_value = DevUtil::get_flag_bits(xcam_cfg, 20);
if (time(nullptr) - opts->m_fod_check_detection.detect_hold_start > HOLD_TIME_3SEC)
opts->m_fod_check_detection.current_detect_value = DevUtil::get_flag_bits(xcam_cfg, 21);
if (time(nullptr) - opts->m_displacement_detection.detect_hold_start > HOLD_TIME_3SEC)
opts->m_displacement_detection.current_detect_value = DevUtil::get_flag_bits(xcam_cfg, 22);
}
// Orca: fun2 support bits (distinct from the top-level cfg/fun fields). ORCA's DevUtil has no
// no-border extractor, so the MachineObject helper is used here instead.
if (print_json.contains("fun2") && print_json["fun2"].is_string()) {
std::string fun2 = print_json["fun2"].get<std::string>();
if (!fun2.empty()) {
opts->m_buildplate_align_detection.is_support_detect = opts->m_obj->get_flag_bits_no_border(fun2, 2) == 1;
opts->m_purify_air_at_print_end.is_support_detect = opts->m_obj->get_flag_bits_no_border(fun2, 4);
opts->m_fod_check_detection.is_support_detect = opts->m_obj->get_flag_bits_no_border(fun2, 13);
opts->m_displacement_detection.is_support_detect = opts->m_obj->get_flag_bits_no_border(fun2, 14);
opts->m_smart_nozzle_blob_detection.is_support_detect = opts->m_obj->get_flag_bits_no_border(fun2, 15);
}
}
}
catch (const std::exception& e)
{
@@ -31,32 +77,32 @@ void DevPrintOptionsParser::ParseDetectionV1_0(DevPrintOptions *opts, MachineObj
{
try {
if (print_json.contains("xcam")) {
if (time(nullptr) - opts->xcam_ai_monitoring_hold_start > HOLD_TIME_3SEC) {
if (time(nullptr) - opts->m_ai_monitoring_detection.detect_hold_start > HOLD_TIME_3SEC) {
if (print_json["xcam"].contains("printing_monitor")) {
// new protocol
opts->xcam_ai_monitoring = print_json["xcam"]["printing_monitor"].get<bool>();
opts->m_ai_monitoring_detection.current_detect_value = print_json["xcam"]["printing_monitor"].get<bool>();
} else {
// old version protocol
if (print_json["xcam"].contains("spaghetti_detector")) {
opts->xcam_ai_monitoring = print_json["xcam"]["spaghetti_detector"].get<bool>();
opts->m_ai_monitoring_detection.current_detect_value = print_json["xcam"]["spaghetti_detector"].get<bool>();
if (print_json["xcam"].contains("print_halt")) {
bool print_halt = print_json["xcam"]["print_halt"].get<bool>();
if (print_halt) { opts->xcam_ai_monitoring_sensitivity = "medium"; }
if (print_halt) { opts->m_ai_monitoring_detection.current_detect_sensitivity_value = "medium"; }
}
}
}
if (print_json["xcam"].contains("halt_print_sensitivity")) {
opts->xcam_ai_monitoring_sensitivity = print_json["xcam"]["halt_print_sensitivity"].get<std::string>();
opts->m_ai_monitoring_detection.current_detect_sensitivity_value = print_json["xcam"]["halt_print_sensitivity"].get<std::string>();
}
}
if (time(nullptr) - opts->xcam_first_layer_hold_start > HOLD_TIME_3SEC) {
if (print_json["xcam"].contains("first_layer_inspector")) { opts->xcam_first_layer_inspector = print_json["xcam"]["first_layer_inspector"].get<bool>(); }
if (time(nullptr) - opts->m_first_layer_detection.detect_hold_start > HOLD_TIME_3SEC) {
if (print_json["xcam"].contains("first_layer_inspector")) { opts->m_first_layer_detection.current_detect_value = print_json["xcam"]["first_layer_inspector"].get<bool>(); }
}
if (time(nullptr) - opts->xcam_buildplate_marker_hold_start > HOLD_TIME_3SEC) {
if (time(nullptr) - opts->m_buildplate_mark_detection.detect_hold_start > HOLD_TIME_3SEC) {
if (print_json["xcam"].contains("buildplate_marker_detector")) {
opts->xcam_buildplate_marker_detector = print_json["xcam"]["buildplate_marker_detector"].get<bool>();
opts->m_buildplate_mark_detection.current_detect_value = print_json["xcam"]["buildplate_marker_detector"].get<bool>();
obj->is_support_build_plate_marker_detect = true;
} else {
obj->is_support_build_plate_marker_detect = false;
@@ -66,32 +112,31 @@ void DevPrintOptionsParser::ParseDetectionV1_0(DevPrintOptions *opts, MachineObj
} catch (...) {
;
}
}
void DevPrintOptionsParser::ParseDetectionV1_1(DevPrintOptions *opts, MachineObject *obj, const nlohmann::json &print_json,bool enable)
{
if (print_json["module_name"].get<std::string>() == "first_layer_inspector") {
if (time(nullptr) - opts->xcam_first_layer_hold_start > HOLD_TIME_3SEC) {
opts->xcam_first_layer_inspector = enable;
if (time(nullptr) - opts->m_first_layer_detection.detect_hold_start > HOLD_TIME_3SEC) {
opts->m_first_layer_detection.current_detect_value = enable;
}
} else if (print_json["module_name"].get<std::string>() == "buildplate_marker_detector") {
if (time(nullptr) - opts->xcam_buildplate_marker_hold_start > HOLD_TIME_3SEC) {
opts->xcam_buildplate_marker_detector = enable;
if (time(nullptr) - opts->m_buildplate_mark_detection.detect_hold_start > HOLD_TIME_3SEC) {
opts->m_buildplate_mark_detection.current_detect_value = enable;
}
} else if (print_json["module_name"].get<std::string>() == "printing_monitor") {
if (time(nullptr) - opts->xcam_ai_monitoring_hold_start > HOLD_TIME_3SEC) {
opts->xcam_ai_monitoring = enable;
if (time(nullptr) - opts->m_ai_monitoring_detection.detect_hold_start > HOLD_TIME_3SEC) {
opts->m_ai_monitoring_detection.current_detect_value = enable;
if (print_json.contains("halt_print_sensitivity")) {
opts->xcam_ai_monitoring_sensitivity = print_json["halt_print_sensitivity"].get<std::string>();
opts->m_ai_monitoring_detection.current_detect_sensitivity_value = print_json["halt_print_sensitivity"].get<std::string>();
}
}
} else if (print_json["module_name"].get<std::string>() == "spaghetti_detector") {
if (time(nullptr) - opts->xcam_ai_monitoring_hold_start > HOLD_TIME_3SEC) {
if (time(nullptr) - opts->m_ai_monitoring_detection.detect_hold_start > HOLD_TIME_3SEC) {
// old protocol
opts->xcam_ai_monitoring = enable;
opts->m_ai_monitoring_detection.current_detect_value = enable;
if (print_json.contains("print_halt")) {
if (print_json["print_halt"].get<bool>()) { opts->xcam_ai_monitoring_sensitivity = "medium"; }
if (print_json["print_halt"].get<bool>()) { opts->m_ai_monitoring_detection.current_detect_sensitivity_value = "medium"; }
}
}
}
@@ -103,12 +148,12 @@ void DevPrintOptionsParser::ParseDetectionV1_2(DevPrintOptions *opts, MachineObj
if (print_json.contains("option")) {
if (print_json["option"].is_number()) {
int option = print_json["option"].get<int>();
if (time(nullptr) - opts->xcam_auto_recovery_hold_start > HOLD_TIME_3SEC) { opts->xcam_auto_recovery_step_loss = ((option & 0x01) != 0); }
if (time(nullptr) - opts->m_auto_recovery_detection.detect_hold_start > HOLD_TIME_3SEC) { opts->m_auto_recovery_detection.current_detect_value = ((option & 0x01) != 0); }
}
}
if (time(nullptr) - opts->xcam_auto_recovery_hold_start > HOLD_TIME_3SEC) {
if (print_json.contains("auto_recovery")) { opts->xcam_auto_recovery_step_loss = print_json["auto_recovery"].get<bool>(); }
if (time(nullptr) - opts->m_auto_recovery_detection.detect_hold_start > HOLD_TIME_3SEC) {
if (print_json.contains("auto_recovery")) { opts->m_auto_recovery_detection.current_detect_value = print_json["auto_recovery"].get<bool>(); }
}
} catch (...) {}
@@ -117,38 +162,52 @@ void DevPrintOptionsParser::ParseDetectionV1_2(DevPrintOptions *opts, MachineObj
void DevPrintOptionsParser::ParseDetectionV2_0(DevPrintOptions *opts, std::string print_json)
{
if (time(nullptr) - opts->xcam_first_layer_hold_start > HOLD_TIME_3SEC) {
opts->xcam_first_layer_inspector = DevUtil::get_flag_bits(print_json, 12);
if (time(nullptr) - opts->m_first_layer_detection.detect_hold_start > HOLD_TIME_3SEC) {
opts->m_first_layer_detection.current_detect_value = DevUtil::get_flag_bits(print_json, 12);
}
if (time(nullptr) - opts->xcam_ai_monitoring_hold_start > HOLD_COUNT_MAX) {
opts->xcam_ai_monitoring = DevUtil::get_flag_bits(print_json, 15);
if (time(nullptr) - opts->m_ai_monitoring_detection.detect_hold_start > HOLD_COUNT_MAX) {
opts->m_ai_monitoring_detection.current_detect_value = DevUtil::get_flag_bits(print_json, 15);
switch (DevUtil::get_flag_bits(print_json, 13, 2)) {
case 0: opts->xcam_ai_monitoring_sensitivity = "never_halt"; break;
case 1: opts->xcam_ai_monitoring_sensitivity = "low"; break;
case 2: opts->xcam_ai_monitoring_sensitivity = "medium"; break;
case 3: opts->xcam_ai_monitoring_sensitivity = "high"; break;
case 0: opts->m_ai_monitoring_detection.current_detect_sensitivity_value = "never_halt"; break;
case 1: opts->m_ai_monitoring_detection.current_detect_sensitivity_value = "low"; break;
case 2: opts->m_ai_monitoring_detection.current_detect_sensitivity_value = "medium"; break;
case 3: opts->m_ai_monitoring_detection.current_detect_sensitivity_value = "high"; break;
default: break;
}
}
if (time(nullptr) - opts->xcam_auto_recovery_hold_start > HOLD_COUNT_MAX){
opts->xcam_auto_recovery_step_loss =DevUtil::get_flag_bits(print_json, 16);
if (time(nullptr) - opts->m_auto_recovery_detection.detect_hold_start > HOLD_COUNT_MAX){
opts->m_auto_recovery_detection.current_detect_value = DevUtil::get_flag_bits(print_json, 16);
}
if (time(nullptr) - opts->xcam_prompt_sound_hold_start > HOLD_TIME_3SEC) {
opts->xcam_allow_prompt_sound = DevUtil::get_flag_bits(print_json, 22);
if (time(nullptr) - opts->m_allow_prompt_sound_detection.detect_hold_start > HOLD_TIME_3SEC) {
opts->m_allow_prompt_sound_detection.current_detect_value = DevUtil::get_flag_bits(print_json, 22);
}
if (time(nullptr) - opts->xcam_filament_tangle_detect_hold_start > HOLD_TIME_3SEC) {
opts->xcam_filament_tangle_detect = DevUtil::get_flag_bits(print_json, 23);
if (time(nullptr) - opts->m_filament_tangle_detection.detect_hold_start > HOLD_TIME_3SEC) {
opts->m_filament_tangle_detection.current_detect_value = DevUtil::get_flag_bits(print_json, 23);
}
}
void DevPrintOptionsParser::ParseDetectionV2_1(DevPrintOptions *opts, std::string cfg) {
if (time(nullptr) - opts->idel_heating_protect_hold_strat > HOLD_TIME_3SEC)
opts->idel_heating_protect_enabled = DevUtil::get_flag_bits(cfg, 32, 2);
if (time(nullptr) - opts->m_idel_heating_protect_detection.detect_hold_start > HOLD_TIME_3SEC)
opts->m_idel_heating_protect_detection.current_detect_value = DevUtil::get_flag_bits(cfg, 32, 2);
// Orca: further top-level cfg-string detection bits, parsed here alongside idle-heating.
if (time(nullptr) - opts->m_purify_air_at_print_end.detect_hold_start > HOLD_TIME_3SEC)
opts->m_purify_air_at_print_end.current_detect_value = DevUtil::get_flag_bits(cfg, 36, 2);
// Smart nozzle blob detection mode: cfg bits 43-44 (2 bits), 0=off, 1=on, 2=auto
if (time(nullptr) - opts->m_smart_nozzle_blob_detection.detect_hold_start > HOLD_TIME_3SEC)
opts->m_smart_nozzle_blob_detection.current_detect_value = DevUtil::get_flag_bits(cfg, 43, 2);
if (time(nullptr) - opts->m_snapshot_detection.detect_hold_start > HOLD_TIME_3SEC) {
opts->m_snapshot_detection.current_detect_value = DevUtil::get_flag_bits(cfg, 38, 2);
opts->m_snapshot_detection.is_support_detect =
opts->m_snapshot_detection.current_detect_value != 0 && opts->m_snapshot_detection.current_detect_value != 3;
}
}
void DevPrintOptions::SetPrintingSpeedLevel(DevPrintingSpeedLevel speed_level)
@@ -163,75 +222,85 @@ void DevPrintOptions::SetPrintingSpeedLevel(DevPrintingSpeedLevel speed_level)
}
}
PrintOptionData* DevPrintOptions::GetDetectionOption(PrintOptionEnum print_option)
{
auto it = m_detection_list.find(print_option);
if (it != m_detection_list.end())
{
return it->second;
}
return nullptr;
}
int DevPrintOptions::command_xcam_control_ai_monitoring(bool on_off, std::string lvl)
{
bool print_halt = (lvl == "never_halt") ? false : true;
xcam_ai_monitoring = on_off;
xcam_ai_monitoring_hold_start = time(nullptr);
xcam_ai_monitoring_sensitivity = lvl;
m_ai_monitoring_detection.current_detect_value = on_off;
m_ai_monitoring_detection.detect_hold_start = time(nullptr);
m_ai_monitoring_detection.current_detect_sensitivity_value = lvl;
return command_xcam_control("printing_monitor", on_off, m_obj, lvl);
}
int DevPrintOptions::command_xcam_control_idelheatingprotect_detector(bool on_off)
{
idel_heating_protect_enabled = on_off;
idel_heating_protect_hold_strat = time(nullptr);
m_idel_heating_protect_detection.current_detect_value = on_off;
m_idel_heating_protect_detection.detect_hold_start = time(nullptr);
return command_set_against_continued_heating_mode(on_off);
}
int DevPrintOptions::command_xcam_control_buildplate_marker_detector(bool on_off)
{
xcam_buildplate_marker_detector = on_off;
xcam_buildplate_marker_hold_start = time(nullptr);
m_buildplate_mark_detection.current_detect_value = on_off;
m_buildplate_mark_detection.detect_hold_start = time(nullptr);
return command_xcam_control("buildplate_marker_detector", on_off ,m_obj);
}
int DevPrintOptions::command_xcam_control_first_layer_inspector(bool on_off, bool print_halt)
{
xcam_first_layer_inspector = on_off;
xcam_first_layer_hold_start = time(nullptr);
m_first_layer_detection.current_detect_value = on_off;
m_first_layer_detection.detect_hold_start = time(nullptr);
return command_xcam_control("first_layer_inspector", on_off, m_obj);
}
int DevPrintOptions::command_xcam_control_auto_recovery_step_loss(bool on_off)
{
xcam_auto_recovery_step_loss = on_off;
xcam_auto_recovery_hold_start = time(nullptr);
m_auto_recovery_detection.current_detect_value = on_off;
m_auto_recovery_detection.detect_hold_start = time(nullptr);
return command_set_printing_option(on_off, m_obj);
}
int DevPrintOptions::command_xcam_control_allow_prompt_sound(bool on_off)
{
xcam_allow_prompt_sound = on_off;
xcam_prompt_sound_hold_start = time(nullptr);
m_allow_prompt_sound_detection.current_detect_value = on_off;
m_allow_prompt_sound_detection.detect_hold_start = time(nullptr);
return command_set_prompt_sound(on_off, m_obj);
}
int DevPrintOptions::command_xcam_control_filament_tangle_detect(bool on_off)
{
xcam_filament_tangle_detect = on_off;
xcam_filament_tangle_detect_hold_start = time(nullptr);
m_filament_tangle_detection.current_detect_value = on_off;
m_filament_tangle_detection.detect_hold_start = time(nullptr);
return command_set_filament_tangle_detect(on_off, m_obj);
}
void DevPrintOptions::parse_auto_recovery_step_loss_status(int flag) {
if (time(nullptr) - xcam_auto_recovery_hold_start > HOLD_TIME_3SEC) {
xcam_auto_recovery_step_loss = ((flag >> 4) & 0x1) != 0;
if (time(nullptr) - m_auto_recovery_detection.detect_hold_start > HOLD_TIME_3SEC) {
m_auto_recovery_detection.current_detect_value = ((flag >> 4) & 0x1) != 0;
}
}
void DevPrintOptions::parse_allow_prompt_sound_status(int flag)
{
if (time(nullptr) - xcam_prompt_sound_hold_start > HOLD_TIME_3SEC) {
xcam_allow_prompt_sound = ((flag >> 17) & 0x1) != 0;
if (time(nullptr) - m_allow_prompt_sound_detection.detect_hold_start > HOLD_TIME_3SEC) {
m_allow_prompt_sound_detection.current_detect_value = ((flag >> 17) & 0x1) != 0;
}
}
void DevPrintOptions::parse_filament_tangle_detect_status(int flag)
{
if (time(nullptr) - xcam_filament_tangle_detect_hold_start > HOLD_TIME_3SEC) {
xcam_filament_tangle_detect = ((flag >> 20) & 0x1) != 0;
if (time(nullptr) - m_filament_tangle_detection.detect_hold_start > HOLD_TIME_3SEC) {
m_filament_tangle_detection.current_detect_value = ((flag >> 20) & 0x1) != 0;
}
}
@@ -289,11 +358,75 @@ int DevPrintOptions::command_set_filament_tangle_detect(bool filament_tangle_det
return obj->publish_json(j);
}
int DevPrintOptions::command_xcam_control_build_plate_align_detector(bool on_off)
{
m_buildplate_align_detection.current_detect_value = on_off;
m_buildplate_align_detection.detect_hold_start = time(nullptr);
return command_xcam_control("plate_offset_switch", on_off, m_obj);
}
int DevPrintOptions::command_xcam_control_fod_check(bool on_off)
{
m_fod_check_detection.current_detect_value = on_off;
m_fod_check_detection.detect_hold_start = time(nullptr);
return command_xcam_control("fod_check", on_off, m_obj);
}
int DevPrintOptions::command_xcam_control_displacement_detection(bool on_off)
{
m_displacement_detection.current_detect_value = on_off;
m_displacement_detection.detect_hold_start = time(nullptr);
return command_xcam_control("model_movement_check", on_off, m_obj);
}
int DevPrintOptions::command_xcam_control_purify_air_at_print_end(int on_off)
{
m_purify_air_at_print_end.current_detect_value = on_off;
m_purify_air_at_print_end.detect_hold_start = time(nullptr);
return command_set_purify_air_at_print_end((PurifyAirAtPrintEndState) on_off, m_obj);
}
int DevPrintOptions::command_set_purify_air_at_print_end(PurifyAirAtPrintEndState state, MachineObject *obj)
{
json j;
j["print"]["command"] = "print_option";
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
switch (state)
{
case PurifyAirAtPrintEndState::PurifyAirDisable: j["print"]["air_purification"] = 0; break;
case PurifyAirAtPrintEndState::PurifyAirByInside: j["print"]["air_purification"] = 1; break;
case PurifyAirAtPrintEndState::PurifyAirByOutside: j["print"]["air_purification"] = 2; break;
default: assert(0);
}
return obj->publish_json(j);
}
int DevPrintOptions::command_snapshot_control(int on_off)
{
m_snapshot_detection.current_detect_value = on_off;
m_snapshot_detection.detect_hold_start = time(nullptr);
return command_set_snapshot_control(on_off, m_obj);
}
int DevPrintOptions::command_set_snapshot_control(int on_off, MachineObject *obj)
{
json j;
j["camera"]["command"] = "ipcam_cap_pic_set";
j["camera"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
j["camera"]["control"] = on_off ? "enable" : "disable";
return obj->publish_json(j);
}
int DevPrintOptions::command_smart_nozzle_blob_detect_mode(int mode)
{
json j;
j["print"]["command"] = "print_option";
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
j["print"]["nozzle_blob_detect_v2"] = mode; // int: 0=off, 1=on, 2=auto
m_smart_nozzle_blob_detection.current_detect_value = mode;
m_smart_nozzle_blob_detection.detect_hold_start = time(nullptr);
return m_obj->publish_json(j);
}
}
// namespace Slic3r
// namespace Slic3r
+72 -26
View File
@@ -1,4 +1,5 @@
#pragma once
#include <map>
#include <nlohmann/json.hpp>
#include "slic3r/Utils/json_diff.hpp"
#include <wx/string.h>
@@ -9,17 +10,53 @@ namespace Slic3r {
class MachineObject;
// Identifies a print-detection option tracked in DevPrintOptions::m_detection_list.
enum class PrintOptionEnum
{
AI_Monitoring,
First_Layer_Detection,
Buildplate_Mark_Detection,
Buildplate_Align_Detection,
Auto_Recovery_Detection,
Allow_Prompt_Sound_Detection,
Filament_Tangle_Detection,
Idle_Heating_Protect_Detection,
Purify_Air_At_Print_End,
Snapshot_Detection,
FOD_Check_Detection,
Displacement_Detection,
Smart_Nozzle_Blob_Detection,
};
// State of a single print-detection option.
struct PrintOptionData
{
bool is_support_detect{false}; // some detections have no supporting field
int current_detect_value{-1}; // -1 until parsed; otherwise the reported value
std::string current_detect_sensitivity_value;
time_t detect_hold_start{0}; // suppresses parsed overwrites for a short hold
};
class DevPrintOptions
{
friend class DevPrintOptionsParser;
public:
DevPrintOptions(MachineObject* obj): m_obj(obj) {}
DevPrintOptions(MachineObject* obj);
// Tri-state for the purify-air-at-print-end firmware option (matches the MQTT air_purification value).
enum class PurifyAirAtPrintEndState : int {
PurifyAirDisable = 0,
PurifyAirByInside = 1,
PurifyAirByOutside = 2,
};
public:
void SetPrintingSpeedLevel(DevPrintingSpeedLevel speed_level);
DevPrintingSpeedLevel GetPrintingSpeedLevel() const { return m_speed_level;}
// Returns the holder for a tracked detection option, or nullptr if not tracked.
PrintOptionData* GetDetectionOption(PrintOptionEnum print_option);
// detect options
int command_xcam_control_ai_monitoring(bool on_off, std::string lvl);
int command_xcam_control_first_layer_inspector(bool on_off, bool print_halt);
@@ -29,6 +66,13 @@ public:
int command_xcam_control_filament_tangle_detect(bool on_off);
int command_xcam_control_idelheatingprotect_detector(bool on_off);
// Firmware print-option toggles (each gated on a fun2 capability bit).
int command_xcam_control_build_plate_align_detector(bool on_off);
int command_xcam_control_fod_check(bool on_off);
int command_xcam_control_displacement_detection(bool on_off);
int command_xcam_control_purify_air_at_print_end(int on_off);
int command_smart_nozzle_blob_detect_mode(int mode); // 0=off, 1=on, 2=auto
int command_snapshot_control(int on_off);
int command_xcam_control(std::string module_name, bool on_off, MachineObject *obj ,std::string lvl = "");
// set print option
@@ -39,42 +83,44 @@ public:
int command_set_filament_tangle_detect(bool fliament_tangle_detect, MachineObject *obj);
int command_set_against_continued_heating_mode(bool on_off);
int command_set_purify_air_at_print_end(PurifyAirAtPrintEndState state, MachineObject *obj);
int command_set_snapshot_control(int on_off, MachineObject *obj);
void parse_auto_recovery_step_loss_status(int flag);
void parse_allow_prompt_sound_status(int flag);
void parse_filament_tangle_detect_status(int flag);
bool GetAiMonitoring() const { return xcam_ai_monitoring; }
bool GetFirstLayerInspector() const{ return xcam_first_layer_inspector; }
bool GetBuildplateMarkerDetector() const { return xcam_buildplate_marker_detector; }
bool GetAutoRecoveryStepLoss() const { return xcam_auto_recovery_step_loss; }
bool GetAllowPromptSound() const { return xcam_allow_prompt_sound; }
bool GetFilamentTangleDetect() const { return xcam_filament_tangle_detect; }
int GetIdelHeatingProtectEenabled() const { return idel_heating_protect_enabled; }
string GetAiMonitoringSensitivity() const { return xcam_ai_monitoring_sensitivity; }
// Thin wrappers preserved for existing callers; read from the detection-option map.
bool GetAiMonitoring() const { return m_ai_monitoring_detection.current_detect_value == 1; }
bool GetFirstLayerInspector() const{ return m_first_layer_detection.current_detect_value == 1; }
bool GetBuildplateMarkerDetector() const { return m_buildplate_mark_detection.current_detect_value == 1; }
bool GetAutoRecoveryStepLoss() const { return m_auto_recovery_detection.current_detect_value == 1; }
bool GetAllowPromptSound() const { return m_allow_prompt_sound_detection.current_detect_value == 1; }
bool GetFilamentTangleDetect() const { return m_filament_tangle_detection.current_detect_value == 1; }
int GetIdelHeatingProtectEenabled() const { return m_idel_heating_protect_detection.current_detect_value; }
std::string GetAiMonitoringSensitivity() const { return m_ai_monitoring_detection.current_detect_sensitivity_value; }
private:
// print option
DevPrintingSpeedLevel m_speed_level = SPEED_LEVEL_INVALID;
// detect options
bool xcam_ai_monitoring{false};
// detection options
PrintOptionData m_ai_monitoring_detection;
PrintOptionData m_first_layer_detection;
PrintOptionData m_buildplate_mark_detection;
PrintOptionData m_buildplate_align_detection;
PrintOptionData m_auto_recovery_detection;
PrintOptionData m_allow_prompt_sound_detection;
PrintOptionData m_filament_tangle_detection;
PrintOptionData m_idel_heating_protect_detection;
PrintOptionData m_purify_air_at_print_end;
PrintOptionData m_snapshot_detection;
PrintOptionData m_fod_check_detection;
PrintOptionData m_displacement_detection;
PrintOptionData m_smart_nozzle_blob_detection;
std::string xcam_ai_monitoring_sensitivity;
bool xcam_buildplate_marker_detector{false};
bool xcam_first_layer_inspector{false};
bool xcam_auto_recovery_step_loss{false};
bool xcam_allow_prompt_sound{false};
bool xcam_filament_tangle_detect{false};
int idel_heating_protect_enabled = -1;
time_t xcam_ai_monitoring_hold_start = 0;
time_t xcam_buildplate_marker_hold_start = 0;
time_t xcam_first_layer_hold_start = 0;
time_t xcam_auto_recovery_hold_start = 0;
time_t xcam_prompt_sound_hold_start = 0;
time_t xcam_filament_tangle_detect_hold_start = 0;
time_t idel_heating_protect_hold_strat = 0;
std::map<PrintOptionEnum, PrintOptionData*> m_detection_list;
MachineObject* m_obj;/*owner*/
};
@@ -93,4 +139,4 @@ public:
static void ParseDetectionV2_1(DevPrintOptions *opts, std::string cfg);
};
} // namespace Slic3r
} // namespace Slic3r
+33
View File
@@ -0,0 +1,33 @@
#include "DevStatus.h"
#include "slic3r/GUI/DeviceManager.hpp"
#include <boost/log/trivial.hpp>
namespace Slic3r {
void DevStatus::ParseStatus(const nlohmann::json& print_jj)
{
try {
if (print_jj.contains("job")) {
const nlohmann::json& job_jj = print_jj.at("job");
if (job_jj.contains("job_state")) {
auto new_state = (DevJobState)job_jj.at("job_state").get<int>();
if (m_job_state != new_state) {
m_job_state = new_state;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": job_state-> " << (int)new_state;
}
if (m_job_state == DevJobState::JobStateFinishing) {
m_job_state = DevJobState::JobStateFinish;
}
}
}
} catch (const std::exception& e) {
#if BBL_RELEASE_TO_PUBLIC
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": get exception";
#else
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": get exception=" << e.what();
#endif
}
}
} // namespace Slic3r
+43
View File
@@ -0,0 +1,43 @@
#pragma once
#include <optional>
#include <nlohmann/json.hpp>
#include "slic3r/Utils/json_diff.hpp"
#include "DevDefs.h"
namespace Slic3r {
enum DevJobState
{
JobStateIdle = 0,
JobStateSlicing = 1,
JobStatePrepare = 2,
JobStateStarting = 3,
JobStateRunning = 4,
JobStatePause = 5,
JobStatePausing = 6,
JobStateResuming = 7,
JobStateFinish = 8,
JobStateFailed = 9,
JobStateFinishing = 10,
JobStateStoppping = 11,
};
class MachineObject;
class DevStatus
{
public:
DevStatus(MachineObject* owner) : m_owner(owner) {};
public:
std::optional<DevJobState> GetJobState() const { return m_job_state; }
// Parse
void ParseStatus(const nlohmann::json& print_jj);
private:
MachineObject *m_owner = nullptr;
std::optional<DevJobState> m_job_state; // could be nullopt for some old firmware
};
} // namespace Slic3r
+34 -8
View File
@@ -19,16 +19,42 @@ DevStorage::SdcardState Slic3r::DevStorage::set_sdcard_state(int state)
{
if (system)
{
if (print_json.contains("sdcard")) {
if (print_json["sdcard"].get<bool>())
system->m_sdcard_state = DevStorage::SdcardState::HAS_SDCARD_NORMAL;
else
try {
if (print_json.contains("sdcard")) {
if (print_json["sdcard"].get<bool>())
system->m_sdcard_state = DevStorage::SdcardState::HAS_SDCARD_NORMAL;
else
system->m_sdcard_state = DevStorage::SdcardState::NO_SDCARD;
} else {
system->m_sdcard_state = DevStorage::SdcardState::NO_SDCARD;
} else {
system->m_sdcard_state = DevStorage::SdcardState::NO_SDCARD;
}
}
}
// parse timelapse storage space from cam push data
if (print_json.contains("device") && print_json["device"].is_object()) {
const auto& device_json = print_json["device"];
if (device_json.contains("cam") && device_json["cam"].is_object()) {
const auto& cam_data = device_json["cam"];
if (cam_data.contains("tl_internal_free_kb"))
system->tl_internal_free_kb = cam_data["tl_internal_free_kb"].get<int>();
if (cam_data.contains("tl_internal_total_kb"))
system->tl_internal_total_kb = cam_data["tl_internal_total_kb"].get<int>();
if (cam_data.contains("tl_external_free_kb"))
system->tl_external_free_kb = cam_data["tl_external_free_kb"].get<int>();
if (cam_data.contains("tl_external_total_kb"))
system->tl_external_total_kb = cam_data["tl_external_total_kb"].get<int>();
}
}
} catch (...) {}
}
}
bool DevStorage::is_timelapse_storage_low(const std::string& storage) const
{
const int THRESHOLD_KB = 20480; // Orca: 20MB (20480 KB; corrected mislabeled comment)
if (storage == "internal")
return tl_internal_free_kb >= 0 && tl_internal_free_kb < THRESHOLD_KB;
else
return tl_external_free_kb >= 0 && tl_external_free_kb < THRESHOLD_KB;
}
} // namespace Slic3r
+11 -1
View File
@@ -26,11 +26,21 @@ public:
SdcardState get_sdcard_state() const { return m_sdcard_state; };
SdcardState set_sdcard_state(int state);
static void ParseV1_0(const json &print_json, DevStorage *system);
static void ParseV1_0(const json &print_json, DevStorage *system);
bool is_timelapse_storage_low(const std::string& storage) const;
private:
MachineObject *m_owner;
SdcardState m_sdcard_state { NO_SDCARD };
// timelapse storage space info (from device push cam data)
int tl_internal_free_kb{-1};
int tl_internal_total_kb{-1};
int tl_external_free_kb{-1};
int tl_external_total_kb{-1};
};
} // namespace Slic3r
+130
View File
@@ -0,0 +1,130 @@
#include "DevUpgrade.h"
#include "DevUtil.h"
#include "slic3r/GUI/DeviceManager.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/I18N.hpp"
namespace Slic3r {
int DevUpgrade::GetUpgradeProgressInt() const
{
try {
return std::stoi(m_upgrade_progress);
} catch (const std::exception &e) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": " << e.what();
}
return 0;
}
void DevUpgrade::ParseUpgrade_V1_0(const json &print_jj)
{
if (print_jj.contains("upgrade_state")) {
const auto &upgrade_jj = print_jj["upgrade_state"];
// status and steps
DevJsonValParser::ParseVal(upgrade_jj, "status", m_upgrade_status);
DevJsonValParser::ParseVal(upgrade_jj, "progress", m_upgrade_progress);
DevJsonValParser::ParseVal(upgrade_jj, "message", m_upgrade_message);
DevJsonValParser::ParseVal(upgrade_jj, "module", m_upgrade_module);
DevJsonValParser::ParseVal(upgrade_jj, "err_code", m_upgrade_err_code);
// version requested
DevJsonValParser::ParseVal(upgrade_jj, "new_version_state", m_upgrade_new_version_state);
DevJsonValParser::ParseVal(upgrade_jj, "consistency_request", m_upgrade_consistency_request);
DevJsonValParser::ParseVal(upgrade_jj, "force_upgrade", m_upgrade_force_upgrade);
// version info
DevJsonValParser::ParseVal(upgrade_jj, "ota_new_version_number", m_ota_new_version_number);
// version list
if (upgrade_jj.contains("new_ver_list")) {
m_new_ver_list.clear();
for (auto ver_item = upgrade_jj["new_ver_list"].begin(); ver_item != upgrade_jj["new_ver_list"].end(); ver_item++) {
DevFirmwareVersionInfo ver_info;
DevJsonValParser::ParseVal(*ver_item, "name", ver_info.name);
DevJsonValParser::ParseVal(*ver_item, "cur_ver", ver_info.sw_ver);
DevJsonValParser::ParseVal(*ver_item, "new_ver", ver_info.sw_new_ver);
m_new_ver_list[ver_info.name] = ver_info;
if (ver_info.name == "ota" && m_ota_new_version_number.empty()) { m_ota_new_version_number = ver_info.sw_new_ver; }
}
} else {
m_new_ver_list.clear();
}
// try to parse display state
ParseUpgradeDisplayState(upgrade_jj);
}
if (print_jj.contains("cfg")) {
const std::string &cfg = print_jj["cfg"].get<std::string>();
if (!cfg.empty()) { m_upgrade_force_upgrade = DevUtil::get_flag_bits(cfg, 2); }
}
}
void DevUpgrade::ParseUpgradeDisplayState(const json &upgrade_state_jj)
{
if (upgrade_state_jj.contains("dis_state")) {
if ((int) m_upgrade_display_state != upgrade_state_jj["dis_state"].get<int>()) {
DevJsonValParser::ParseVal(upgrade_state_jj, "dis_state", m_upgrade_display_state);
// update the version after upgrade finished
if (m_upgrade_display_state == DevFirmwareUpgradeState::UpgradingFinished) {
Slic3r::GUI::wxGetApp().CallAfter([this] {
m_owner->command_get_version();
});
}
// lan mode printer, hide upgrade avaliable state
if (m_upgrade_display_state == DevFirmwareUpgradeState::UpgradingAvaliable &&
m_owner->is_lan_mode_printer()) {
m_upgrade_display_state = DevFirmwareUpgradeState::UpgradingUnavaliable;
}
}
} else {
// BBS compatibility with old version
if (m_upgrade_status == "DOWNLOADING" ||
m_upgrade_status == "FLASHING" ||
m_upgrade_status == "UPGRADE_REQUEST" ||
m_upgrade_status == "PRE_FLASH_START" ||
m_upgrade_status == "PRE_FLASH_SUCCESS") {
m_upgrade_display_state = DevFirmwareUpgradeState::UpgradingInProgress;
} else if (m_upgrade_status == "UPGRADE_SUCCESS" ||
m_upgrade_status == "DOWNLOAD_FAIL" ||
m_upgrade_status == "FLASH_FAIL" ||
m_upgrade_status == "PRE_FLASH_FAIL" ||
m_upgrade_status == "UPGRADE_FAIL") {
m_upgrade_display_state = DevFirmwareUpgradeState::UpgradingFinished;
} else {
if (HasNewVersion()) {
m_upgrade_display_state = DevFirmwareUpgradeState::UpgradingAvaliable;
} else {
m_upgrade_display_state = DevFirmwareUpgradeState::UpgradingUnavaliable;
}
}
}
}
#define UpgradeNoError 0
#define UpgradeDownloadFailed -1
#define UpgradeVerfifyFailed -2
#define UpgradeFlashFailed -3
#define UpgradePrinting -4
wxString DevUpgrade::GetUpgradeErrCodeStr() const
{
switch (m_upgrade_err_code) {
case UpgradeNoError: return _L("Update successful.");
case UpgradeDownloadFailed: return _L("Downloading failed.");
case UpgradeVerfifyFailed: return _L("Verification failed.");
case UpgradeFlashFailed: return _L("Update failed.");
case UpgradePrinting: return _L("Update failed.");
default: return _L("Update failed.");
}
return "";
}
} // namespace Slic3r
+108
View File
@@ -0,0 +1,108 @@
#pragma once
#include <nlohmann/json.hpp>
#include "slic3r/Utils/json_diff.hpp"
#include "DevDefs.h"
#include "DevFirmware.h"
namespace Slic3r {
class MachineObject;
class DevUpgrade
{
public:
static std::shared_ptr<DevUpgrade> Create(MachineObject *owner) { return std::shared_ptr<DevUpgrade>(new DevUpgrade(owner)); }
public:
// upgrade status
bool IsUpgrading() const { return m_upgrade_display_state == DevFirmwareUpgradeState::UpgradingInProgress; }
bool IsUpgradeAvaliable() const { return m_upgrade_display_state == DevFirmwareUpgradeState::UpgradingAvaliable; }
DevFirmwareUpgradeState GetUpgradeState() const { return m_upgrade_display_state; }
std::string GetUpgradeStatusStr() const { return m_upgrade_status; }
// upgrade request
bool IsUpgradeConsistencyRequest() const { return m_upgrade_consistency_request; }
bool IsUpgradeForceUpgrade() const { return m_upgrade_force_upgrade; }
bool HasNewVersion() const { return m_upgrade_new_version_state == 1; }
std::string GetOtaNewVersion() const { return m_ota_new_version_number; }
std::map<std::string, DevFirmwareVersionInfo> GetNewVersionList() const { return m_new_ver_list; }
// upgrading
std::string GetUpgradeModuleStr() const { return m_upgrade_module; }
std::string GetUpgradeProgressStr() const { return m_upgrade_progress; }
int GetUpgradeProgressInt() const;
std::string GetUpgradeMessageStr() const { return m_upgrade_message; }
wxString GetUpgradeErrCodeStr() const;
public:
// ctrls
int CtrlUpgradeConfirm();
int CtrlUpgradeConsistencyConfirm();
int CtrlUpgradeFirmware(FirmwareInfo info);
int CtrlUpgradeModule(std::string url, std::string module_type, std::string version);
// parser
void ParseUpgrade_V1_0(const json &print_jj);
void ParseUpgradeDisplayState(const json &upgrade_state_jj);
protected:
DevUpgrade(MachineObject *owner) : m_owner(owner) {}
private:
MachineObject *m_owner = nullptr;
int m_upgrade_new_version_state = 0; // 0: invalid version, 1: new version available, 2: no new version
bool m_upgrade_consistency_request = false;
bool m_upgrade_force_upgrade = false;
std::string m_ota_new_version_number;
std::map<std::string, DevFirmwareVersionInfo> m_new_ver_list; // some protcols have version list
DevFirmwareUpgradeState m_upgrade_display_state = DevFirmwareUpgradeState::DC;
int m_upgrade_err_code = 0;
std::string m_upgrade_status;
std::string m_upgrade_progress;
std::string m_upgrade_message;
std::string m_upgrade_module;
};
#if 0 /*TODO*/
class DevUpgradeGetVersionInfo
{
public:
DevUpgradeGetVersionInfo(MachineObject* obj) : m_owner(obj) {} ;
public:
bool IsEmpty() const { m_module_version_map.empty();}
DevFirmwareVersionInfo GetAirPumpVersionInfo() const { return m_air_pump_version_info; }
DevFirmwareVersionInfo GetLaserVersionInfo() const { return m_laser_version_info; }
DevFirmwareVersionInfo GetCuttingModuleVersionInfo() const { return m_cutting_module_version_info; }
DevFirmwareVersionInfo GetExtinguishVersionInfo() const { return m_extinguish_version_info; }
DevFirmwareVersionInfo GetRotaryVersionInfo() const { return m_rotary_version_info; }
std::map<std::string, DevFirmwareVersionInfo> GetModuleVersionInfoMap() const { return m_module_version_map; }
public:
int CtrlGetVersion(bool with_retry = true);
void ParseGetVersion(const json &print_jj);
private:
MachineObject *m_owner;
// some version info
DevFirmwareVersionInfo m_air_pump_version_info;
DevFirmwareVersionInfo m_laser_version_info;
DevFirmwareVersionInfo m_cutting_module_version_info;
DevFirmwareVersionInfo m_extinguish_version_info;
DevFirmwareVersionInfo m_rotary_version_info;
std::map<std::string, DevFirmwareVersionInfo> m_module_version_map;
// ctrl retry
int m_retry_count = 0;
int m_max_retry = 3;
};
#endif
} // namespace Slic3r
@@ -0,0 +1,56 @@
#include "DevUpgrade.h"
#include "DevUtil.h"
#include "slic3r/GUI/DeviceManager.hpp"
namespace Slic3r {
int DevUpgrade::CtrlUpgradeConfirm()
{
json j;
j["upgrade"]["command"] = "upgrade_confirm";
j["upgrade"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
j["upgrade"]["src_id"] = 1; // 1 for slicer
return m_owner->publish_json(j);
}
int DevUpgrade::CtrlUpgradeConsistencyConfirm()
{
json j;
j["upgrade"]["command"] = "consistency_confirm";
j["upgrade"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
j["upgrade"]["src_id"] = 1; // 1 for slicer
return m_owner->publish_json(j);
}
int DevUpgrade::CtrlUpgradeFirmware(FirmwareInfo info)
{
std::string version = info.version;
std::string dst_url = info.url;
std::string module_name = info.module_type;
json j;
j["upgrade"]["command"] = "start";
j["upgrade"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
j["upgrade"]["url"] = info.url;
j["upgrade"]["module"] = info.module_type;
j["upgrade"]["version"] = info.version;
j["upgrade"]["src_id"] = 1;
return m_owner->publish_json(j);
}
int DevUpgrade::CtrlUpgradeModule(std::string url, std::string module_type, std::string version)
{
json j;
j["upgrade"]["command"] = "start";
j["upgrade"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
j["upgrade"]["url"] = url;
j["upgrade"]["module"] = module_type;
j["upgrade"]["version"] = version;
j["upgrade"]["src_id"] = 1;
return m_owner->publish_json(j);
}
} // namespace Slic3r
+73
View File
@@ -24,6 +24,79 @@ int DevUtil::get_flag_bits(std::string str, int start, int count)
return 0;
}
uint32_t DevUtil::get_flag_bits_no_border(std::string str, int start_idx, int count)
{
if (start_idx < 0 || count <= 0) return 0;
try {
// --- 1) trim ---
auto ltrim = [](std::string &s) { s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) { return !std::isspace(ch); })); };
auto rtrim = [](std::string &s) { s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) { return !std::isspace(ch); }).base(), s.end()); };
ltrim(str);
rtrim(str);
// --- 2) remove 0x/0X prefix ---
if (str.size() >= 2 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) { str.erase(0, 2); }
// --- 3) keep only hex digits ---
std::string hex;
hex.reserve(str.size());
for (char c : str) {
if (std::isxdigit(static_cast<unsigned char>(c))) hex.push_back(c);
}
if (hex.empty()) return 0;
// --- 4) use size_t for all index/bit math ---
const size_t total_bits = hex.size() * 4ULL;
const size_t ustart = static_cast<size_t>(start_idx);
if (ustart >= total_bits) return 0;
const int int_bits = std::numeric_limits<uint32_t>::digits; // typically 32
const size_t need_bits = static_cast<size_t>(std::min(count, int_bits));
// [first_bit, last_bit]
const size_t first_bit = ustart;
const size_t last_bit = std::min(ustart + need_bits, total_bits) - 1ULL;
if (last_bit < first_bit) return 0;
const size_t right_index = hex.size() - 1ULL;
const size_t first_nibble = first_bit / 4ULL;
const size_t last_nibble = last_bit / 4ULL;
const size_t start_idx = right_index - last_nibble;
const size_t end_idx = right_index - first_nibble;
if (end_idx < start_idx) return 0;
const size_t sub_len = end_idx - start_idx + 1ULL;
if (end_idx >= hex.size()) return 0;
const std::string sub_hex = hex.substr(start_idx, sub_len);
unsigned long long chunk = std::stoull(sub_hex, nullptr, 16);
const unsigned nibble_offset = static_cast<unsigned>(first_bit % 4ULL);
const unsigned long long shifted = (nibble_offset == 0U) ? chunk : (chunk >> nibble_offset);
uint32_t mask;
if (need_bits >= static_cast<size_t>(std::numeric_limits<uint32_t>::digits)) {
mask = std::numeric_limits<uint32_t>::max();
} else {
mask = static_cast<uint32_t>((1ULL << need_bits) - 1ULL);
}
const uint32_t val = static_cast<uint32_t>(shifted & mask);
return val;
} catch (const std::invalid_argument &) {
return 0;
} catch (const std::out_of_range &) {
return 0;
} catch (...) {
return 0;
}
}
int DevUtil::get_flag_bits(int num, int start, int count, int base)
{
+106
View File
@@ -12,8 +12,16 @@
#include <stdexcept>
#include <boost/log/trivial.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/format.hpp>
#include "nlohmann/json.hpp"
/* Sequence Id*/
#define STUDIO_START_SEQ_ID 20000
#define STUDIO_END_SEQ_ID 30000
#define CLOUD_SEQ_ID 0
namespace Slic3r
{
@@ -27,10 +35,18 @@ public:
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);
static uint32_t get_flag_bits_no_border(std::string str, int start_idx, int count = 1);
// 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);
// sequence id check
static bool is_studio_cmd(int seq) { return seq >= STUDIO_START_SEQ_ID && seq < STUDIO_END_SEQ_ID;};
static bool is_cloud_cmd(int seq) { return seq == CLOUD_SEQ_ID;};
};
@@ -104,4 +120,94 @@ struct NumericStrCompare
}
};
enum class DirtyMode{
COUNTER,
TIMER
};
template<typename T>
class DevDirtyHandler{
public:
DevDirtyHandler(T init_value, int setting_threshold, DirtyMode mode): m_value(init_value), m_setting_threshold(setting_threshold), m_mode(mode)
{
m_threshold = setting_threshold;
}
~DevDirtyHandler(){};
T GetValue() const { return m_value; };
void SetOptimisticValue(const T& data)
{
m_value = data;
m_start_time = time(nullptr);
m_threshold = m_setting_threshold;
}
void UpdateValue(const T& data)
{
if (m_mode == DirtyMode::COUNTER)
{
if (m_threshold > 0)
m_threshold--;
else
m_value = data;
}
else if (m_mode == DirtyMode::TIMER)
{
if (time(nullptr) - m_start_time > m_threshold)
m_value = data;
}
}
private:
T m_value;
DirtyMode m_mode;
int m_setting_threshold{0};
int m_start_time{0};
int m_threshold{0};
};
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;
}
}
static float s_get_diameter(const std::string& diameter)
{
try {
return boost::lexical_cast<float>(diameter);
} catch (...) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed to boost::lexical_cast: " << diameter;
}
try {
return std::stof(diameter);
} catch (...) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " std::stof: " << diameter;
}
return 0.0;
}
}; // namespace Slic3r
@@ -0,0 +1,123 @@
#include "DevUtilBackend.h"
#include "slic3r/GUI/BackgroundSlicingProcess.hpp"
#include "slic3r/GUI/PartPlate.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include <boost/lexical_cast.hpp>
#include <wx/string.h>
namespace Slic3r
{
Slic3r::MultiNozzleUtils::NozzleInfo DevUtilBackend::GetNozzleInfo(const DevNozzle& dev_nozzle)
{
MultiNozzleUtils::NozzleInfo info;
info.diameter = dev_nozzle.GetNozzleDiameterStr().ToStdString();
info.volume_type = DevNozzle::ToNozzleVolumeType(dev_nozzle.GetNozzleFlowType());
info.extruder_id = dev_nozzle.GetLogicExtruderId();
return info;
}
std::shared_ptr<Slic3r::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;
}
std::unordered_map<NozzleDef, int> DevUtilBackend::CollectNozzleInfo(MultiNozzleUtils::NozzleGroupResultBase *nozzle_group_res, int logic_ext_id)
{
std::unordered_map<NozzleDef, int> need_nozzle_map;
if (!nozzle_group_res) {
return need_nozzle_map;
}
const std::vector<Slic3r::MultiNozzleUtils::NozzleInfo>& nozzle_vec = nozzle_group_res->get_used_nozzles_in_extruder(logic_ext_id);
for (auto slicing_nozzle : nozzle_vec) {
try {
NozzleDef data;
data.nozzle_diameter = boost::lexical_cast<float>(slicing_nozzle.diameter);
data.nozzle_flow_type = DevNozzle::ToNozzleFlowType(slicing_nozzle.volume_type);
need_nozzle_map[data]++;
} catch (const std::exception& e) {
assert(0);
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "exception: " << e.what();
}
}
return need_nozzle_map;
}
static std::unordered_map<std::string, DevAmsType> s_ams_type_map = {
{"0", DevAmsType::N3F},
{"1", DevAmsType::N3S},
};
std::optional<Slic3r::DevFilamentDryingPreset> DevUtilBackend::GetFilamentDryingPreset(const std::string& fila_id)
{
if (fila_id.empty() || !GUI::wxGetApp().preset_bundle) {
return std::nullopt;
}
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 == fila_id) {
DevFilamentDryingPreset info;
info.filament_id = fila_id;
try {
if (config.has("filament_dev_ams_drying_ams_limitations")) {
std::vector<std::string> types = config.option<ConfigOptionStrings>("filament_dev_ams_drying_ams_limitations")->values;
for (auto type : types) {
if (s_ams_type_map.count(type) == 0) {
// assert(0);
continue;
}
info.ams_limitations.insert(s_ams_type_map[type]);
}
}
if (config.has("filament_dev_ams_drying_temperature")) {
info.filament_dev_ams_drying_temperature_on_idle[DevAmsType::N3F] = config.option<ConfigOptionFloats>("filament_dev_ams_drying_temperature")->get_at(0);
info.filament_dev_ams_drying_temperature_on_idle[DevAmsType::N3S] = config.option<ConfigOptionFloats>("filament_dev_ams_drying_temperature")->get_at(1);
info.filament_dev_ams_drying_temperature_on_print[DevAmsType::N3F] = config.option<ConfigOptionFloats>("filament_dev_ams_drying_temperature")->get_at(2);
info.filament_dev_ams_drying_temperature_on_print[DevAmsType::N3S] = config.option<ConfigOptionFloats>("filament_dev_ams_drying_temperature")->get_at(3);
}
if (config.has("filament_dev_ams_drying_time")) {
info.filament_dev_ams_drying_time_on_idle[DevAmsType::N3F] = config.option<ConfigOptionFloats>("filament_dev_ams_drying_time")->get_at(0);
info.filament_dev_ams_drying_time_on_idle[DevAmsType::N3S] = config.option<ConfigOptionFloats>("filament_dev_ams_drying_time")->get_at(1);
info.filament_dev_ams_drying_time_on_print[DevAmsType::N3F] = config.option<ConfigOptionFloats>("filament_dev_ams_drying_time")->get_at(2);
info.filament_dev_ams_drying_time_on_print[DevAmsType::N3S] = config.option<ConfigOptionFloats>("filament_dev_ams_drying_time")->get_at(3);
}
if (config.has("filament_dev_drying_softening_temperature")) {
info.filament_dev_drying_softening_temperature = config.option<ConfigOptionFloats>("filament_dev_drying_softening_temperature")->get_at(0);
}
if (config.has("filament_dev_ams_drying_heat_distortion_temperature")){
info.filament_dev_ams_drying_heat_distortion_temperature = config.option<ConfigOptionFloats>("filament_dev_ams_drying_heat_distortion_temperature")->get_at(0);
}
if (config.has("filament_dev_drying_cooling_temperature")) {
info.filament_dev_drying_cooling_temperature = config.option<ConfigOptionFloats>("filament_dev_drying_cooling_temperature")->get_at(0);
}
return info;
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " exception: " << e.what();
}
}
}
return std::nullopt;
}
};// namespace Slic3r
@@ -0,0 +1,47 @@
/**
* @file DevUtilBackend.h
* @brief Provides common static utility methods for backend (preset/slicing).
*
* This class offers a collection of static helper functions such as string manipulation,
* file operations, and other frequently used utilities.
*/
#pragma once
#include "DevDefs.h"
#include "DevNozzleSystem.h"
#include "DevFilaSystem.h"
#include "libslic3r/MultiNozzleUtils.hpp"
#include <unordered_map>
// Forward declarations
namespace Slic3r
{
class MachineObject;
namespace GUI
{
class Plater;
}
}; // namespace Slic3r::GUI
namespace Slic3r
{
class DevUtilBackend
{
public:
DevUtilBackend() = delete;
public:
static MultiNozzleUtils::NozzleInfo GetNozzleInfo(const DevNozzle& dev_nozzle);
// for rack
static std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase> GetNozzleGroupResult(Slic3r::GUI::Plater *plater);
static std::unordered_map<NozzleDef, int> CollectNozzleInfo(MultiNozzleUtils::NozzleGroupResultBase *nozzle_group_res, int logic_ext_id);
// for filament preset
static std::optional<DevFilamentDryingPreset> GetFilamentDryingPreset(const std::string& fila_id);
};
}; // namespace Slic3r
+190 -17
View File
@@ -5,6 +5,10 @@
#include "GUI_App.hpp"
#include "MainFrame.hpp"
#include "ReleaseNote.hpp"
#include "wxExtensions.hpp"
#include <wx/mstream.h>
#include <wx/dcmemory.h>
namespace Slic3r {
namespace GUI
@@ -88,10 +92,22 @@ DeviceErrorDialog::DeviceErrorDialog(MachineObject* obj, wxWindow* parent, wxWin
if (m_obj) { m_obj->command_clean_print_error_uiop(m_obj->print_error); }
e.Skip();
});
m_request_timer = new wxTimer(this);
Bind(wxEVT_TIMER, &DeviceErrorDialog::on_request_timeout, this, m_request_timer->GetId());
}
DeviceErrorDialog::~DeviceErrorDialog()
{
// Orca: invalidate any in-flight cloud snapshot callback (the request has no cancel handle)
*m_alive = false;
if (m_request_timer) {
m_request_timer->Stop();
delete m_request_timer;
m_request_timer = nullptr;
}
if (web_request.IsOk() && web_request.GetState() == wxWebRequest::State_Active)
{
BOOST_LOG_TRIVIAL(info) << "web_request: cancelled";
@@ -103,6 +119,11 @@ DeviceErrorDialog::~DeviceErrorDialog()
void DeviceErrorDialog::on_webrequest_state(wxWebRequestEvent& evt)
{
BOOST_LOG_TRIVIAL(trace) << "monitor: monitor_panel web request state = " << evt.GetState();
// A local/HTTP illustration resolved (or the cloud path fell back to it): stop the watchdog.
m_request_cancelled.store(true);
clear_request_timer();
switch (evt.GetState())
{
case wxWebRequest::State_Completed:
@@ -120,7 +141,9 @@ void DeviceErrorDialog::on_webrequest_state(wxWebRequestEvent& evt)
case wxWebRequest::State_Cancelled:
case wxWebRequest::State_Unauthorized:
{
m_error_picture->SetBitmap(wxBitmap());
m_error_picture->SetBitmap(make_placeholder_image(_L("Network unavailable")));
Layout();
Fit();
break;
}
case wxWebRequest::State_Active:
@@ -129,6 +152,123 @@ void DeviceErrorDialog::on_webrequest_state(wxWebRequestEvent& evt)
}
}
void DeviceErrorDialog::on_request_timeout(wxTimerEvent& event)
{
if (m_request_cancelled.load()) { return; }
// Same fallback order as the cloud callback: local/HTTP illustration first, placeholder last.
if (!get_fail_snapshot_from_local(m_local_img_url)) {
m_error_picture->SetBitmap(make_placeholder_image(_L("Network unavailable")));
}
Layout();
Fit();
}
void DeviceErrorDialog::clear_request_timer()
{
if (m_request_timer && m_request_timer->IsRunning()) {
m_request_timer->Stop();
}
}
wxBitmap DeviceErrorDialog::make_placeholder_image(const wxString& text)
{
const int w = FromDIP(320);
const int h = FromDIP(180);
wxBitmap bmp(wxSize(w, h));
wxMemoryDC dc(bmp);
dc.SetBackground(wxBrush(wxColour(238, 238, 238)));
dc.Clear();
ScalableBitmap icon = ScalableBitmap(this, "dev_hms_diag_loading", 80);
wxBitmap icon_bmp = icon.bmp();
if (icon_bmp.IsOk()) {
int ix = (w - icon_bmp.GetWidth()) / 2;
int iy = (h - icon_bmp.GetHeight()) / 3;
dc.DrawBitmap(icon_bmp, ix, iy, true);
}
dc.SetTextForeground(wxColour(158, 158, 158));
dc.SetFont(Label::Body_14);
wxSize txtSize = dc.GetTextExtent(text);
int tx = (w - txtSize.GetWidth()) / 2;
int ty = h - txtSize.GetHeight() - FromDIP(45);
dc.DrawText(text, tx, ty);
dc.SelectObject(wxNullBitmap);
return bmp;
}
bool DeviceErrorDialog::get_fail_snapshot_from_cloud()
{
if (!m_obj || m_obj->m_print_error_img_id.empty()) { return false; }
NetworkAgent* agent = wxGetApp().getAgent();
if (!agent) { return false; }
// Orca: the cloud request has no cancel handle, so guard the callback against dialog
// destruction with a liveness token (m_alive, cleared in the dtor) and marshal onto the UI
// thread via the always-valid app handler instead of this->CallAfter. The raw bytes are
// decoded on the UI thread so no wxImage (non-atomic refcount) is shared across threads.
// m_request_seq guards against a callback from a previous error painting over the
// current one when the dialog is reused by a later show_error_code().
auto alive = m_alive;
const int seq = m_request_seq;
int ret = agent->get_hms_snapshot(m_obj->get_dev_id(), m_obj->m_print_error_img_id,
[this, alive, seq](std::string body, int status) {
if (!alive->load()) { return; }
wxGetApp().CallAfter([this, alive, seq, body = std::move(body), status]() {
if (!alive->load()) { return; }
if (seq != m_request_seq) { return; }
m_request_cancelled.store(true);
clear_request_timer();
wxImage image;
bool ok = false;
if (status == 200) {
wxMemoryInputStream stream(body.data(), body.size());
ok = image.LoadFile(stream, wxBITMAP_TYPE_ANY);
if (!ok) { BOOST_LOG_TRIVIAL(error) << "get_fail_snapshot_from_cloud: failed to resolve stream"; }
} else {
BOOST_LOG_TRIVIAL(error) << "get_fail_snapshot_from_cloud: status = " << status;
}
if (ok) {
wxImage resize_img = image.Scale(FromDIP(320), FromDIP(180), wxIMAGE_QUALITY_HIGH);
m_error_picture->SetBitmap(wxBitmap(resize_img));
} else if (!get_fail_snapshot_from_local(m_local_img_url)) {
m_error_picture->SetBitmap(make_placeholder_image(_L("Network unavailable")));
}
Layout();
Fit();
});
});
return ret == 0; // dispatched; the frame arrives later via the callback
}
bool DeviceErrorDialog::get_fail_snapshot_from_local(const wxString& image_url)
{
if (image_url.empty()) { return false; }
const wxImage& img = wxGetApp().get_hms_query()->query_image_from_local(image_url);
if (!img.IsOk() && image_url.Contains("http"))
{
web_request = wxWebSession::GetDefault().CreateRequest(this, image_url);
BOOST_LOG_TRIVIAL(trace) << "monitor: create new webrequest, state = " << web_request.GetState();
if (web_request.GetState() == wxWebRequest::State_Idle) web_request.Start();
BOOST_LOG_TRIVIAL(trace) << "monitor: start new webrequest, state = " << web_request.GetState();
}
else
{
m_request_cancelled.store(true);
clear_request_timer();
const wxImage& resize_img = img.Scale(FromDIP(320), FromDIP(180), wxIMAGE_QUALITY_HIGH);
m_error_picture->SetBitmap(wxBitmap(resize_img));
}
return true;
}
void DeviceErrorDialog::init_button(ActionButton style, wxString buton_text)
{
if (btn_bg_white.count() == 0)
@@ -170,6 +310,7 @@ void DeviceErrorDialog::init_button_list()
init_button(FILAMENT_LOAD_RESUME, _L("Filament Loaded, Resume"));
init_button(JUMP_TO_LIVEVIEW, _L("View Liveview"));
init_button(NO_REMINDER_NEXT_TIME, _L("No Reminder Next Time"));
init_button(REFRESH_NOZZLE, _L("Recheck"));
init_button(IGNORE_NO_REMINDER_NEXT_TIME, _L("Ignore. Don't Remind Next Time"));
init_button(IGNORE_RESUME, _L("Ignore this and Resume"));
init_button(PROBLEM_SOLVED_RESUME, _L("Problem Solved and Resume"));
@@ -178,6 +319,10 @@ void DeviceErrorDialog::init_button_list()
init_button(CANCEL, _L("Cancel"));
init_button(STOP_DRYING, _L("Stop Drying"));
init_button(PROCEED, _L("Proceed"));
init_button(OK_JUMP_RACK, _L("OK"));
init_button(ABORT, _L("Abort"));
init_button(DISABLE_PURIFICATION, _L("Disable Purification for This Print"));
init_button(DONT_REMIND_NEXT_TIME, _L("Don't Remind Me"));
init_button(DBL_CHECK_CANCEL, _L("Cancel"));
init_button(DBL_CHECK_DONE, _L("Done"));
init_button(DBL_CHECK_RETRY, _L("Retry"));
@@ -241,6 +386,7 @@ wxString DeviceErrorDialog::show_error_code(int error_code)
Raise();
#ifndef __linux__
// Orca: skip RequestUserAttention on Linux/Wayland (urgency-hint deadlock).
// On Linux (especially Wayland) RequestUserAttention(wxUSER_ATTENTION_ERROR) maps to
// gtk_window_set_urgency_hint(TRUE) which can leave the window in an urgent-but-unfocused
// state — clicks no longer reach any widget in the app and the user has to kill the
@@ -305,22 +451,23 @@ void DeviceErrorDialog::update_contents(const wxString& title, const wxString& t
}
/* image */
if (!image_url.empty())
// Orca: tiered image slot — prefer the printer's captured camera frame of the failure
// (cloud, keyed by m_print_error_img_id), else the local/HTTP HMS illustration, else hide.
// Reuses m_error_picture + on_webrequest_state (single widget, no second request). The
// loading placeholder and 10s watchdog are armed only for the async cloud fetch; the local
// path keeps its existing behavior.
m_local_img_url = image_url;
m_request_cancelled.store(false);
++m_request_seq; // invalidate any snapshot fetch still in flight for the previous error
clear_request_timer();
if (get_fail_snapshot_from_cloud())
{
m_error_picture->SetBitmap(make_placeholder_image(_L("Loading ...")));
m_request_timer->StartOnce(10000);
m_error_picture->Show();
}
else if (get_fail_snapshot_from_local(image_url))
{
const wxImage& img = wxGetApp().get_hms_query()->query_image_from_local(image_url);
if (!img.IsOk() && image_url.Contains("http"))
{
web_request = wxWebSession::GetDefault().CreateRequest(this, image_url);
BOOST_LOG_TRIVIAL(trace) << "monitor: create new webrequest, state = " << web_request.GetState();
if (web_request.GetState() == wxWebRequest::State_Idle) web_request.Start();
BOOST_LOG_TRIVIAL(trace) << "monitor: start new webrequest, state = " << web_request.GetState();
}
else
{
const wxImage& resize_img = img.Scale(FromDIP(320), FromDIP(180), wxIMAGE_QUALITY_HIGH);
m_error_picture->SetBitmap(wxBitmap(resize_img));
}
m_error_picture->Show();
}
else
@@ -349,7 +496,10 @@ void DeviceErrorDialog::update_contents(const wxString& title, const wxString& t
auto text_size = m_error_msg_label->GetBestSize();
if (text_size.y < FromDIP(360))
{
if (!image_url.empty())
// Orca: reserve the image area exactly when an image is coming (cloud snapshot,
// local illustration, or the loading placeholder) - m_error_picture's visibility
// was decided above, so no blank slot is reserved when neither source is usable.
if (m_error_picture->IsShown())
{
m_scroll_area->SetMinSize(wxSize(FromDIP(320), text_size.y + FromDIP(220)));
}
@@ -462,6 +612,29 @@ void DeviceErrorDialog::on_button_click(ActionButton btn_id)
}
break;
}
case DeviceErrorDialog::REFRESH_NOZZLE: {
m_obj->command_refresh_nozzle();
break;
}
case DeviceErrorDialog::OK_JUMP_RACK: {
Slic3r::GUI::wxGetApp().mainframe->jump_to_monitor();
Slic3r::GUI::wxGetApp().mainframe->m_monitor->jump_to_Rack();
break;
}
case DeviceErrorDialog::ABORT: {
m_obj->command_ams_control("abort");
break;
}
case DeviceErrorDialog::DISABLE_PURIFICATION: {
m_obj->command_purification_disable();
break;
}
case DeviceErrorDialog::DONT_REMIND_NEXT_TIME: {
if (!m_action_json.is_null()) {
m_obj->command_dont_remind_next_time(m_action_json);
}
break;
}
case DeviceErrorDialog::ERROR_BUTTON_COUNT: break;
case DeviceErrorDialog::DBL_CHECK_CANCEL: {
+21
View File
@@ -1,7 +1,10 @@
#pragma once
#include <unordered_set>
#include <atomic>
#include <memory>
#include <wx/statbmp.h>
#include <wx/timer.h>
#include <wx/webrequest.h>
#include "GUI_Utils.hpp"
@@ -35,6 +38,7 @@ public:
JUMP_TO_LIVEVIEW,
NO_REMINDER_NEXT_TIME = 23,
REFRESH_NOZZLE = 24,
IGNORE_NO_REMINDER_NEXT_TIME = 25,
//LOAD_FILAMENT = 26*/
IGNORE_RESUME = 27,
@@ -46,6 +50,10 @@ public:
CANCEL = 37,
REMOVE_CLOSE_BTN = 39, // special case, do not show close button
PROCEED = 41,
OK_JUMP_RACK = 49,
ABORT = 51,
DISABLE_PURIFICATION = 54,
DONT_REMIND_NEXT_TIME = 57,
ERROR_BUTTON_COUNT,
@@ -84,6 +92,11 @@ protected:
void on_button_click(ActionButton btn_id);
void on_webrequest_state(wxWebRequestEvent& evt);
void on_request_timeout(wxTimerEvent& event);
void clear_request_timer();
wxBitmap make_placeholder_image(const wxString& text);
bool get_fail_snapshot_from_cloud();
bool get_fail_snapshot_from_local(const wxString& image_url);
void on_dpi_changed(const wxRect& suggested_rect);
private:
@@ -93,6 +106,14 @@ private:
std::unordered_set<Button*> m_used_button;
wxWebRequest web_request;
wxTimer* m_request_timer{ nullptr };
std::atomic<bool> m_request_cancelled{ false };
// UI-thread-only generation counter: bumped on every update_contents() so a cloud
// snapshot callback from a previous error cannot paint over the current one.
int m_request_seq{ 0 };
wxString m_local_img_url;
// Orca: liveness token for the async cloud snapshot callback (the request has no cancel handle)
std::shared_ptr<std::atomic_bool> m_alive{ std::make_shared<std::atomic_bool>(true) };
wxStaticBitmap* m_error_picture;
Label* m_error_msg_label{ nullptr };
Label* m_error_code_label{ nullptr };
+357 -9
View File
@@ -23,6 +23,7 @@
#include "fast_float/fast_float.h"
#include "DeviceCore/DevFilaSystem.h"
#include "DeviceCore/DevFilaSwitch.h"
#include "DeviceCore/DevExtensionTool.h"
#include "DeviceCore/DevExtruderSystem.h"
#include "DeviceCore/DevNozzleSystem.h"
@@ -39,9 +40,16 @@
#include "DeviceCore/DevHMS.h"
#include "DeviceCore/DevMapping.h"
#include "DeviceCore/DevMappingNozzle.h"
#include "DeviceCore/DevManager.h"
#include "DeviceCore/DevUtil.h"
// Orca: adopt DeviceCore split — axis/calib/chamber/status/upgrade modules
#include "DeviceCore/DevAxis.h"
#include "DeviceCore/DevChamber.h"
#include "DeviceCore/DevStatus.h"
#include "DeviceCore/DevUpgrade.h"
#define CALI_DEBUG
#define MINUTE_30 1800000 //ms
@@ -66,6 +74,44 @@ int get_tray_id_by_ams_id_and_slot_id(int ams_id, int slot_id)
}
}
namespace {
// Stringing-prone filament IDs per nozzle-diameter bucket.
// Mirrors the printer firmware tables (see g_leak_pron_idx_for_0_4 / _0_6_0_8).
// Keep these in sync with firmware when new stringing-prone filaments are added.
const std::unordered_set<std::string> g_stringing_prone_for_0_4 = {
"GFA11", // PLA Aero
"GFU90", // TPU 90A
"GFU00", // TPU 95A HF
"GFU02", // Generic TPU for AMS
"GFU98", // TPU for AMS
};
const std::unordered_set<std::string> g_stringing_prone_for_0_6_0_8 = {
"GFA11", // PLA Aero
"GFU00", // TPU 95A HF
};
// Pick the right table for the given nozzle diameter; returns nullptr if the
// nozzle bucket has no entries (e.g. 0.2 mm) or the diameter is invalid.
const std::unordered_set<std::string>* pick_stringing_set(float nozzle_diameter)
{
if (!(nozzle_diameter > 0.f)) return nullptr;
if (nozzle_diameter < 0.3f) return nullptr; // 0.2 nozzle: empty
if (nozzle_diameter < 0.5f) return &g_stringing_prone_for_0_4;
return &g_stringing_prone_for_0_6_0_8; // 0.6 / 0.8 nozzles
}
} // namespace
bool Slic3r::is_stringing_prone_filament(const std::string& filament_id, float nozzle_diameter)
{
if (filament_id.empty()) return false;
const auto* set = pick_stringing_set(nozzle_diameter);
if (!set) return false;
return set->count(filament_id) > 0;
}
wxString Slic3r::get_stage_string(int stage)
{
switch(stage) {
@@ -551,6 +597,7 @@ MachineObject::MachineObject(DeviceManager* manager, NetworkAgent* agent, std::s
mc_print_sub_stage = 0;
mc_left_time = 0;
hw_switch_state = 0;
m_print_error_img_id = "";
has_ipcam = true; // default true
@@ -566,12 +613,22 @@ MachineObject::MachineObject(DeviceManager* manager, NetworkAgent* agent, std::s
m_extder_system = new DevExtderSystem(this);
m_extension_tool = DevExtensionTool::Create(this);
m_nozzle_system = new DevNozzleSystem(this);
m_fila_system = new DevFilaSystem(this);
m_fila_system = std::make_shared<DevFilaSystem>(this);
m_fila_switch = new DevFilaSwitch(this);
m_hms_system = new DevHMS(this);
m_config = new DevConfig(this);
m_ctrl = new DevCtrl(this);
m_print_options = new DevPrintOptions(this);
m_nozzle_mapping_ptr = std::make_shared<DevNozzleMappingCtrl>(this);
// Orca: adopt DeviceCore split — axis/calib/chamber/status/upgrade modules
m_axis = DevAxis::Create(this);
m_chamber = DevChamber::Create(this);
m_upgrade = DevUpgrade::Create(this);
m_status = new DevStatus(this);
m_calib = new DevCalib(this);
}
}
@@ -612,8 +669,9 @@ MachineObject::~MachineObject()
delete m_ctrl;
m_ctrl = nullptr;
delete m_fila_system;
m_fila_system = nullptr;
delete m_fila_switch;
m_fila_switch = nullptr;
delete m_hms_system;
m_hms_system = nullptr;
@@ -623,6 +681,13 @@ MachineObject::~MachineObject()
delete m_print_options;
m_print_options = nullptr;
// Orca: adopt DeviceCore split
delete m_calib;
m_calib = nullptr;
delete m_status;
m_status = nullptr;
}
}
@@ -692,6 +757,33 @@ std::string MachineObject::get_filament_display_type(const std::string& ams_id,
return this->get_tray(ams_id, tray_id).get_display_filament_type();
}
bool MachineObject::any_loaded_filament_is_stringing_prone() const
{
if (print_job_filament_mapping.empty()) return false;
std::vector<float> nozzle_diameters;
if (m_extder_system) {
for (const auto& ext : m_extder_system->GetExtruders()) {
const float d = ext.GetNozzleDiameter();
if (d > 0.f) nozzle_diameters.push_back(d);
}
}
if (nozzle_diameters.empty()) return false;
for (uint16_t v : print_job_filament_mapping) {
if (v == 0xFFFF) continue;
const int ams_id = (v >> 8) & 0xFF;
const int slot_id = v & 0xFF;
const std::string fid = this->get_filament_id(std::to_string(ams_id), std::to_string(slot_id));
if (fid.empty()) continue;
for (float d : nozzle_diameters) {
if (Slic3r::is_stringing_prone_filament(fid, d))
return true;
}
}
return false;
}
void MachineObject::_parse_ams_status(int ams_status)
{
ams_status_sub = ams_status & 0xFF;
@@ -710,6 +802,8 @@ void MachineObject::_parse_ams_status(int ams_status)
ams_status_main = AmsStatusMain::AMS_STATUS_MAIN_SELF_CHECK;
} else if (ams_status_main_int == (int) AmsStatusMain::AMS_STATUS_MAIN_DEBUG) {
ams_status_main = AmsStatusMain::AMS_STATUS_MAIN_DEBUG;
} else if (ams_status_main_int == (int) AmsStatusMain::AMS_STATUS_MAIN_COLD_PULL) {
ams_status_main = AmsStatusMain::AMS_STATUS_MAIN_COLD_PULL;
} else {
ams_status_main = AmsStatusMain::AMS_STATUS_MAIN_UNKNOWN;
}
@@ -861,7 +955,14 @@ void MachineObject::clear_version_info()
laser_version_info = DevFirmwareVersionInfo();
cutting_module_version_info = DevFirmwareVersionInfo();
extinguish_version_info = DevFirmwareVersionInfo();
rotary_version_info = DevFirmwareVersionInfo();
exhaustfan_version_info = DevFirmwareVersionInfo();
amshub_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 +975,19 @@ void MachineObject::store_version_info(const DevFirmwareVersionInfo& info)
cutting_module_version_info = info;
} else if (info.isExtinguishSystem()) {
extinguish_version_info = info;
} else if (info.isRotary()) {
rotary_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.isExhaustFan()) {
exhaustfan_version_info = info;
} else if (info.isHmshub()) {
amshub_version_info = info;
} else if (info.isFilaTrackSwitch()) {
filatrack_version_info = info;
}
module_vers.emplace(info.name, info);
@@ -1190,7 +1304,7 @@ bool MachineObject::canEnableTimelapse(wxString &error_message) const
return true;
}
if (m_storage->get_sdcard_state() != DevStorage::SdcardState::HAS_SDCARD_NORMAL) {
if (m_storage->get_sdcard_state() != DevStorage::SdcardState::HAS_SDCARD_NORMAL && !m_has_timelapse_kit) {
if (m_storage->get_sdcard_state() == DevStorage::SdcardState::NO_SDCARD) {
error_message = _L("Timelapse is not supported while the storage does not exist.");
} else if (m_storage->get_sdcard_state() == DevStorage::SdcardState::HAS_SDCARD_ABNORMAL) {
@@ -1556,7 +1670,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 +1702,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);
}
@@ -1833,10 +1954,17 @@ int MachineObject::command_axis_control(std::string axis, double unit, double in
{
if (m_support_mqtt_axis_control)
{
int dir = input_val > 0 ? 1 : -1;
// i3-arch printers move the bed for Y/Z, so the on-screen direction is
// reversed — same negation the g-code fallback below applies.
if (!is_core_xy() && (axis.compare("Y") == 0 || axis.compare("Z") == 0)) {
dir = -dir;
}
json j;
j["print"]["command"] = "xyz_ctrl";
j["print"]["axis"] = axis;
j["print"]["dir"] = input_val > 0 ? 1 : -1;
j["print"]["dir"] = dir;
j["print"]["mode"] = (std::abs(input_val) >= 10) ? 1 : 0;
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
return this->publish_json(j);
@@ -1976,6 +2104,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 +2126,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 +2150,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 +2181,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();
@@ -2158,6 +2303,14 @@ int MachineObject::command_ack_proceed(json& proceed) {
} else {
proceed["err_ignored"] = std::vector<int>{proceed["err_index"]};
}
for (auto& item : proceed["err_ignored"]) {
json error_item;
error_item["idx"] = item.get<int>();
error_item["mode"] = 0;
proceed["rm_idx"].push_back(error_item);
}
proceed["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
json j;
@@ -2165,6 +2318,78 @@ int MachineObject::command_ack_proceed(json& proceed) {
return this->publish_json(j);
}
bool MachineObject::is_timelapse_storage_low(const std::string& storage) const
{
return m_storage && m_storage->is_timelapse_storage_low(storage);
}
int MachineObject::command_ipcam_check_timelapse_storage(const std::string& storage, int total_layer)
{
json j;
j["camera"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
j["camera"]["command"] = "ipcam_get_media_info";
j["camera"]["sub_command"] = "is_timelapse_storage_enough";
j["camera"]["storage"] = storage;
j["camera"]["total_layer"] = total_layer;
return this->publish_json(j);
}
int MachineObject::command_ipcam_delete_oldest_timelapse(const std::string& storage, int total_layer)
{
json j;
j["camera"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
j["camera"]["command"] = "ipcam_delete_oldest_timelapse";
j["camera"]["storage"] = storage;
j["camera"]["total_layer"] = total_layer;
return this->publish_json(j);
}
int MachineObject::command_purification_disable()
{
json j;
j["print"]["command"] = "close_air_filt";
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
return this->publish_json(j, 1);
}
int MachineObject::command_dont_remind_next_time(json& mqtt_guard_json)
{
if (!mqtt_guard_json.contains("command") ||
!mqtt_guard_json.contains("err_index") ||
mqtt_guard_json["err_index"].empty()) return -1;
json j;
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
try {
j["print"]["command"] = mqtt_guard_json["command"].get<std::string>();
int err_index = mqtt_guard_json["err_index"].get<int>();
if (mqtt_guard_json.contains("err_ignored") &&
mqtt_guard_json["err_ignored"].is_array()) {
j["print"]["err_ignored"] = mqtt_guard_json["err_ignored"];
j["print"]["err_ignored"].push_back(err_index);
} else {
j["print"]["err_ignored"] = std::vector<int>{err_index};
}
for (auto& item : j["print"]["err_ignored"]) {
if (!item.is_number_integer()) continue;
json item_json;
item_json["idx"] = item.get<int>();
item_json["mode"] = 1; // 1-next time ignore, 2-always ignore
j["print"]["rm_idx"].push_back(item_json);
}
} catch (const json::exception& e) {
BOOST_LOG_TRIVIAL(error) << "JSON parsing error in command_dont_remind_next_time: " << e.what();
return -1;
}
return this->publish_json(j, 1);
}
int MachineObject::command_xcam_control_ai_monitoring(bool on_off, std::string lvl)
{
bool print_halt = (lvl == "never_halt") ? false:true;
@@ -2379,6 +2604,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;
@@ -2881,6 +3107,7 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
//supported function
m_config->ParseConfig(jj);
m_status->ParseStatus(jj); // Orca: adopt DeviceCore split — populate DevStatus module
if (jj.contains("support_build_plate_marker_detect")) {
if (jj["support_build_plate_marker_detect"].is_boolean()) {
@@ -2904,6 +3131,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()) {
@@ -3008,6 +3236,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()) {
@@ -3105,6 +3335,17 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
if (jj["print_error"].is_number())
print_error = jj["print_error"].get<int>();
}
// Orca: keep the failure-snapshot id only while an error is active, so a stale
// id can't leak into a later unrelated error dialog once the failure clears.
if (print_error <= 0) {
m_print_error_img_id.clear();
}
else if (jj.contains("err2") && jj["err2"].is_object()) {
json err2 = jj["err2"];
if (err2.contains("img_id") && err2["img_id"].is_string()) {
m_print_error_img_id = err2["img_id"].get<std::string>();
}
}
DevStorage::ParseV1_0(jj, m_storage);
@@ -3126,6 +3367,9 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
int flag3 = jj["flag3"].get<int>();
is_support_filament_setting_inprinting = get_flag_bits(flag3, 3);
is_enable_ams_np = get_flag_bits(flag3, 9);
is_support_fila_change_abort = get_flag_bits(flag3, 13); // filament-change Stop button
is_support_ext_change_assist_old = get_flag_bits(flag3, 16); // A/P-series multi-color external change assist
is_support_filament_32_colors = get_flag_bits(flag3, 17);
}
}
if (!key_field_only) {
@@ -3291,6 +3535,16 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
if (!key_field_only) {
/* temperature */
// Orca: adopt DeviceCore split — populate DevAxis/DevChamber modules
// alongside the inline handling (side-effect-free; inline stays authoritative).
// Contained locally so a malformed field cannot abort the whole status parse.
try {
m_axis->ParseAxis(jj);
m_chamber->ParseChamber(jj);
} catch (...) {
BOOST_LOG_TRIVIAL(warning) << "parse_json: DevAxis/DevChamber parse failed";
}
DevBed::ParseV1_0(jj,m_bed);
if (jj.contains("frame_temper")) {
@@ -3731,7 +3985,7 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
update_printer_preset_name();
update_filament_list();
if (jj.contains("ams")) {
DevFilaSystemParser::ParseV1_0(jj, this, m_fila_system, key_field_only);
DevFilaSystemParser::ParseV1_0(jj, this, m_fila_system.get(), key_field_only);
}
/* vitrual tray*/
@@ -4090,6 +4344,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()) {
@@ -4187,6 +4449,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())
@@ -4283,6 +4553,17 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
this->camera_resolution = j["camera"]["resolution"].get<std::string>();
BOOST_LOG_TRIVIAL(info) << "ack of resolution = " << camera_resolution;
}
} else if (j["camera"]["command"].get<std::string>() == "ipcam_get_media_info") {
if (j["camera"].contains("sub_command") &&
j["camera"]["sub_command"].get<std::string>() == "is_timelapse_storage_enough") {
timelapse_storage_check_result = j["camera"].value("result", -1);
timelapse_storage_is_enough = j["camera"].value("is_enough", true);
timelapse_storage_file_count = j["camera"].value("file_count", 0);
timelapse_storage_check_done = true;
BOOST_LOG_TRIVIAL(info) << "timelapse storage check: result=" << timelapse_storage_check_result
<< " is_enough=" << timelapse_storage_is_enough
<< " file_count=" << timelapse_storage_file_count;
}
}
}
}
@@ -4374,6 +4655,40 @@ void MachineObject::set_ctt_dlg( wxString text){
}
}
void MachineObject::show_unsupported_dlg(int code)
{
// why: a dead control invites repeat clicks, and the frame is modeless - without the guard
// every click stacks another one. Same shape as set_ctt_dlg above, including the reset on
// both hide and close so a dismissed dialog can reappear on the next attempt.
if (m_unsupported_dlg_shown) {
return;
}
m_unsupported_dlg_shown = true;
// why: two codes so the user learns which kind of dead end this is - the slicer having no
// translation for the command, or the printer's own config lacking the hardware to run it.
const wxString text = (code == ORCA_NETWORK_ERR_CAP_NOT_AVAILABLE) ?
_L("This printer is not configured with the hardware this control needs.") :
_L("This control is not supported on this printer.");
// note: constructed directly rather than through CallAfter because every publish_json caller
// is on the UI thread - clicks come from wx handlers, and the agent marshals its own push
// callbacks back to main before parse_json runs. set_ctt_dlg relies on the same property.
auto unsupported_dlg = new GUI::SecondaryCheckDialog(nullptr, wxID_ANY, _L("Warning"),
GUI::SecondaryCheckDialog::VisibleButtons::ONLY_CONFIRM);
unsupported_dlg->update_text(text);
unsupported_dlg->Bind(wxEVT_SHOW, [this](auto& e) {
if (!e.IsShown()) {
m_unsupported_dlg_shown = false;
}
});
unsupported_dlg->Bind(wxEVT_CLOSE_WINDOW, [this](auto& e) {
e.Skip();
m_unsupported_dlg_shown = false;
});
unsupported_dlg->on_show();
}
int MachineObject::publish_gcode(std::string gcode_str)
{
json j;
@@ -4887,6 +5202,15 @@ bool MachineObject::check_enable_np(const json& print) const
return false;
}
// Max filament color count for the send gate; 0 = no explicit upper bound.
int MachineObject::get_max_filament_color_count() const
{
if (is_support_filament_32_colors) return 32;
if (is_enable_ams_np && !is_series_x()) return 20;
if (!is_series_x() && !is_series_o()) return 16;
return 0;
}
void MachineObject::parse_new_info(json print)
{
is_enable_np = check_enable_np(print);
@@ -5012,6 +5336,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);
@@ -5039,6 +5364,23 @@ 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_pa_mode = (get_flag_bits_no_border(fun2, 3) == 1);
is_support_remote_dry = (get_flag_bits_no_border(fun2, 5) == 1);
is_support_check_track_switch_match_slice_printer = get_flag_bits_no_border(fun2, 19) == 1;
if (DevPrinterConfigUtil::support_print_check_firmware_for_tpu_left(printer_type)) {
m_firmware_support_print_tpu_left = get_flag_bits_no_border(fun2, 7) == 1;
}
}
/* Per-filament-index AMS slot mapping reported by the printer (task-level state) */
if (print.contains("mapping") && print["mapping"].is_array()) {
std::vector<uint16_t> new_mapping;
new_mapping.reserve(print["mapping"].size());
for (const auto& v : print["mapping"]) {
new_mapping.push_back(static_cast<uint16_t>(v.get<unsigned>()));
}
print_job_filament_mapping = std::move(new_mapping);
}
/*aux*/
@@ -5048,6 +5390,7 @@ void MachineObject::parse_new_info(json print)
if (!aux.empty()) {
m_storage->set_sdcard_state(get_flag_bits(aux, 12, 2));
m_has_timelapse_kit = (get_flag_bits(aux, 26, 1) == 1);
}
/*stat*/
@@ -5074,7 +5417,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); }
@@ -5523,7 +5869,9 @@ wxString MachineObject::get_nozzle_replace_url() const
return link_map["en"].get<wxString>();
}/*retry with en*/
return "https://wiki.bambulab.com/en/h2/maintenance/replace-hotend";
// Orca: no neutral wiki equivalent for this fallback — return empty so the caller hides the link
// (PrinterPartsDialog::OnWikiClicked reports "No wiki link available" instead of opening a browser)
return wxEmptyString;
}
std::string MachineObject::get_error_code_str(int error_code)
+83 -8
View File
@@ -3,11 +3,13 @@
#include <map>
#include <mutex>
#include <atomic>
#include <vector>
#include <string>
#include <memory>
#include <chrono>
#include <unordered_set>
#include <optional>
#include <boost/thread.hpp>
#include <boost/nowide/fstream.hpp>
#include "nlohmann/json.hpp"
@@ -20,6 +22,7 @@
#include "DeviceCore/DevDefs.h"
#include "DeviceCore/DevConfigUtil.h"
#include "DeviceCore/DevFirmware.h"
#include "DeviceCore/DevCalib.h" // Orca: adopt DeviceCore split (defines DevCalib, CalibStatus, ManualPaCaliMethod)
#include "DeviceErrorDialog.hpp"
#include <wx/object.h>
@@ -61,11 +64,7 @@ class DeviceErrorDialog; // Previous definitions
}
class NetworkAgent;
enum ManualPaCaliMethod {
PA_LINE = 0,
PA_PATTERN,
};
// Orca: ManualPaCaliMethod now provided by DeviceCore/DevCalib.h (enum class)
#define UpgradeNoError 0
#define UpgradeDownloadFailed -1
@@ -76,21 +75,31 @@ enum ManualPaCaliMethod {
// Previous definitions
class DevAms;
class DevAmsTray;
class DevAxis; // Orca: adopt DeviceCore split
class DevBed;
class DevChamber; // Orca: adopt DeviceCore split
class DevConfig;
class DevCtrl;
class DevExtensionTool;
class DevExtderSystem;
class DevFan;
class DevFilaSystem;
class DevFilaSwitch;
class DevPrintOptions;
class DevHMS;
class DevLamp;
class DevNozzleSystem;
class DevNozzleMappingCtrl;
class DeviceManager;
class DevStatus; // Orca: adopt DeviceCore split
class DevStorage;
class DevUpgrade; // Orca: adopt DeviceCore split
struct DevPrintTaskRatingInfo;
// Returns true when filament_id (e.g. "GFA11", "GFU00") is on the stringing-prone list for the
// given nozzle diameter (mm), bucketed per nozzle size to mirror the printer firmware.
bool is_stringing_prone_filament(const std::string& filament_id, float nozzle_diameter);
class MachineObject
{
@@ -114,11 +123,21 @@ private:
std::shared_ptr<DevExtensionTool> m_extension_tool;
DevExtderSystem* m_extder_system;
DevNozzleSystem* m_nozzle_system;
DevFilaSystem* m_fila_system;
std::shared_ptr<DevFilaSystem> m_fila_system;
DevFilaSwitch* m_fila_switch;
DevFan* m_fan;
DevBed * m_bed;
DevStorage* m_storage;
/* Orca: adopt DeviceCore split — axis/calib/chamber/status/upgrade modules alongside
MachineObject's inline handling of these concerns, which stays authoritative here (Orca keeps
the inline model permanently; the split modules coexist with it via accessors). */
std::shared_ptr<DevAxis> m_axis;
std::shared_ptr<DevChamber> m_chamber;
DevCalib* m_calib{ nullptr };
DevStatus* m_status{ nullptr };
std::shared_ptr<DevUpgrade> m_upgrade;
/*Ctrl*/
DevCtrl* m_ctrl;
@@ -131,11 +150,28 @@ 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();
void set_agent(NetworkAgent* agent) { m_agent = agent; }
NetworkAgent* get_agent() const { return m_agent; } // Orca: needed by DeviceCore modules (DevAxisCtrl)
// Orca: these DeviceCore module accessors are unwired on the read side — axis/chamber/status
// are fed every MQTT push but no GUI consumer reads them yet, and for calib/upgrade the inline
// parse in DeviceManager.cpp remains authoritative. Do not wire DevUpgrade naively: its
// ParseUpgradeDisplayState would duplicate the inline block's dis_state==3 ->
// command_get_version CallAfter side effect.
std::shared_ptr<DevAxis> GetAxis() const { return m_axis; }
std::shared_ptr<DevChamber> GetChamber() const { return m_chamber; }
DevCalib* GetCalib() const { return m_calib; }
DevStatus* GetStatus() const { return m_status; }
std::weak_ptr<DevUpgrade> GetUpgrade() const { return m_upgrade; }
public:
enum ActiveState {
@@ -236,9 +272,11 @@ public:
bool m_is_online;
bool m_lan_mode_connection_state{false};
bool m_set_ctt_dlg{ false };
bool m_unsupported_dlg_shown{ false };
void set_lan_mode_connection_state(bool state) {m_lan_mode_connection_state = state;};
bool get_lan_mode_connection_state() {return m_lan_mode_connection_state;};
void set_ctt_dlg( wxString text);
void show_unsupported_dlg(int code);
int parse_msg_count = 0;
int keep_alive_count = 0;
std::chrono::system_clock::time_point last_update_time; /* last received print data from machine */
@@ -329,7 +367,12 @@ public:
DevNozzleSystem* GetNozzleSystem() const { return m_nozzle_system;}
DevFilaSystem* GetFilaSystem() const { return m_fila_system;}
/* print-dispatch nozzle mapping (H2C hotend rack); result stays empty for non-rack printers */
std::shared_ptr<DevNozzleMappingCtrl> get_nozzle_mapping_result() const { return m_nozzle_mapping_ptr; }
void clear_auto_nozzle_mapping();// defined in DevMappingNozzle.cpp
std::shared_ptr<DevFilaSystem> GetFilaSystem() const { return m_fila_system;}
DevFilaSwitch* GetFilaSwitch() const { return m_fila_switch;}
bool HasAms() const;
DevLamp* GetLamp() const { return m_lamp; }
@@ -365,6 +408,10 @@ public:
DevFirmwareVersionInfo laser_version_info;
DevFirmwareVersionInfo cutting_module_version_info;
DevFirmwareVersionInfo extinguish_version_info;
DevFirmwareVersionInfo rotary_version_info;
DevFirmwareVersionInfo exhaustfan_version_info;
DevFirmwareVersionInfo amshub_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;
@@ -402,6 +449,7 @@ public:
bool is_system_printing();
int print_error;
std::string m_print_error_img_id;
static std::string get_error_code_str(int error_code);
std::string get_print_error_str() const { return MachineObject::get_error_code_str(this->print_error); }
@@ -542,6 +590,7 @@ public:
bool file_model_download{false};
bool virtual_camera{false};
bool m_has_timelapse_kit{false};
bool xcam_ai_monitoring{ false };
bool xcam_disable_ai_detection_display{false};
@@ -618,8 +667,18 @@ public:
bool is_support_airprinting_detection{false};
bool is_support_idelheadingprotect_detection{false};
// timelapse storage check result (temp state from MQTT response)
std::atomic<bool> timelapse_storage_check_done { false };
int timelapse_storage_check_result { -1 };
bool timelapse_storage_is_enough { true };
int timelapse_storage_file_count { 0 };
// fun2
bool is_support_print_with_emmc{false};
bool is_support_remote_dry = false;
bool is_support_check_track_switch_match_slice_printer{false};
bool is_support_pa_mode{false};
std::optional<bool> m_firmware_support_print_tpu_left;
bool installed_upgrade_kit{false};
int bed_temperature_limit = -1;
@@ -649,6 +708,11 @@ public:
boost::thread* get_slice_info_thread { nullptr };
boost::thread* get_model_task_thread { nullptr };
// Per-filament-index AMS slot mapping reported by the printer in print.mapping. Up to
// 32 entries, each value packs (ams_id << 8) | slot_id; 0xFFFF means unused.
std::vector<uint16_t> print_job_filament_mapping;
bool any_loaded_filament_is_stringing_prone() const;
/* job attr */
int jobState_ = 0;
@@ -686,6 +750,7 @@ public:
/* quick check*/
bool canEnableTimelapse(wxString& error_message) const;
bool is_timelapse_storage_low(const std::string& storage) const;
/* command commands */
int command_get_version(bool with_retry = true);
@@ -697,6 +762,8 @@ public:
int command_set_printer_nozzle2(int id, std::string nozzle_type, float diameter);
int command_get_access_code();
int command_ack_proceed(json& proceed);
int command_purification_disable();
int command_dont_remind_next_time(json& mqtt_guard_json);
/* command upgrade */
int command_upgrade_confirm();
@@ -733,7 +800,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);
@@ -788,6 +855,8 @@ public:
int command_ipcam_record(bool on_off);
int command_ipcam_timelapse(bool on_off);
int command_ipcam_resolution_set(std::string resolution);
int command_ipcam_check_timelapse_storage(const std::string& storage, int total_layer);
int command_ipcam_delete_oldest_timelapse(const std::string& storage, int total_layer);
int command_xcam_control(std::string module_name, bool on_off, std::string lvl = "");
//refine printer
@@ -861,6 +930,12 @@ public:
/*for more extruder*/
bool is_enable_np{ false };
bool is_enable_ams_np{ false };
bool is_support_filament_32_colors{ false };
bool is_support_fila_change_abort{ false }; // filament-change Stop button
bool is_support_ext_change_assist_old{ false }; // A/P-series multi-color external change assist
// Max filament color count for the send gate; returns 0 when there is no explicit upper bound.
int get_max_filament_color_count() const;
/**
* Virtual Tray (vt_slot) - External/manual filament loading slots.
+12
View File
@@ -2,7 +2,19 @@
list(APPEND SLIC3R_GUI_SOURCES
GUI/DeviceTab/uiAmsHumidityPopup.h
GUI/DeviceTab/uiAmsHumidityPopup.cpp
GUI/DeviceTab/uiAMSBestPositionPopup.hpp
GUI/DeviceTab/uiAMSBestPositionPopup.cpp
GUI/DeviceTab/uiDeviceUpdateVersion.h
GUI/DeviceTab/uiDeviceUpdateVersion.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
GUI/DeviceTab/wgtMsgPanel.h
GUI/DeviceTab/wgtMsgPanel.cpp
)
set(SLIC3R_GUI_SOURCES ${SLIC3R_GUI_SOURCES} PARENT_SCOPE)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,226 @@
//**********************************************************/
/* File: uiAMSBestPositionPopup.hpp
* Description: The popup with suggest best ams position
*
//**********************************************************/
#pragma once
#include "slic3r/GUI/Widgets/AMSItem.hpp"
#include "slic3r/GUI/Widgets/Label.hpp"
#include "slic3r/GUI/Widgets/PopupWindow.hpp"
#include "slic3r/GUI/wxExtensions.hpp"
#include "slic3r/GUI/Widgets/Button.hpp"
#include "slic3r/GUI/DeviceCore/DevFilaSystem.h"
#include "slic3r/GUI/DeviceCore/DevFilaSwitch.h"
#include <algorithm>
#include <tuple>
namespace Slic3r { namespace GUI {
class UiStyledAMSPanel : public wxPanel
{
public:
UiStyledAMSPanel(wxWindow* parent,
wxWindowID id = wxID_ANY,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
const wxColour& borderColor = wxColour(200, 200, 200),
const wxColour& bgColor = wxColour(255, 255, 255),
bool borderDashed = true,
wxString name = "",
bool isTop = false);
protected:
void OnPaint(wxPaintEvent& event);
private:
bool m_borderDashed;
int m_borderWidth;
int m_radius;
wxColour m_borderColor;
wxColour m_bgColor;
wxString m_name;
bool m_isTop;
};
class UiStyledSwitchPanel : public wxPanel
{
public:
UiStyledSwitchPanel(wxWindow* parent,
wxWindowID id,
const wxPoint& pos,
const wxSize& size,
const wxColour& borderColor,
const wxColour& bgColor,
bool borderDashed,
int borderWidth,
int radius,
bool isTop);
void AddToLeft(wxWindow* window, int proportion = 0, int flag = wxEXPAND, int border = 0);
void AddToRight(wxWindow* window, int proportion = 0, int flag = wxEXPAND, int border = 0);
void Clear(bool deleteWindows);
wxSizer* GetLeftSizer() { return m_leftSizer; }
wxSizer* GetRightSizer() { return m_rightSizer; }
void LayoutAndFit()
{
m_leftSizer->Layout();
// m_leftSizer->Fit();
m_rightSizer->Layout();
// m_rightSizer->Fit();
m_splitSizer->Layout();
// m_splitSizer->Fit();
m_contentSizer->Layout();
// m_contentSizer->Fit();
m_mainSizer->Layout();
// m_mainSizer->Fit();
Fit();
}
protected:
void OnPaint(wxPaintEvent& event);
private:
bool m_borderDashed;
int m_borderWidth;
int m_radius;
wxColour m_borderColor;
wxColour m_bgColor;
bool m_isTop;
wxBoxSizer* m_mainSizer{nullptr};
wxBoxSizer* m_contentSizer{nullptr};
wxBoxSizer* m_splitSizer{nullptr};
wxSizer* m_leftSizer{nullptr};
wxSizer* m_rightSizer{nullptr};
static constexpr int labelHeight = 30;
};
enum DataStatusType {
ADJUST,
OK,
UNMATCHED
};
class UiAMSSlot : public wxPanel
{
public:
UiAMSSlot(wxWindow* parent,
const std::vector<wxColour>& bgColours,
const wxString& text,
DataStatusType status,
wxWindowID id = wxID_ANY,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
double colourFactor = 1.0,
double scaleFactor = 1.0);
private:
void OnPaint(wxPaintEvent&);
void DrawRectangle(wxPaintDC& dc, const wxSize& cli);
void DrawLine(wxPaintDC& dc, const wxSize& cli);
wxColour LightenColour(const wxColour& original);
bool IsDark(const wxColour& c)
{
int brightness = (c.Red() * 299 + c.Green() * 587 + c.Blue() * 114) / 1000;
return brightness < 128; // 0-255 range, 128 mid
}
private:
// Copied, not referenced: the slot repaints long after construction, so it must not
// depend on the caller's vector staying alive.
std::vector<wxColour> m_bgColours;
wxString m_text;
DataStatusType m_status;
wxSize m_size;
ScalableBitmap *m_ams_slot_readonly{nullptr};
int m_rectangleW = 44;
int m_rectangleH = 62;
int rectangleW = 44;
int rectangleH = 62;
double m_colourFactor = 1.0f;
double m_scaleFactor = 1.0f;
// wxDECLARE_EVENT_TABLE();
};
struct DataAmsSlotInfo
{
wxString amsName;
wxString name;
std::vector<wxColour> colours;
double colourFactor;
double scaleFactor;
DataStatusType status;
};
class UiAMS : public UiStyledAMSPanel
{
public:
UiAMS(wxWindow* parent,
const std::vector<DataAmsSlotInfo>& amsInfo,
wxWindowID id,
const wxPoint& pos,
const wxSize& minSize);
private:
void init();
std::vector<DataAmsSlotInfo> m_amsInfo;
wxString m_amsTitle;
wxSize m_minSize;
};
struct DataStatusParam {
// int count = 0;
int width = 0;
int height = 0;
std::vector<DataAmsSlotInfo> slots;
};
class ReselectMachineDialog : public wxDialog
{
public:
ReselectMachineDialog(wxWindow* parent);
~ReselectMachineDialog();
void Update(MachineObject* obj,
const std::map<int, int>& best_pos_map,
const std::vector<FilamentInfo>& ams_mapping,
wxString save_time);
private:
int CaculateSwitcherDistribution(MachineObject* obj, const std::map<int, int>& best_pos_map, const std::vector<FilamentInfo>& ams_mapping);
wxString getTrayID(MachineObject* obj, const std::string& amsID, const std::string& slotID);
void OnRefreshButton(wxCommandEvent& event);
private:
int saveTimes{0};
wxBoxSizer* mainSizer{nullptr};
wxPanel* textPanel{nullptr};
wxBoxSizer* textSizer{nullptr};
Label* suggestText{nullptr};
// wxHyperlinkCtrl* linkwiki{nullptr};
Label* linkwiki{nullptr};
Label* summaryText{nullptr};
UiStyledSwitchPanel* filamentSwitch{nullptr};
wxStaticText* filamentTips{nullptr};
std::vector<std::vector<DataAmsSlotInfo>> inAAMS{};
std::vector<std::vector<DataAmsSlotInfo>> inBAMS{};
wxPanel* statusBar{nullptr};
wxBoxSizer* btnSizer{nullptr};
Button* m_buttonClose{nullptr };
Button* m_buttonRefresh{ nullptr };
std::vector<wxColour>colourAdjust{wxColour("#675AFF")};
std::vector<wxColour>colourOK{wxColour("#FF8181")};
std::vector<wxColour>colourUnused{wxColour("#FF818140")};
// wxStaticBitmap* m_bitmapSelectMachine{nullptr};
};
wxDECLARE_EVENT(wxEVT_REFRESH_DATA, wxCommandEvent);
}} // namespace Slic3r::GUI
@@ -1,9 +1,9 @@
//**********************************************************/
/* File: uiAmsHumidityPopup.cpp
/**********************************************************
* File: uiAmsHumidityPopup.cpp
* Description: The popup with DevAms Humidity
*
* \n class uiAmsHumidityPopup
//**********************************************************/
**********************************************************/
#include "uiAmsHumidityPopup.h"
@@ -23,16 +23,16 @@ uiAmsPercentHumidityDryPopup::uiAmsPercentHumidityDryPopup(wxWindow *parent)
: wxDialog(parent, wxID_ANY, "")
{
Create();
wxGetApp().UpdateDlgDarkUI(this);
wxGetApp().UpdateDlgDarkUI(this); // Orca: apply dark-mode theming to the popup
}
void uiAmsPercentHumidityDryPopup::Create()
{
// create images
idle_img = ScalableBitmap(this, "ams_drying", 16);
drying_img = ScalableBitmap(this, "ams_is_drying", 16);
drying_img = ScalableBitmap(this, "ams_is_drying", 16); // Orca: use the drying-state icon
// background
// background
SetBackgroundColour(*wxWHITE);
// create title sizer
@@ -147,7 +147,8 @@ void uiAmsPercentHumidityDryPopup::UpdateContents()
// table grid
const wxString& humidity_str = wxString::Format("%d%%", m_humidity_percent);
m_humidity_label->SetLabel(humidity_str);
const wxString& temp_str = wxString::Format(wxString::FromUTF8(u8"%d\u2103" /* °C */), (int)std::round(m_current_temperature));
// Orca: format the temperature with an explicit UTF-8 \u2103 (no translation of a units-only format)
const wxString& temp_str = wxString::Format(wxString::FromUTF8(u8"%d\u2103" /* \u00b0C */), (int)std::round(m_current_temperature));
m_temperature_label->SetLabel(temp_str);
if (m_left_dry_time > 0)
@@ -190,4 +191,4 @@ void uiAmsPercentHumidityDryPopup::msw_rescale()
} // namespace GUI
} // namespace Slic3r
} // namespace Slic3r
@@ -1,9 +1,9 @@
//**********************************************************/
/* File: uiAmsHumidityPopup.h
/**********************************************************
* File: uiAmsHumidityPopup.h
* Description: The popup with DevAms Humidity
*
* \n class uiAmsHumidityPopup
//**********************************************************/
**********************************************************/
#pragma once
#include "slic3r/GUI/Widgets/AMSItem.hpp"
@@ -20,7 +20,7 @@ namespace Slic3r { namespace GUI {
struct uiAmsHumidityInfo
{
std::string ams_id;
AMSModel ams_type;
AMSModel ams_type; // Orca: GUI-layer AMS enum; consumer (AMSItem.cpp) assigns an AMSModel
int humidity_display_idx = -1;
int humidity_percent = -1;
float current_temperature;
@@ -68,7 +68,7 @@ private:
wxStaticBitmap* m_dry_state_img;
Label* m_dry_state;
Label* m_humidity_header;
Label* m_humidity_label;
@@ -81,4 +81,4 @@ private:
wxSizer* m_sizer;
};
}} // namespace Slic3r::GUI
}} // namespace Slic3r::GUI
@@ -1,15 +1,16 @@
//**********************************************************/
/* File: uiDeviceUpdateVersion.cpp
/**********************************************************
* File: uiDeviceUpdateVersion.cpp
* Description: The panel with firmware info
*
* \n class uiDeviceUpdateVersion
//**********************************************************/
**********************************************************/
#include "uiDeviceUpdateVersion.h"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/wxExtensions.hpp"
#include "slic3r/GUI/Widgets/Label.hpp"
#include "slic3r/GUI/Widgets/Label.hpp" // Orca: explicit Label include
#include <wx/stattext.h>
@@ -27,6 +28,7 @@ uiDeviceUpdateVersion::uiDeviceUpdateVersion(wxWindow* parent,
long style /*= wxTAB_TRAVERSAL*/)
: wxPanel(parent, id, pos, size, style)
{
SetBackgroundColour(*wxWHITE);
CreateWidgets();
}
@@ -81,7 +83,6 @@ void uiDeviceUpdateVersion::CreateWidgets()
serial_text->SetFont(font);
version_text->SetFont(font);
model_text->SetFont(font);
// The grid sizer
wxFlexGridSizer* grid_sizer = new wxFlexGridSizer(0, 2, 0, 0);
//grid_sizer->AddGrowableCol(1);
@@ -103,12 +104,14 @@ void uiDeviceUpdateVersion::CreateWidgets()
grid_sizer->Add(version_hsizer, 0, wxEXPAND, 0);
grid_sizer->Add(m_dev_version, 0, wxEXPAND | wxALL, FromDIP(5));
// Updating
wxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
main_sizer->AddSpacer(FromDIP(40));
main_sizer->AddSpacer(FromDIP(40)); // Orca: top spacing above the firmware-info grid
main_sizer->Add(grid_sizer, 0, wxALIGN_LEFT, FromDIP(5));
SetSizer(main_sizer);
Layout();
}
wxGetApp().UpdateDarkUIWin(this);
}
@@ -1,9 +1,9 @@
//**********************************************************/
/* File: uiDeviceUpdateVersion.h
/**********************************************************
* File: uiDeviceUpdateVersion.h
* Description: The panel with firmware info
*
* \n class uiDeviceUpdateVersion
//**********************************************************/
**********************************************************/
#pragma once
#include <wx/panel.h>
@@ -44,4 +44,4 @@ private:
wxStaticText* m_dev_version;
wxStaticBitmap* m_dev_upgrade_indicator;
};
};// end of namespace Slic3r::GUI
};// end of namespace Slic3r::GUI
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,255 @@
//**********************************************************/
/* File: wgtDeviceNozzleRack.h
* Description: The panel with rack and nozzles
*
* \n class wgtDeviceNozzleRackArea;
* \n class wgtDeviceNozzleRackNozzleItem;
* \n class wgtDeviceNozzleRackToolHead;
* \n class wgtDeviceNozzleRackPos;
//**********************************************************/
#pragma once
#include "slic3r/GUI/DeviceCore/DevNozzleRack.h"
#include "slic3r/GUI/Widgets/StaticBox.hpp"
#include "slic3r/GUI/Widgets/AnimaController.hpp"
#include <wx/panel.h>
#include <wx/simplebook.h>
#include <memory>
// 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;
}
};
// Events
wxDECLARE_EVENT(EVT_NOZZLE_RACK_NOZZLE_ITEM_SELECTED, wxCommandEvent);
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 };
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 };
};
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,705 @@
//**********************************************************/
/* File: wgtDeviceNozzleRackUpdate.cpp
* Description: The panel with rack updating
*
* \n class wgtDeviceNozzleRackUpdate
//**********************************************************/
#include "wgtDeviceNozzleRackUpdate.h"
#include "slic3r/GUI/DeviceCore/DevNozzleSystem.h"
#include "slic3r/GUI/DeviceCore/DevUpgrade.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 as a literal, no equivalent colour macro in Orca
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->SetBackgroundColour(WGT_DEVICE_NOZZLE_RACK_HOTEND_UPDATE_DEFAULT_BG);
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);
type_panel->SetBackgroundColour(WGT_DEVICE_NOZZLE_RACK_HOTEND_UPDATE_DEFAULT_BG);
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)));
// m_colour_box->SetBackgroundColour(*wxRED);
// type_sizer_row_1->AddStretchSpacer();
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));
// type_sizer_row_1->AddStretchSpacer();
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);
info_panel->SetBackgroundColour(WGT_DEVICE_NOZZLE_RACK_HOTEND_UPDATE_DEFAULT_BG);
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_in_refreh_icon = new ScalableBitmap(this, "refresh_nozzle", 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 the size internally), so none is passed.
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);
// m_status_label->SetForegroundColour(wxColour("#00AE42"));
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);
// m_status_bitmap->Refresh();
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")); // Orca: accent green
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,166 @@
//**********************************************************/
/* File: wgtDeviceNozzleRackUpdate.h
* Description: The panel for updating hotends
*
* \n class wgtDeviceNozzleRackUpdate
//**********************************************************/
#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>
// 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;
// ScalableBitmap* m_in_refreh_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,293 @@
//**********************************************************/
/* File: wgtDeviceNozzleSelect.cpp
* Description: The panel to select nozzle
*
* \n class wgtDeviceNozzleSelect;
//**********************************************************/
#include "wgtDeviceNozzleSelect.h"
#include "wgtDeviceNozzleRack.h"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/DeviceTab/wgtMsgBox.h"
#include "slic3r/GUI/Widgets/Label.hpp" // Orca: explicit Label include
static wxColour s_gray_clr("#B0B0B0");
static wxColour s_hgreen_clr("#009688"); // Orca: accent green
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);
panel->SetBackgroundColour(*wxWHITE);
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(wxColour(0xEE, 0xEE, 0xEE));
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();
wxGetApp().UpdateDarkUIWin(this);
}
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,71 @@
//**********************************************************/
/* File: wgtDeviceNozzleSelect.h
* Description: The panel to select nozzle
*
* \n class wgtDeviceNozzleSelect;
//**********************************************************/
#pragma once
#include "slic3r/GUI/DeviceCore/DevNozzleSystem.h"
#include <wx/panel.h>
#include <memory>
// Previous definitions
class Label;
namespace Slic3r
{
struct DevNozzle;
class DevNozzleRack;
namespace GUI
{
class wgtDeviceNozzleRackNozzleItem;
class wgtMsgBox;
}
};
wxDECLARE_EVENT(EVT_NOZZLE_SELECT_CHANGED, wxCommandEvent);
wxDECLARE_EVENT(EVT_NOZZLE_SELECT_CLICKED, wxCommandEvent);
namespace Slic3r::GUI
{
class wgtDeviceNozzleRackSelect : public wxPanel
{
public:
wgtDeviceNozzleRackSelect(wxWindow* parent);
~wgtDeviceNozzleRackSelect() = default;
public:
int GetSelectedNozzlePosID() const { return m_selected_nozzle.GetNozzlePosId();}
void UpdatSelectedNozzles(std::shared_ptr<DevNozzleRack> rack, std::vector<int> selected_nozzle_pos_vec, bool use_dynamic_switch, std::optional<PrintFromType> print_from_type);// for slicing with dynamic switch
void Rescale();
private:
void CreateGui();
void ClearSelection();
void SetSelectedNozzle(const DevNozzle &nozzle);
void UpdatSelectedNozzle(std::shared_ptr<DevNozzleRack> rack, int selected_nozzle_pos_id = -1); // for slicing without dynamic switch
void UpdateNozzleInfos(std::shared_ptr<DevNozzleRack> rack);
void OnNozzleItemSelected(wxCommandEvent& evt);
private:
DevNozzle m_selected_nozzle;
std::weak_ptr<DevNozzleRack> m_nozzle_rack;
// pick
bool m_enable_manual_nozzle_pick = true;
// Label
wgtMsgBox* m_title_tips_dynamic;
// GUI
wgtDeviceNozzleRackNozzleItem * m_toolhead_nozzle_l{nullptr};
wgtDeviceNozzleRackNozzleItem * m_toolhead_nozzle_r{nullptr};
std::unordered_map<int, wgtDeviceNozzleRackNozzleItem *> m_nozzle_items; // from 16 to 21
};
};// end of namespace Slic3r::GUI
+43
View File
@@ -0,0 +1,43 @@
#include "wgtMsgBox.h"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Widgets/Label.hpp"
#include "slic3r/GUI/wxExtensions.hpp"
#include <wx/sizer.h>
namespace Slic3r::GUI
{
wgtMsgBox::wgtMsgBox(wxWindow* parent)
: StaticBox(parent, wxID_ANY)
{
CreateGUI();
}
void wgtMsgBox::CreateGUI()
{
wxBoxSizer* mainSizer = new wxBoxSizer(wxHORIZONTAL);
m_txt_label = new Label(this);
m_txt_label->SetFont(::Label::Body_13);
m_txt_label->SetBackgroundColour(*wxWHITE);
auto padding_sizer = new wxBoxSizer(wxHORIZONTAL);
padding_sizer->Add(m_txt_label, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5));
mainSizer->Add(padding_sizer, 0, wxALIGN_CENTER_VERTICAL | wxALL, 1);
//Button* btnClose = CreateCloseButton();
//wxButton* btnClose = CreateCloseButton();
//mainSizer->Add(btnClose, 0, wxALIGN_CENTER_VERTICAL);
SetSizer(mainSizer);
mainSizer->Fit(this);
}
void wgtMsgBox::OnCloseClicked(wxCommandEvent& evt)
{
this->Hide();
}
} // namespace Slic3r::GUI

Some files were not shown because too many files have changed in this diff Show More