From e29970a2ede17b9a3e43608a3c691b08efe88c5d Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 14 Mar 2023 13:34:33 +0100 Subject: [PATCH 001/103] Added 'is_serializing' flag to GLGizmoBase::data_changed function --- src/slic3r/GUI/Gizmos/GLGizmoBase.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 4 ++-- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp | 4 ++-- src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoMove.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoScale.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 2 +- 26 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp index 108b869da..aec026f11 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp @@ -175,7 +175,7 @@ public: /// /// Is called when data (Selection) is changed /// - virtual void data_changed(){}; + virtual void data_changed(bool is_serializing){}; /// /// Implement when want to process mouse events in gizmo diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index e9b6fa694..891964a04 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -2496,7 +2496,7 @@ CommonGizmosDataID GLGizmoCut3D::on_get_requirements() const { | int(CommonGizmosDataID::ObjectClipper)); } -void GLGizmoCut3D::data_changed() +void GLGizmoCut3D::data_changed(bool is_serializing) { update_bb(); if (auto oc = m_c->object_clipper()) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 794d0d339..e66262971 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -255,7 +255,7 @@ protected: std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Cut gizmo"); } std::string get_action_snapshot_name() override { return _u8L("Cut gizmo editing"); } - void data_changed() override; + void data_changed(bool is_serializing) override; private: void set_center(const Vec3d& center, bool update_tbb = false); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 0210de3b5..b786e557e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -903,7 +903,7 @@ void GLGizmoEmboss::on_set_state() } } -void GLGizmoEmboss::data_changed() { +void GLGizmoEmboss::data_changed(bool is_serializing) { set_volume_by_selection(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index 2ed6c2000..405137929 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -62,7 +62,7 @@ protected: bool on_is_activable() const override { return true; } bool on_is_selectable() const override { return false; } void on_set_state() override; - void data_changed() override; // selection changed + void data_changed(bool is_serializing) override; // selection changed void on_set_hover_id() override{ m_rotate_gizmo.set_hover_id(m_hover_id); } void on_enable_grabber(unsigned int id) override { m_rotate_gizmo.enable_grabber(); } void on_disable_grabber(unsigned int id) override { m_rotate_gizmo.disable_grabber(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 0c89d7620..11ea96578 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -386,9 +386,9 @@ void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool block) m_parent.set_as_dirty(); } -void GLGizmoFdmSupports::data_changed() +void GLGizmoFdmSupports::data_changed(bool is_serializing) { - GLGizmoPainterBase::data_changed(); + GLGizmoPainterBase::data_changed(is_serializing); if (! m_c->selection_info()) return; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index b79e1dda7..b1cf74384 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -26,7 +26,7 @@ protected: private: bool on_init() override; - void data_changed() override; + void data_changed(bool is_serializing) override; void update_model_object() const override; void update_from_model_object() override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp index 947ef2df8..c62c4994f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp @@ -57,7 +57,7 @@ bool GLGizmoFlatten::on_mouse(const wxMouseEvent &mouse_event) return false; } -void GLGizmoFlatten::data_changed() +void GLGizmoFlatten::data_changed(bool is_serializing) { const Selection & selection = m_parent.get_selection(); const ModelObject *model_object = nullptr; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp index 0444fa217..1701b76a5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp @@ -55,7 +55,7 @@ public: /// Return True when use the information otherwise False. bool on_mouse(const wxMouseEvent &mouse_event) override; - void data_changed() override; + void data_changed(bool is_serializing) override; protected: bool on_init() override; std::string on_get_name() const override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index d04a9cf2f..c0073e7fb 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -42,7 +42,7 @@ bool GLGizmoHollow::on_init() return true; } -void GLGizmoHollow::data_changed() +void GLGizmoHollow::data_changed(bool is_serializing) { if (! m_c->selection_info()) return; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp index abfb2503f..025e70b64 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp @@ -24,7 +24,7 @@ class GLGizmoHollow : public GLGizmoSlaBase { public: GLGizmoHollow(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); - void data_changed() override; + void data_changed(bool is_serializing) override; bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); void delete_selected_points(); bool is_selection_rectangle_dragging() const override { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp index 80520651d..bf10ec7a7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp @@ -455,7 +455,7 @@ bool GLGizmoMeasure::on_mouse(const wxMouseEvent &mouse_event) return false; } -void GLGizmoMeasure::data_changed() +void GLGizmoMeasure::data_changed(bool is_serializing) { m_parent.toggle_sla_auxiliaries_visibility(false, nullptr, -1); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp index 4652a171b..02acc7bb5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp @@ -155,7 +155,7 @@ public: /// Return True when use the information otherwise False. bool on_mouse(const wxMouseEvent &mouse_event) override; - void data_changed() override; + void data_changed(bool is_serializing) override; bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index fd7963050..516a2b258 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -147,9 +147,9 @@ void GLGizmoMmuSegmentation::render_painter_gizmo() glsafe(::glDisable(GL_BLEND)); } -void GLGizmoMmuSegmentation::data_changed() +void GLGizmoMmuSegmentation::data_changed(bool is_serializing) { - GLGizmoPainterBase::data_changed(); + GLGizmoPainterBase::data_changed(is_serializing); if (m_state != On || wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptFFF || wxGetApp().extruders_edited_cnt() <= 1) return; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp index 67eccd8e8..14f0cc4dd 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp @@ -88,7 +88,7 @@ public: void render_painter_gizmo() override; - void data_changed() override; + void data_changed(bool is_serializing) override; void render_triangles(const Selection& selection) const override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 98f183b74..13d7a3aac 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -52,7 +52,7 @@ bool GLGizmoMove3D::on_mouse(const wxMouseEvent &mouse_event) { return use_grabbers(mouse_event); } -void GLGizmoMove3D::data_changed() { +void GLGizmoMove3D::data_changed(bool is_serializing) { m_grabbers[2].enabled = !m_parent.get_selection().is_wipe_tower(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp index cd92d7472..104cba889 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp @@ -51,7 +51,7 @@ public: /// /// Detect reduction of move for wipetover on selection change /// - void data_changed() override; + void data_changed(bool is_serializing) override; protected: bool on_init() override; std::string on_get_name() const override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 3d88ad500..967d654d1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -32,7 +32,7 @@ GLGizmoPainterBase::~GLGizmoPainterBase() s_sphere.reset(); } -void GLGizmoPainterBase::data_changed() +void GLGizmoPainterBase::data_changed(bool is_serializing) { if (m_state != On) return; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp index 44c7f40ab..4720569f8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -82,7 +82,7 @@ private: public: GLGizmoPainterBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); ~GLGizmoPainterBase() override; - void data_changed() override; + void data_changed(bool is_serializing) override; virtual bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); // Following function renders the triangles and cursor. Having this separated diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 345d733af..9ce7957a2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -601,7 +601,7 @@ bool GLGizmoRotate3D::on_mouse(const wxMouseEvent &mouse_event) return use_grabbers(mouse_event); } -void GLGizmoRotate3D::data_changed() { +void GLGizmoRotate3D::data_changed(bool is_serializing) { if (m_parent.get_selection().is_wipe_tower()) { #if !ENABLE_WORLD_COORDINATE const DynamicPrintConfig& config = wxGetApp().preset_bundle->prints.get_edited_preset().config; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp index ebfed1920..7ae4ce726 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -146,7 +146,7 @@ public: /// Return True when use the information otherwise False. bool on_mouse(const wxMouseEvent &mouse_event) override; - void data_changed() override; + void data_changed(bool is_serializing) override; protected: bool on_init() override; std::string on_get_name() const override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 9e5191f65..395bda40d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -106,7 +106,7 @@ void GLGizmoScale3D::enable_ununiversal_scale(bool enable) m_grabbers[i].enabled = enable; } -void GLGizmoScale3D::data_changed() +void GLGizmoScale3D::data_changed(bool is_serializing) { #if ENABLE_WORLD_COORDINATE set_scale(Vec3d::Ones()); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index fe6ab4972..73dcecfe7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -82,7 +82,7 @@ public: /// Return True when use the information otherwise False. bool on_mouse(const wxMouseEvent &mouse_event) override; - void data_changed() override; + void data_changed(bool is_serializing) override; void enable_ununiversal_scale(bool enable); protected: virtual bool on_init() override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 4d76dfb04..bd7d7fec9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -48,7 +48,7 @@ bool GLGizmoSlaSupports::on_init() return true; } -void GLGizmoSlaSupports::data_changed() +void GLGizmoSlaSupports::data_changed(bool is_serializing) { if (! m_c->selection_info()) return; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index 578858b5b..d63e04d86 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -54,7 +54,7 @@ private: public: GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); virtual ~GLGizmoSlaSupports() = default; - void data_changed() override; + void data_changed(bool is_serializing) override; bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); void delete_selected_points(bool force = false); //ClippingPlane get_sla_clipping_plane() const; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index af93c07df..361fd7792 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -223,7 +223,7 @@ void GLGizmosManager::update_data() m_common_gizmos_data->update(get_current() ? get_current()->get_requirements() : CommonGizmosDataID(0)); - if (m_current != Undefined) m_gizmos[m_current]->data_changed(); + if (m_current != Undefined) m_gizmos[m_current]->data_changed(m_serializing); } bool GLGizmosManager::is_running() const From 4c0e7d9683d5b85b876e435d6e709b731b148158 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Thu, 23 Mar 2023 16:29:23 +0100 Subject: [PATCH 002/103] Set volume only by function data_changed. --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 35 ++++++++++++++----------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index c74ab2e9d..656f174d3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -521,7 +521,7 @@ static void draw_mouse_offset(const std::optional &offset) void GLGizmoEmboss::on_render_input_window(float x, float y, float bottom_limit) { - set_volume_by_selection(); + assert(m_volume != nullptr); // Do not render window for not selected text volume if (m_volume == nullptr || get_model_volume(m_volume_id, m_parent.get_selection().get_model()->objects) == nullptr || @@ -665,20 +665,9 @@ void GLGizmoEmboss::on_set_state() // to reload fonts from system, when install new one wxFontEnumerator::InvalidateCache(); - // Try(when exist) set text configuration by volume - set_volume_by_selection(); - - // when open window by "T" and no valid volume is selected, so Create new one - if (m_volume == nullptr || - get_model_volume(m_volume_id, m_parent.get_selection().get_model()->objects) == nullptr ) { - // reopen gizmo when new object is created - GLGizmoBase::m_state = GLGizmoBase::Off; - if (wxGetApp().get_mode() == comSimple || wxGetApp().obj_list()->has_selected_cut_object()) - // It's impossible to add a part in simple mode - return; - // start creating new object - create_volume(ModelVolumeType::MODEL_PART); - } + // Immediately after set state On is called function data_changed(), + // where one could distiguish undo/redo serialization from opening by letter 'T' + //set_volume_by_selection(); // change position of just opened emboss window if (m_allow_open_near_volume) { @@ -696,8 +685,22 @@ void GLGizmoEmboss::on_set_state() } } -void GLGizmoEmboss::data_changed(bool is_serializing) { +void GLGizmoEmboss::data_changed(bool is_serializing) { + if (is_serializing) + reset_volume(); + set_volume_by_selection(); + + // when open window by "T" and no valid volume is selected, so Create new one + if (!is_serializing && m_volume == nullptr) { + // reopen gizmo when new object is created + close(); + if (wxGetApp().get_mode() == comSimple || wxGetApp().obj_list()->has_selected_cut_object()) + // It's impossible to add a part in simple mode + return; + // start creating new object + create_volume(ModelVolumeType::MODEL_PART); + } } void GLGizmoEmboss::on_start_dragging() { m_rotate_gizmo.start_dragging(); } From 51db683c9d5d29e8dce2b8fafbe205fc07aa8c76 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Thu, 23 Mar 2023 18:05:26 +0100 Subject: [PATCH 003/103] Prevent creation volume on undo redo action. --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 4 +++- src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 656f174d3..b5b02532b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -521,6 +521,7 @@ static void draw_mouse_offset(const std::optional &offset) void GLGizmoEmboss::on_render_input_window(float x, float y, float bottom_limit) { + m_is_just_opened = false; assert(m_volume != nullptr); // Do not render window for not selected text volume if (m_volume == nullptr || @@ -668,6 +669,7 @@ void GLGizmoEmboss::on_set_state() // Immediately after set state On is called function data_changed(), // where one could distiguish undo/redo serialization from opening by letter 'T' //set_volume_by_selection(); + m_is_just_opened = true; // change position of just opened emboss window if (m_allow_open_near_volume) { @@ -692,7 +694,7 @@ void GLGizmoEmboss::data_changed(bool is_serializing) { set_volume_by_selection(); // when open window by "T" and no valid volume is selected, so Create new one - if (!is_serializing && m_volume == nullptr) { + if (!is_serializing && m_is_just_opened && m_volume == nullptr) { // reopen gizmo when new object is created close(); if (wxGetApp().get_mode() == comSimple || wxGetApp().obj_list()->has_selected_cut_object()) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index 0b263378c..ec2803100 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -302,6 +302,10 @@ private: // True when m_text contain character unknown by selected font bool m_text_contain_unknown_glyph = false; + // True from on_set_state(State::On) to first call of on_render_input_window() + // To know that on data_change when no volume is selected create new one + bool m_is_just_opened = false; + // cancel for previous update of volume to cancel finalize part std::shared_ptr> m_job_cancel; From 856b8d1a2431291d7f1046639019936d64c36c92 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Tue, 28 Mar 2023 21:48:34 +0200 Subject: [PATCH 004/103] Add check for unselection to close emboss gizmo before snapshot --- src/slic3r/GUI/GLCanvas3D.cpp | 5 +++++ src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 30 ++++++++++++++++++++++++- src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp | 1 + 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 08f68356f..efb5f2e36 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1711,6 +1711,11 @@ void GLCanvas3D::deselect_all() if (m_selection.is_empty()) return; + // close actual opened gizmo before deselection(m_selection.remove_all()) write to undo/redo snapshot + if (GLGizmosManager::EType current_type = m_gizmos.get_current_type(); + current_type != GLGizmosManager::Undefined) + m_gizmos.open_gizmo(current_type); + m_selection.remove_all(); wxGetApp().obj_manipul()->set_dirty(); m_gizmos.reset_all_states(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 8e747c01e..a8db29340 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -405,6 +405,33 @@ bool GLGizmoEmboss::on_mouse_for_translate(const wxMouseEvent &mouse_event) return res; } +void GLGizmoEmboss::on_mouse_change_selection(const wxMouseEvent &mouse_event) +{ + if (mouse_event.LeftDown()) { + // is hovered volume closest hovered? + int hovered_idx = m_parent.get_first_hover_volume_idx(); + if (hovered_idx < 0) + // unselect object + return close(); + + const GLVolumePtrs &gl_volumes = m_parent.get_volumes().volumes; + auto hovered_idx_ = static_cast(hovered_idx); + if (hovered_idx_ >= gl_volumes.size()) + return close(); + + const GLVolume *gl_volume = gl_volumes[hovered_idx_]; + if (gl_volume == nullptr) + return close(); + + const ModelVolume *volume = get_model_volume(*gl_volume, m_parent.get_model()->objects); + if (volume == nullptr || !volume->text_configuration.has_value()) + // select volume without text configuration + return close(); + + // Reselection of text to another text + } +} + bool GLGizmoEmboss::on_mouse(const wxMouseEvent &mouse_event) { // not selected volume @@ -414,7 +441,7 @@ bool GLGizmoEmboss::on_mouse(const wxMouseEvent &mouse_event) if (on_mouse_for_rotation(mouse_event)) return true; if (on_mouse_for_translate(mouse_event)) return true; - + on_mouse_change_selection(mouse_event); return false; } @@ -527,6 +554,7 @@ void GLGizmoEmboss::on_render_input_window(float x, float y, float bottom_limit) if (m_volume == nullptr || get_model_volume(m_volume_id, m_parent.get_selection().get_model()->objects) == nullptr || !m_volume->text_configuration.has_value()) { + // This closing could lead to bad behavior of undo/redo stack when unselection create snapshot before close close(); return; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index d9bad8ab6..ec1d2b7a7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -149,6 +149,7 @@ private: // process mouse event bool on_mouse_for_rotation(const wxMouseEvent &mouse_event); bool on_mouse_for_translate(const wxMouseEvent &mouse_event); + void on_mouse_change_selection(const wxMouseEvent &mouse_event); // When open text loaded from .3mf it could be written with unknown font bool m_is_unknown_font; From adb6e00d4dbd821e034a089f05947fe8b0f7a9e3 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Wed, 29 Mar 2023 09:14:30 +0200 Subject: [PATCH 005/103] Open emboss when already opened --- src/slic3r/GUI/GUI_Factories.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 60049c865..4c12fbe23 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -998,12 +998,13 @@ void MenuFactory::append_menu_item_edit_text(wxMenu *menu) wxString description = _L("Ability to change text, font, size, ..."); std::string icon = ""; - append_menu_item( - menu, wxID_ANY, name, description, - [](wxCommandEvent &) { - plater()->canvas3D()->get_gizmos_manager().open_gizmo(GLGizmosManager::Emboss); - }, - icon, nullptr, can_edit_text, m_parent); + auto open_emboss = [](const wxCommandEvent &) { + GLGizmosManager &mng = plater()->canvas3D()->get_gizmos_manager(); + if (mng.get_current_type() == GLGizmosManager::Emboss) + mng.open_gizmo(GLGizmosManager::Emboss); // close() and reopen - move to be visible + mng.open_gizmo(GLGizmosManager::Emboss); + }; + append_menu_item(menu, wxID_ANY, name, description, open_emboss, icon, nullptr, can_edit_text, m_parent); } MenuFactory::MenuFactory() From 85ee20dc8bff41330f81da583fbde39f0e82f925 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Wed, 29 Mar 2023 09:27:47 +0200 Subject: [PATCH 006/103] Only on mouse up is emboss closed and only when no drag(move with scene) before was made. --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index a8db29340..2317cabe8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -407,7 +407,8 @@ bool GLGizmoEmboss::on_mouse_for_translate(const wxMouseEvent &mouse_event) void GLGizmoEmboss::on_mouse_change_selection(const wxMouseEvent &mouse_event) { - if (mouse_event.LeftDown()) { + static bool was_dragging = true; + if ((mouse_event.LeftUp() || mouse_event.RightUp()) && !was_dragging) { // is hovered volume closest hovered? int hovered_idx = m_parent.get_first_hover_volume_idx(); if (hovered_idx < 0) @@ -430,6 +431,7 @@ void GLGizmoEmboss::on_mouse_change_selection(const wxMouseEvent &mouse_event) // Reselection of text to another text } + was_dragging = mouse_event.Dragging(); } bool GLGizmoEmboss::on_mouse(const wxMouseEvent &mouse_event) From 471d8df398a19250ad99adebfeefd4ea1d883c69 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Thu, 30 Mar 2023 11:49:23 +0200 Subject: [PATCH 007/103] Fix right button dragging --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 59 ++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index dfbe5426a..2fd68cadd 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -411,7 +411,7 @@ bool GLGizmoEmboss::on_mouse_for_translate(const wxMouseEvent &mouse_event) void GLGizmoEmboss::on_mouse_change_selection(const wxMouseEvent &mouse_event) { - static bool was_dragging = true; + static bool was_dragging = true; if ((mouse_event.LeftUp() || mouse_event.RightUp()) && !was_dragging) { // is hovered volume closest hovered? int hovered_idx = m_parent.get_first_hover_volume_idx(); @@ -436,6 +436,63 @@ void GLGizmoEmboss::on_mouse_change_selection(const wxMouseEvent &mouse_event) // Reselection of text to another text } was_dragging = mouse_event.Dragging(); + + // Hook When click on object for reselection must be on event left down not up + if (mouse_event.LeftDown()) { + // is hovered volume closest hovered? + int hovered_idx = m_parent.get_first_hover_volume_idx(); + if (hovered_idx < 0) + // Potentionaly move with camera (drag) + return; + + const GLVolumePtrs &gl_volumes = m_parent.get_volumes().volumes; + auto hovered_idx_ = static_cast(hovered_idx); + if (hovered_idx_ >= gl_volumes.size()) + return; + const GLVolume *gl_volume = gl_volumes[hovered_idx_]; + if (gl_volume == nullptr) + return; + const ModelVolume *volume = get_model_volume(*gl_volume, m_parent.get_model()->objects); + if (volume == nullptr) + return; + + if (volume->text_configuration.has_value()) + return; // Reselection of text to another text + + // select volume without text configuration + return close(); + } + + // Hook When drag with scene by right mouse button + // object it is selected after drag scene !! + if (mouse_event.RightDown()) { + // is hovered volume closest hovered? + int hovered_idx = m_parent.get_first_hover_volume_idx(); + if (hovered_idx < 0) + // Potentionaly move with camera (drag) + return; + + const GLVolumePtrs &gl_volumes = m_parent.get_volumes().volumes; + auto hovered_idx_ = static_cast(hovered_idx); + if (hovered_idx_ >= gl_volumes.size()) + return; + const GLVolume *gl_volume = gl_volumes[hovered_idx_]; + if (gl_volume == nullptr) + return; + const ModelVolume *volume = get_model_volume(*gl_volume, m_parent.get_model()->objects); + if (volume == nullptr) + return; + + // is actual selected? + if (m_volume->id() == volume->id()) + return; + + if (volume->is_the_only_one_part()) + return; // reselect to another text + + // select volume without text configuration + return close(); + } } bool GLGizmoEmboss::on_mouse(const wxMouseEvent &mouse_event) From ba715ea005008e1087aecaf4e8e0c822d77ea4ae Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Thu, 30 Mar 2023 11:58:02 +0200 Subject: [PATCH 008/103] fix you can't reselect to another text object --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 2fd68cadd..eeec7916f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -487,9 +487,6 @@ void GLGizmoEmboss::on_mouse_change_selection(const wxMouseEvent &mouse_event) if (m_volume->id() == volume->id()) return; - if (volume->is_the_only_one_part()) - return; // reselect to another text - // select volume without text configuration return close(); } From 827d08d0900d141333bf344ca73fc08f1455e838 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Thu, 30 Mar 2023 12:37:50 +0200 Subject: [PATCH 009/103] add more comment --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 4e45263fa..3631a4bea 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -782,12 +782,14 @@ void GLGizmoEmboss::data_changed(bool is_serializing) { set_volume_by_selection(); // when open window by "T" and no valid volume is selected, so Create new one + // this is only place where One could distiguish between serializing and open by shortcut T if (!is_serializing && m_is_just_opened && m_volume == nullptr) { // reopen gizmo when new object is created close(); if (wxGetApp().get_mode() == comSimple || wxGetApp().obj_list()->has_selected_cut_object()) - // It's impossible to add a part in simple mode + // It's unwanted to add a part in simple mode return; + // start creating new object create_volume(ModelVolumeType::MODEL_PART); } From 58e3143ad8fee1069b01a4ebe85e07711331e5aa Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Thu, 30 Mar 2023 18:16:59 +0200 Subject: [PATCH 010/103] Different handling of shortcut 'T' for emboss text --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 108 +++++++++++----------- src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp | 8 +- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 23 +++-- 3 files changed, 72 insertions(+), 67 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 3631a4bea..93a4dea2e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -140,13 +140,6 @@ GLGizmoEmboss::GLGizmoEmboss(GLCanvas3D &parent) // Private namespace with helper function for create volume namespace priv { -/// -/// Check if volume type is possible use for new text volume -/// -/// Type -/// True when allowed otherwise false -static bool is_valid(ModelVolumeType volume_type); - /// /// Prepare data for emboss /// @@ -249,10 +242,9 @@ static bool apply_camera_dir(const Camera &camera, GLCanvas3D &canvas); void GLGizmoEmboss::create_volume(ModelVolumeType volume_type, const Vec2d& mouse_pos) { - if (!priv::is_valid(volume_type)) return; - m_style_manager.discard_style_changes(); - set_default_text(); - + if (!init_create(volume_type)) + return; + GLVolume *gl_volume = get_first_hovered_gl_volume(m_parent); DataBase emboss_data = priv::create_emboss_data_base(m_text, m_style_manager, m_job_cancel); if (gl_volume != nullptr) { @@ -271,9 +263,8 @@ void GLGizmoEmboss::create_volume(ModelVolumeType volume_type, const Vec2d& mous // Designed for create volume without information of mouse in scene void GLGizmoEmboss::create_volume(ModelVolumeType volume_type) { - if (!priv::is_valid(volume_type)) return; - m_style_manager.discard_style_changes(); - set_default_text(); + if (!init_create(volume_type)) + return; // select position by camera position and view direction const Selection &selection = m_parent.get_selection(); @@ -321,6 +312,28 @@ void GLGizmoEmboss::create_volume(ModelVolumeType volume_type) } } +bool GLGizmoEmboss::init_create(ModelVolumeType volume_type) +{ + // check valid volume type + if (volume_type != ModelVolumeType::MODEL_PART && + volume_type != ModelVolumeType::NEGATIVE_VOLUME && + volume_type != ModelVolumeType::PARAMETER_MODIFIER){ + BOOST_LOG_TRIVIAL(error) << "Can't create embossed volume with this type: " << (int) volume_type; + return false; + } + + if (!is_activable()) { + BOOST_LOG_TRIVIAL(error) << "Can't create text. Gizmo is not activabled."; + return false; + } + + m_style_manager.discard_style_changes(); + + // set default text + m_text = _u8L("Embossed text"); + return true; +} + bool GLGizmoEmboss::on_mouse_for_rotation(const wxMouseEvent &mouse_event) { if (mouse_event.Moving()) return false; @@ -510,6 +523,8 @@ bool GLGizmoEmboss::on_init() m_rotate_gizmo.init(); ColorRGBA gray_color(.6f, .6f, .6f, .3f); m_rotate_gizmo.set_highlight_color(gray_color); + + // NOTE: It has special handling in GLGizmosManager::handle_shortcut m_shortcut_key = WXK_CONTROL_T; // initialize text styles @@ -520,6 +535,11 @@ bool GLGizmoEmboss::on_init() return true; } +bool GLGizmoEmboss::on_is_activable() const { + return wxGetApp().get_mode() != comSimple && + !wxGetApp().obj_list()->has_selected_cut_object(); +} + std::string GLGizmoEmboss::on_get_name() const { return _u8L("Emboss"); } void GLGizmoEmboss::on_render() { @@ -729,34 +749,33 @@ void GLGizmoEmboss::on_set_state() { // enable / disable bed from picking // Rotation gizmo must work through bed - m_parent.set_raycaster_gizmos_on_top(GLGizmoBase::m_state == GLGizmoBase::On); + m_parent.set_raycaster_gizmos_on_top(m_state == GLGizmoBase::On); - m_rotate_gizmo.set_state(GLGizmoBase::m_state); + m_rotate_gizmo.set_state(m_state); // Closing gizmo. e.g. selecting another one - if (GLGizmoBase::m_state == GLGizmoBase::Off) { + if (m_state == GLGizmoBase::Off) { // refuse outgoing during text preview - if (false) { - GLGizmoBase::m_state = GLGizmoBase::On; - auto notification_manager = wxGetApp().plater()->get_notification_manager(); - notification_manager->push_notification( - NotificationType::CustomNotification, - NotificationManager::NotificationLevel::RegularNotificationLevel, - _u8L("ERROR: Wait until ends or Cancel process.")); - return; - } reset_volume(); // Store order and last activ index into app.ini // TODO: what to do when can't store into file? m_style_manager.store_styles_to_app_config(false); remove_notification_not_valid_font(); - } else if (GLGizmoBase::m_state == GLGizmoBase::On) { + } else if (m_state == GLGizmoBase::On) { // to reload fonts from system, when install new one wxFontEnumerator::InvalidateCache(); // Immediately after set state On is called function data_changed(), // where one could distiguish undo/redo serialization from opening by letter 'T' - //set_volume_by_selection(); + set_volume_by_selection(); + + // when open window by "T" and no valid volume is selected, so Create new one + if (m_volume == nullptr) { + // reopen gizmo when new object is created + m_state = GLGizmoBase::Off; + // start creating new object + return; + } m_is_just_opened = true; // change position of just opened emboss window @@ -778,21 +797,7 @@ void GLGizmoEmboss::on_set_state() void GLGizmoEmboss::data_changed(bool is_serializing) { if (is_serializing) reset_volume(); - set_volume_by_selection(); - - // when open window by "T" and no valid volume is selected, so Create new one - // this is only place where One could distiguish between serializing and open by shortcut T - if (!is_serializing && m_is_just_opened && m_volume == nullptr) { - // reopen gizmo when new object is created - close(); - if (wxGetApp().get_mode() == comSimple || wxGetApp().obj_list()->has_selected_cut_object()) - // It's unwanted to add a part in simple mode - return; - - // start creating new object - create_volume(ModelVolumeType::MODEL_PART); - } } void GLGizmoEmboss::on_start_dragging() { m_rotate_gizmo.start_dragging(); } @@ -989,8 +994,6 @@ EmbossStyles GLGizmoEmboss::create_default_styles() return styles; } -void GLGizmoEmboss::set_default_text(){ m_text = _u8L("Embossed text"); } - void GLGizmoEmboss::set_volume_by_selection() { const Selection &selection = m_parent.get_selection(); @@ -3402,16 +3405,6 @@ bool priv::draw_button(const IconManager::VIcons &icons, IconType type, bool dis // priv namespace implementation /////////////// -bool priv::is_valid(ModelVolumeType volume_type) -{ - if (volume_type == ModelVolumeType::MODEL_PART || volume_type == ModelVolumeType::NEGATIVE_VOLUME || - volume_type == ModelVolumeType::PARAMETER_MODIFIER) - return true; - - BOOST_LOG_TRIVIAL(error) << "Can't create embossed volume with this type: " << (int) volume_type; - return false; -} - DataBase priv::create_emboss_data_base(const std::string &text, StyleManager &style_manager, std::shared_ptr>& cancel) { // create volume_name @@ -3421,9 +3414,12 @@ DataBase priv::create_emboss_data_base(const std::string &text, StyleManager &st // change enters to space std::replace(volume_name.begin(), volume_name.end(), '\n', ' '); - if (!style_manager.is_active_font()) + if (!style_manager.is_active_font()) { style_manager.load_valid_style(); - assert(style_manager.is_active_font()); + assert(style_manager.is_active_font()); + if (!style_manager.is_active_font()) + return {}; // no active font in style, should never happend !!! + } const EmbossStyle &es = style_manager.get_style(); // actualize font path - during changes in gui it could be corrupted diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index 5683820c5..5f6817595 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -49,12 +49,12 @@ public: protected: bool on_init() override; + bool on_is_activable() const override; std::string on_get_name() const override; void on_render() override; - virtual void on_register_raycasters_for_picking() override; - virtual void on_unregister_raycasters_for_picking() override; + void on_register_raycasters_for_picking() override; + void on_unregister_raycasters_for_picking() override; void on_render_input_window(float x, float y, float bottom_limit) override; - bool on_is_activable() const override { return true; } bool on_is_selectable() const override { return false; } void on_set_state() override; void data_changed(bool is_serializing) override; // selection changed @@ -79,7 +79,7 @@ protected: private: static EmbossStyles create_default_styles(); // localized default text - void set_default_text(); + bool init_create(ModelVolumeType volume_type); void set_volume_by_selection(); void reset_volume(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 361fd7792..4dfaff09f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -238,15 +238,24 @@ bool GLGizmosManager::is_running() const bool GLGizmosManager::handle_shortcut(int key) { - if (!m_enabled || m_parent.get_selection().is_empty()) + if (!m_enabled) return false; - auto it = std::find_if(m_gizmos.begin(), m_gizmos.end(), - [key](const std::unique_ptr& gizmo) { - int gizmo_key = gizmo->get_shortcut_key(); - return gizmo->is_activable() - && ((gizmo_key == key - 64) || (gizmo_key == key - 96)); - }); + auto is_key = [pressed_key = key](int gizmo_key) { return (gizmo_key == pressed_key - 64) || (gizmo_key == pressed_key - 96); }; + // allowe open shortcut even when selection is empty + if (GLGizmoBase* gizmo_emboss = m_gizmos[Emboss].get(); + is_key(gizmo_emboss->get_shortcut_key())) { + dynamic_cast(gizmo_emboss)->create_volume(ModelVolumeType::MODEL_PART); + return true; + } + + if (m_parent.get_selection().is_empty()) + return false; + + auto is_gizmo = [is_key](const std::unique_ptr &gizmo) { + return gizmo->is_activable() && is_key(gizmo->get_shortcut_key()); + }; + auto it = std::find_if(m_gizmos.begin(), m_gizmos.end(), is_gizmo); if (it == m_gizmos.end()) return false; From dd7a19505ae23fc979e00205acc2370fc5afbbe7 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Thu, 30 Mar 2023 19:04:29 +0200 Subject: [PATCH 011/103] Another approach to fix undo redo: Do not open gizmo on shortcut key: only create volume --- src/slic3r/GUI/Gizmos/GLGizmoBase.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 39 +++++++++++-------- src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp | 10 ++--- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp | 2 +- .../GUI/Gizmos/GLGizmoMmuSegmentation.cpp | 4 +- .../GUI/Gizmos/GLGizmoMmuSegmentation.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoMove.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoScale.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 4 +- 26 files changed, 54 insertions(+), 49 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp index 9632723e4..9ed34b011 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp @@ -174,7 +174,7 @@ public: /// /// Is called when data (Selection) is changed /// - virtual void data_changed(bool is_serializing){}; + virtual void data_changed(){}; /// /// Implement when want to process mouse events in gizmo diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 1fe409df4..5d8a69e41 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -2510,7 +2510,7 @@ CommonGizmosDataID GLGizmoCut3D::on_get_requirements() const { | int(CommonGizmosDataID::ObjectClipper)); } -void GLGizmoCut3D::data_changed(bool is_serializing) +void GLGizmoCut3D::data_changed() { update_bb(); if (auto oc = m_c->object_clipper()) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index d17ca6cf4..ee804686e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -256,7 +256,7 @@ protected: std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Cut gizmo"); } std::string get_action_snapshot_name() const override { return _u8L("Cut gizmo editing"); } - void data_changed(bool is_serializing) override; + void data_changed() override; private: void set_center(const Vec3d& center, bool update_tbb = false); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 93a4dea2e..b84b70be0 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -312,6 +312,21 @@ void GLGizmoEmboss::create_volume(ModelVolumeType volume_type) } } +void GLGizmoEmboss::on_shortcut_key() { + set_volume_by_selection(); + if (m_volume == nullptr) { + // No volume to select from selection so create volume. + // NOTE: After finish job for creation emboss Text volume, + // GLGizmoEmboss will be opened + create_volume(ModelVolumeType::MODEL_PART); + } else { + // shortcut is pressed when text is selected soo start edit it. + auto &mng = m_parent.get_gizmos_manager(); + if (mng.get_current_type() != GLGizmosManager::Emboss) + mng.open_gizmo(GLGizmosManager::Emboss); + } +} + bool GLGizmoEmboss::init_create(ModelVolumeType volume_type) { // check valid volume type @@ -628,7 +643,6 @@ static void draw_mouse_offset(const std::optional &offset) void GLGizmoEmboss::on_render_input_window(float x, float y, float bottom_limit) { - m_is_just_opened = false; assert(m_volume != nullptr); // Do not render window for not selected text volume if (m_volume == nullptr || @@ -767,16 +781,7 @@ void GLGizmoEmboss::on_set_state() // Immediately after set state On is called function data_changed(), // where one could distiguish undo/redo serialization from opening by letter 'T' - set_volume_by_selection(); - - // when open window by "T" and no valid volume is selected, so Create new one - if (m_volume == nullptr) { - // reopen gizmo when new object is created - m_state = GLGizmoBase::Off; - // start creating new object - return; - } - m_is_just_opened = true; + // set_volume_by_selection(); // change position of just opened emboss window if (m_allow_open_near_volume) { @@ -794,9 +799,7 @@ void GLGizmoEmboss::on_set_state() } } -void GLGizmoEmboss::data_changed(bool is_serializing) { - if (is_serializing) - reset_volume(); +void GLGizmoEmboss::data_changed() { set_volume_by_selection(); } @@ -1001,13 +1004,15 @@ void GLGizmoEmboss::set_volume_by_selection() if (gl_volume == nullptr) return reset_volume(); - const ModelObjectPtrs &objects = selection.get_model()->objects; - ModelVolume *volume =get_model_volume(*gl_volume, objects); + const ModelObjectPtrs &objects = m_parent.get_model()->objects; + ModelVolume *volume = get_model_volume(*gl_volume, objects); if (volume == nullptr) return reset_volume(); // is same volume as actual selected? - if (volume->id() == m_volume_id) + if (volume->id() == m_volume_id && + m_volume != nullptr && + volume->text_configuration->style == m_volume->text_configuration->style) return; // for changed volume notification is NOT valid diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index 5f6817595..4cfe31898 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -47,6 +47,10 @@ public: /// Object part / Negative volume / Modifier void create_volume(ModelVolumeType volume_type); + /// + /// Handle pressing of shortcut + /// + void on_shortcut_key(); protected: bool on_init() override; bool on_is_activable() const override; @@ -57,7 +61,7 @@ protected: void on_render_input_window(float x, float y, float bottom_limit) override; bool on_is_selectable() const override { return false; } void on_set_state() override; - void data_changed(bool is_serializing) override; // selection changed + void data_changed() override; // selection changed void on_set_hover_id() override{ m_rotate_gizmo.set_hover_id(m_hover_id); } void on_enable_grabber(unsigned int id) override { m_rotate_gizmo.enable_grabber(); } void on_disable_grabber(unsigned int id) override { m_rotate_gizmo.disable_grabber(); } @@ -304,10 +308,6 @@ private: // True when m_text contain character unknown by selected font bool m_text_contain_unknown_glyph = false; - // True from on_set_state(State::On) to first call of on_render_input_window() - // To know that on data_change when no volume is selected create new one - bool m_is_just_opened = false; - // cancel for previous update of volume to cancel finalize part std::shared_ptr> m_job_cancel; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 3eb99eb6c..d5520403c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -387,9 +387,9 @@ void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool block) m_parent.set_as_dirty(); } -void GLGizmoFdmSupports::data_changed(bool is_serializing) +void GLGizmoFdmSupports::data_changed() { - GLGizmoPainterBase::data_changed(is_serializing); + GLGizmoPainterBase::data_changed(); if (! m_c->selection_info()) return; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index ee77573c0..aee669199 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -28,7 +28,7 @@ protected: private: bool on_init() override; - void data_changed(bool is_serializing) override; + void data_changed() override; void update_model_object() const override; void update_from_model_object() override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp index 213b0b99f..6cbec0891 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp @@ -56,7 +56,7 @@ bool GLGizmoFlatten::on_mouse(const wxMouseEvent &mouse_event) return false; } -void GLGizmoFlatten::data_changed(bool is_serializing) +void GLGizmoFlatten::data_changed() { const Selection & selection = m_parent.get_selection(); const ModelObject *model_object = nullptr; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp index 1701b76a5..0444fa217 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp @@ -55,7 +55,7 @@ public: /// Return True when use the information otherwise False. bool on_mouse(const wxMouseEvent &mouse_event) override; - void data_changed(bool is_serializing) override; + void data_changed() override; protected: bool on_init() override; std::string on_get_name() const override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index 03fcefd25..89e0809fd 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -42,7 +42,7 @@ bool GLGizmoHollow::on_init() return true; } -void GLGizmoHollow::data_changed(bool is_serializing) +void GLGizmoHollow::data_changed() { if (! m_c->selection_info()) return; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp index 025e70b64..abfb2503f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp @@ -24,7 +24,7 @@ class GLGizmoHollow : public GLGizmoSlaBase { public: GLGizmoHollow(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); - void data_changed(bool is_serializing) override; + void data_changed() override; bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); void delete_selected_points(); bool is_selection_rectangle_dragging() const override { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp index d298abdda..e8f6ad4af 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp @@ -454,7 +454,7 @@ bool GLGizmoMeasure::on_mouse(const wxMouseEvent &mouse_event) return false; } -void GLGizmoMeasure::data_changed(bool is_serializing) +void GLGizmoMeasure::data_changed() { m_parent.toggle_sla_auxiliaries_visibility(false, nullptr, -1); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp index 6a8f21a19..cc43b068e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp @@ -156,7 +156,7 @@ public: /// Return True when use the information otherwise False. bool on_mouse(const wxMouseEvent &mouse_event) override; - void data_changed(bool is_serializing) override; + void data_changed() override; bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index 516a2b258..fd7963050 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -147,9 +147,9 @@ void GLGizmoMmuSegmentation::render_painter_gizmo() glsafe(::glDisable(GL_BLEND)); } -void GLGizmoMmuSegmentation::data_changed(bool is_serializing) +void GLGizmoMmuSegmentation::data_changed() { - GLGizmoPainterBase::data_changed(is_serializing); + GLGizmoPainterBase::data_changed(); if (m_state != On || wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptFFF || wxGetApp().extruders_edited_cnt() <= 1) return; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp index 21138c530..ee32c4596 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp @@ -90,7 +90,7 @@ public: void render_painter_gizmo() override; - void data_changed(bool is_serializing) override; + void data_changed() override; void render_triangles(const Selection& selection) const override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 7e40cee7f..73615b463 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -51,7 +51,7 @@ bool GLGizmoMove3D::on_mouse(const wxMouseEvent &mouse_event) { return use_grabbers(mouse_event); } -void GLGizmoMove3D::data_changed(bool is_serializing) { +void GLGizmoMove3D::data_changed() { m_grabbers[2].enabled = !m_parent.get_selection().is_wipe_tower(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp index 104cba889..cd92d7472 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp @@ -51,7 +51,7 @@ public: /// /// Detect reduction of move for wipetover on selection change /// - void data_changed(bool is_serializing) override; + void data_changed() override; protected: bool on_init() override; std::string on_get_name() const override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 2ef660ce8..aa3d56ef9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -31,7 +31,7 @@ GLGizmoPainterBase::~GLGizmoPainterBase() s_sphere.reset(); } -void GLGizmoPainterBase::data_changed(bool is_serializing) +void GLGizmoPainterBase::data_changed() { if (m_state != On) return; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp index 007b959e7..1fd718a25 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -83,7 +83,7 @@ private: public: GLGizmoPainterBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); ~GLGizmoPainterBase() override; - void data_changed(bool is_serializing) override; + void data_changed() override; virtual bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); // Following function renders the triangles and cursor. Having this separated diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index d2aadb0bc..75b80d8f5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -600,7 +600,7 @@ bool GLGizmoRotate3D::on_mouse(const wxMouseEvent &mouse_event) return use_grabbers(mouse_event); } -void GLGizmoRotate3D::data_changed(bool is_serializing) { +void GLGizmoRotate3D::data_changed() { if (m_parent.get_selection().is_wipe_tower()) { #if !ENABLE_WORLD_COORDINATE const DynamicPrintConfig& config = wxGetApp().preset_bundle->prints.get_edited_preset().config; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp index 7ae4ce726..ebfed1920 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -146,7 +146,7 @@ public: /// Return True when use the information otherwise False. bool on_mouse(const wxMouseEvent &mouse_event) override; - void data_changed(bool is_serializing) override; + void data_changed() override; protected: bool on_init() override; std::string on_get_name() const override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 9c8806a4e..063dce721 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -105,7 +105,7 @@ void GLGizmoScale3D::enable_ununiversal_scale(bool enable) m_grabbers[i].enabled = enable; } -void GLGizmoScale3D::data_changed(bool is_serializing) +void GLGizmoScale3D::data_changed() { #if ENABLE_WORLD_COORDINATE set_scale(Vec3d::Ones()); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index 73dcecfe7..fe6ab4972 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -82,7 +82,7 @@ public: /// Return True when use the information otherwise False. bool on_mouse(const wxMouseEvent &mouse_event) override; - void data_changed(bool is_serializing) override; + void data_changed() override; void enable_ununiversal_scale(bool enable); protected: virtual bool on_init() override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 8563d330c..fe7e1b4ea 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -49,7 +49,7 @@ bool GLGizmoSlaSupports::on_init() return true; } -void GLGizmoSlaSupports::data_changed(bool is_serializing) +void GLGizmoSlaSupports::data_changed() { if (! m_c->selection_info()) return; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index c098905aa..bb0513682 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -55,7 +55,7 @@ private: public: GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); virtual ~GLGizmoSlaSupports() = default; - void data_changed(bool is_serializing) override; + void data_changed() override; bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); void delete_selected_points(bool force = false); //ClippingPlane get_sla_clipping_plane() const; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 4dfaff09f..d6dd6ff3e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -223,7 +223,7 @@ void GLGizmosManager::update_data() m_common_gizmos_data->update(get_current() ? get_current()->get_requirements() : CommonGizmosDataID(0)); - if (m_current != Undefined) m_gizmos[m_current]->data_changed(m_serializing); + if (m_current != Undefined) m_gizmos[m_current]->data_changed(); } bool GLGizmosManager::is_running() const @@ -245,7 +245,7 @@ bool GLGizmosManager::handle_shortcut(int key) // allowe open shortcut even when selection is empty if (GLGizmoBase* gizmo_emboss = m_gizmos[Emboss].get(); is_key(gizmo_emboss->get_shortcut_key())) { - dynamic_cast(gizmo_emboss)->create_volume(ModelVolumeType::MODEL_PART); + dynamic_cast(gizmo_emboss)->on_shortcut_key(); return true; } From b9ac8aeafa06ecb1588e1d72aa2e127379a5babc Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Mon, 3 Apr 2023 14:07:16 +0200 Subject: [PATCH 012/103] Fix for crash --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index ac74b43b5..8b134e2e6 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -342,6 +342,13 @@ bool GLGizmoEmboss::init_create(ModelVolumeType volume_type) BOOST_LOG_TRIVIAL(error) << "Can't create text. Gizmo is not activabled."; return false; } + + // Check can't be inside is_activable() cause crash + // steps to reproduce: start App -> key 't' -> key 'delete' + if (wxGetApp().obj_list()->has_selected_cut_object()) { + BOOST_LOG_TRIVIAL(error) << "Can't create text on cut object"; + return false; + } m_style_manager.discard_style_changes(); @@ -552,8 +559,7 @@ bool GLGizmoEmboss::on_init() } bool GLGizmoEmboss::on_is_activable() const { - return wxGetApp().get_mode() != comSimple && - !wxGetApp().obj_list()->has_selected_cut_object(); + return wxGetApp().get_mode() != comSimple; } std::string GLGizmoEmboss::on_get_name() const { return _u8L("Emboss"); } From ad81d530b100b0d616c053dc4d226b996edfb793 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Mon, 3 Apr 2023 16:24:07 +0200 Subject: [PATCH 013/103] Fix of freez when undo redo on text with modifier. --- src/slic3r/GUI/Gizmos/GLGizmoBase.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 4 +++- src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp | 4 ++-- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 4 ++-- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp | 4 ++-- src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoMove.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 3 +-- src/slic3r/GUI/Gizmos/GLGizmoScale.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 2 +- 26 files changed, 31 insertions(+), 30 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp index 82bcba91f..d2bf3537d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp @@ -174,7 +174,7 @@ public: /// /// Is called when data (Selection) is changed /// - virtual void data_changed(){}; + virtual void data_changed(bool is_serializing){}; /// /// Implement when want to process mouse events in gizmo diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 5d8a69e41..661040451 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -2510,7 +2510,7 @@ CommonGizmosDataID GLGizmoCut3D::on_get_requirements() const { | int(CommonGizmosDataID::ObjectClipper)); } -void GLGizmoCut3D::data_changed() +void GLGizmoCut3D::data_changed(bool is_serializing) { update_bb(); if (auto oc = m_c->object_clipper()) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index ee804686e..49d1848e4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -256,7 +256,7 @@ protected: std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Cut gizmo"); } std::string get_action_snapshot_name() const override { return _u8L("Cut gizmo editing"); } - void data_changed() override; + void data_changed(bool is_serializing) override; private: void set_center(const Vec3d& center, bool update_tbb = false); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 8b134e2e6..ab345a4bb 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -806,8 +806,10 @@ void GLGizmoEmboss::on_set_state() } } -void GLGizmoEmboss::data_changed() { +void GLGizmoEmboss::data_changed(bool is_serializing) { set_volume_by_selection(); + if (!is_serializing && m_volume == nullptr) + close(); } void GLGizmoEmboss::on_start_dragging() { m_rotate_gizmo.start_dragging(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index 4cfe31898..db8de5e4e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -60,8 +60,8 @@ protected: void on_unregister_raycasters_for_picking() override; void on_render_input_window(float x, float y, float bottom_limit) override; bool on_is_selectable() const override { return false; } - void on_set_state() override; - void data_changed() override; // selection changed + void on_set_state() override; + void data_changed(bool is_serializing) override; // selection changed void on_set_hover_id() override{ m_rotate_gizmo.set_hover_id(m_hover_id); } void on_enable_grabber(unsigned int id) override { m_rotate_gizmo.enable_grabber(); } void on_disable_grabber(unsigned int id) override { m_rotate_gizmo.disable_grabber(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 92b46aaa2..7700e1eef 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -383,9 +383,9 @@ void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool block) m_parent.set_as_dirty(); } -void GLGizmoFdmSupports::data_changed() +void GLGizmoFdmSupports::data_changed(bool is_serializing) { - GLGizmoPainterBase::data_changed(); + GLGizmoPainterBase::data_changed(is_serializing); if (! m_c->selection_info()) return; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index aee669199..ee77573c0 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -28,7 +28,7 @@ protected: private: bool on_init() override; - void data_changed() override; + void data_changed(bool is_serializing) override; void update_model_object() const override; void update_from_model_object() override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp index fea8ddc06..398aebb52 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp @@ -56,7 +56,7 @@ bool GLGizmoFlatten::on_mouse(const wxMouseEvent &mouse_event) return false; } -void GLGizmoFlatten::data_changed() +void GLGizmoFlatten::data_changed(bool is_serializing) { const Selection & selection = m_parent.get_selection(); const ModelObject *model_object = nullptr; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp index 0444fa217..1701b76a5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp @@ -55,7 +55,7 @@ public: /// Return True when use the information otherwise False. bool on_mouse(const wxMouseEvent &mouse_event) override; - void data_changed() override; + void data_changed(bool is_serializing) override; protected: bool on_init() override; std::string on_get_name() const override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index 374f19edd..91b2bd879 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -42,7 +42,7 @@ bool GLGizmoHollow::on_init() return true; } -void GLGizmoHollow::data_changed() +void GLGizmoHollow::data_changed(bool is_serializing) { if (! m_c->selection_info()) return; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp index abfb2503f..025e70b64 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp @@ -24,7 +24,7 @@ class GLGizmoHollow : public GLGizmoSlaBase { public: GLGizmoHollow(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); - void data_changed() override; + void data_changed(bool is_serializing) override; bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); void delete_selected_points(); bool is_selection_rectangle_dragging() const override { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp index e8f6ad4af..d298abdda 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp @@ -454,7 +454,7 @@ bool GLGizmoMeasure::on_mouse(const wxMouseEvent &mouse_event) return false; } -void GLGizmoMeasure::data_changed() +void GLGizmoMeasure::data_changed(bool is_serializing) { m_parent.toggle_sla_auxiliaries_visibility(false, nullptr, -1); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp index cc43b068e..6a8f21a19 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp @@ -156,7 +156,7 @@ public: /// Return True when use the information otherwise False. bool on_mouse(const wxMouseEvent &mouse_event) override; - void data_changed() override; + void data_changed(bool is_serializing) override; bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index fd7963050..516a2b258 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -147,9 +147,9 @@ void GLGizmoMmuSegmentation::render_painter_gizmo() glsafe(::glDisable(GL_BLEND)); } -void GLGizmoMmuSegmentation::data_changed() +void GLGizmoMmuSegmentation::data_changed(bool is_serializing) { - GLGizmoPainterBase::data_changed(); + GLGizmoPainterBase::data_changed(is_serializing); if (m_state != On || wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptFFF || wxGetApp().extruders_edited_cnt() <= 1) return; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp index ee32c4596..21138c530 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp @@ -90,7 +90,7 @@ public: void render_painter_gizmo() override; - void data_changed() override; + void data_changed(bool is_serializing) override; void render_triangles(const Selection& selection) const override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 3d697c63c..556d5b61e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -34,7 +34,7 @@ bool GLGizmoMove3D::on_mouse(const wxMouseEvent &mouse_event) { return use_grabbers(mouse_event); } -void GLGizmoMove3D::data_changed() { +void GLGizmoMove3D::data_changed(bool is_serializing) { m_grabbers[2].enabled = !m_parent.get_selection().is_wipe_tower(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp index 371cda80a..b68747920 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp @@ -47,7 +47,7 @@ public: /// /// Detect reduction of move for wipetover on selection change /// - void data_changed() override; + void data_changed(bool is_serializing) override; protected: bool on_init() override; std::string on_get_name() const override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 0a2f9f098..d574f7b56 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -31,7 +31,7 @@ GLGizmoPainterBase::~GLGizmoPainterBase() s_sphere.reset(); } -void GLGizmoPainterBase::data_changed() +void GLGizmoPainterBase::data_changed(bool is_serializing) { if (m_state != On) return; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp index 1fd718a25..007b959e7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -83,7 +83,7 @@ private: public: GLGizmoPainterBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); ~GLGizmoPainterBase() override; - void data_changed() override; + void data_changed(bool is_serializing) override; virtual bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); // Following function renders the triangles and cursor. Having this separated diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 2636486a7..677f2589e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -535,7 +535,7 @@ bool GLGizmoRotate3D::on_mouse(const wxMouseEvent &mouse_event) return use_grabbers(mouse_event); } -void GLGizmoRotate3D::data_changed() { +void GLGizmoRotate3D::data_changed(bool is_serializing) { if (m_parent.get_selection().is_wipe_tower()) { m_gizmos[0].disable_grabber(); m_gizmos[1].disable_grabber(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp index a1bb2ee44..bbc32ec7a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -138,7 +138,7 @@ public: /// Return True when use the information otherwise False. bool on_mouse(const wxMouseEvent &mouse_event) override; - void data_changed() override; + void data_changed(bool is_serializing) override; protected: bool on_init() override; std::string on_get_name() const override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 5e6ab1ae6..1a7251ddf 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -99,8 +99,7 @@ void GLGizmoScale3D::enable_ununiversal_scale(bool enable) m_grabbers[i].enabled = enable; } -void GLGizmoScale3D::data_changed() -{ +void GLGizmoScale3D::data_changed(bool is_serializing) { set_scale(Vec3d::Ones()); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index d0b239c33..6a7b4a331 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -61,7 +61,7 @@ public: /// Return True when use the information otherwise False. bool on_mouse(const wxMouseEvent &mouse_event) override; - void data_changed() override; + void data_changed(bool is_serializing) override; void enable_ununiversal_scale(bool enable); protected: virtual bool on_init() override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 5ff491d2d..109490cc7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -49,7 +49,7 @@ bool GLGizmoSlaSupports::on_init() return true; } -void GLGizmoSlaSupports::data_changed() +void GLGizmoSlaSupports::data_changed(bool is_serializing) { if (! m_c->selection_info()) return; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index bb0513682..c098905aa 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -55,7 +55,7 @@ private: public: GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); virtual ~GLGizmoSlaSupports() = default; - void data_changed() override; + void data_changed(bool is_serializing) override; bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); void delete_selected_points(bool force = false); //ClippingPlane get_sla_clipping_plane() const; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index d6dd6ff3e..98dd15137 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -223,7 +223,7 @@ void GLGizmosManager::update_data() m_common_gizmos_data->update(get_current() ? get_current()->get_requirements() : CommonGizmosDataID(0)); - if (m_current != Undefined) m_gizmos[m_current]->data_changed(); + if (m_current != Undefined) m_gizmos[m_current]->data_changed(m_serializing); } bool GLGizmosManager::is_running() const From c6d5013e9adbe6c93ea9ddfa88a5e51a5ecd3e54 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Tue, 4 Apr 2023 13:18:55 +0200 Subject: [PATCH 014/103] Allowe edit text objects in simple mode --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 6 +----- src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 7daabf3d7..b1ffee652 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -246,7 +246,7 @@ void GLGizmoEmboss::create_volume(ModelVolumeType volume_type, const Vec2d& mous if (!init_create(volume_type)) return; - GLVolume *gl_volume = get_first_hovered_gl_volume(m_parent); + const GLVolume *gl_volume = get_first_hovered_gl_volume(m_parent); DataBase emboss_data = priv::create_emboss_data_base(m_text, m_style_manager, m_job_cancel); if (gl_volume != nullptr) { // Try to cast ray into scene and find object for add volume @@ -558,10 +558,6 @@ bool GLGizmoEmboss::on_init() return true; } -bool GLGizmoEmboss::on_is_activable() const { - return wxGetApp().get_mode() != comSimple; -} - std::string GLGizmoEmboss::on_get_name() const { return _u8L("Emboss"); } void GLGizmoEmboss::on_render() { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index db8de5e4e..318917863 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -53,13 +53,13 @@ public: void on_shortcut_key(); protected: bool on_init() override; - bool on_is_activable() const override; std::string on_get_name() const override; void on_render() override; void on_register_raycasters_for_picking() override; void on_unregister_raycasters_for_picking() override; void on_render_input_window(float x, float y, float bottom_limit) override; bool on_is_selectable() const override { return false; } + bool on_is_activable() const override { return true; }; void on_set_state() override; void data_changed(bool is_serializing) override; // selection changed void on_set_hover_id() override{ m_rotate_gizmo.set_hover_id(m_hover_id); } From a7c5a8fda904bcda4ae1bbd91f9fb71db54eff49 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Tue, 4 Apr 2023 13:26:05 +0200 Subject: [PATCH 015/103] In simple mode is possible create only Text object --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index b1ffee652..2012d138d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -248,7 +248,8 @@ void GLGizmoEmboss::create_volume(ModelVolumeType volume_type, const Vec2d& mous const GLVolume *gl_volume = get_first_hovered_gl_volume(m_parent); DataBase emboss_data = priv::create_emboss_data_base(m_text, m_style_manager, m_job_cancel); - if (gl_volume != nullptr) { + bool is_simple_mode = wxGetApp().get_mode() == comSimple; + if (gl_volume != nullptr && !is_simple_mode) { // Try to cast ray into scene and find object for add volume if (!priv::start_create_volume_on_surface_job(emboss_data, volume_type, mouse_pos, gl_volume, m_raycast_manager, m_parent)) { // When model is broken. It could appear that hit miss the object. @@ -275,8 +276,9 @@ void GLGizmoEmboss::create_volume(ModelVolumeType volume_type) Vec2d screen_center(s.get_width() / 2., s.get_height() / 2.); DataBase emboss_data = priv::create_emboss_data_base(m_text, m_style_manager, m_job_cancel); const ModelObjectPtrs &objects = selection.get_model()->objects; + bool is_simple_mode = wxGetApp().get_mode() == comSimple; // No selected object so create new object - if (selection.is_empty() || object_idx < 0 || static_cast(object_idx) >= objects.size()) { + if (selection.is_empty() || object_idx < 0 || static_cast(object_idx) >= objects.size() || is_simple_mode) { // create Object on center of screen // when ray throw center of screen not hit bed it create object on center of bed priv::start_create_object_job(emboss_data, screen_center); From eef077b6ffd09e6370e440ed3ddc660a8255640f Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Tue, 4 Apr 2023 16:06:50 +0200 Subject: [PATCH 016/103] Fix issue 10229: When dynamic fan speed is enabled, sometimes the extrusion speed was not reset correctly after travel, resulting in travel speeds used for printing. https://github.com/prusa3d/PrusaSlicer/issues/10229 --- src/libslic3r/GCode.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 79d6d191c..e82433d47 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -3083,8 +3083,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de std::string cooling_marker_setspeed_comments; if (m_enable_cooling_markers) { - if (path.role().is_bridge() && - (!path.role().is_perimeter() || !this->config().enable_dynamic_fan_speeds.get_at(m_writer.extruder()->id()))) + if (path.role().is_bridge()) gcode += ";_BRIDGE_FAN_START\n"; else cooling_marker_setspeed_comments = ";_EXTRUDE_SET_SPEED"; @@ -3120,7 +3119,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de double last_set_speed = new_points[0].speed * 60.0; double last_set_fan_speed = new_points[0].fan_speed; gcode += m_writer.set_speed(last_set_speed, "", cooling_marker_setspeed_comments); - gcode += ";_SET_FAN_SPEED" + std::to_string(int(last_set_fan_speed)) + "\n"; + gcode += "\n;_SET_FAN_SPEED" + std::to_string(int(last_set_fan_speed)) + "\n"; Vec2d prev = this->point_to_gcode_quantized(new_points[0].p); for (size_t i = 1; i < new_points.size(); i++) { const ProcessedPoint &processed_point = new_points[i]; @@ -3135,10 +3134,10 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de } if (last_set_fan_speed != processed_point.fan_speed) { last_set_fan_speed = processed_point.fan_speed; - gcode += ";_SET_FAN_SPEED" + std::to_string(int(last_set_fan_speed)) + "\n"; + gcode += "\n;_SET_FAN_SPEED" + std::to_string(int(last_set_fan_speed)) + "\n"; } } - gcode += ";_RESET_FAN_SPEED\n"; + gcode += "\n;_RESET_FAN_SPEED\n"; } if (m_enable_cooling_markers) From 439e9bc7e79abd473f04886045cf325c36c4ece5 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 5 Apr 2023 09:23:19 +0200 Subject: [PATCH 017/103] #10259 - Preview/GCodeViewer: Ignore purge line in 'Width' display when Custom extrusion paths are set to invisible --- src/slic3r/GUI/GCodeViewer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 62d4314db..7dd1db935 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -836,7 +836,8 @@ void GCodeViewer::refresh(const GCodeProcessorResult& gcode_result, const std::v case EMoveType::Extrude: { m_extrusions.ranges.height.update_from(round_to_bin(curr.height)); - m_extrusions.ranges.width.update_from(round_to_bin(curr.width)); + if (curr.extrusion_role != GCodeExtrusionRole::Custom || is_visible(GCodeExtrusionRole::Custom)) + m_extrusions.ranges.width.update_from(round_to_bin(curr.width)); m_extrusions.ranges.fan_speed.update_from(curr.fan_speed); m_extrusions.ranges.temperature.update_from(curr.temperature); if (curr.extrusion_role != GCodeExtrusionRole::Custom || is_visible(GCodeExtrusionRole::Custom)) From 6f6f714f847d4ea1caaebc699084f1f735339ea9 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Wed, 5 Apr 2023 10:31:42 +0200 Subject: [PATCH 018/103] alt eject in thread to multiple tries of LOCK operation and additional checks. --- src/slic3r/GUI/RemovableDriveManager.cpp | 107 +++++++++++++++++------ src/slic3r/GUI/RemovableDriveManager.hpp | 7 ++ 2 files changed, 86 insertions(+), 28 deletions(-) diff --git a/src/slic3r/GUI/RemovableDriveManager.cpp b/src/slic3r/GUI/RemovableDriveManager.cpp index ecd8e0727..7c8032810 100644 --- a/src/slic3r/GUI/RemovableDriveManager.cpp +++ b/src/slic3r/GUI/RemovableDriveManager.cpp @@ -78,32 +78,7 @@ std::vector RemovableDriveManager::search_for_removable_drives() cons } namespace { -int eject_alt(const std::wstring& volume_access_path) -{ - HANDLE handle = CreateFileW(volume_access_path.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr); - if (handle == INVALID_HANDLE_VALUE) { - BOOST_LOG_TRIVIAL(error) << "Alt Ejecting " << volume_access_path << " failed (handle == INVALID_HANDLE_VALUE): " << GetLastError(); - return 1; - } - DWORD deviceControlRetVal(0); - //these 3 commands should eject device safely but they dont, the device does disappear from file explorer but the "device was safely remove" notification doesnt trigger. - //sd cards does trigger WM_DEVICECHANGE messege, usb drives dont - BOOL e1 = DeviceIoControl(handle, FSCTL_LOCK_VOLUME, nullptr, 0, nullptr, 0, &deviceControlRetVal, nullptr); - BOOST_LOG_TRIVIAL(debug) << "FSCTL_LOCK_VOLUME " << e1 << " ; " << deviceControlRetVal << " ; " << GetLastError(); - BOOL e2 = DeviceIoControl(handle, FSCTL_DISMOUNT_VOLUME, nullptr, 0, nullptr, 0, &deviceControlRetVal, nullptr); - BOOST_LOG_TRIVIAL(debug) << "FSCTL_DISMOUNT_VOLUME " << e2 << " ; " << deviceControlRetVal << " ; " << GetLastError(); - - // some implemenatations also calls IOCTL_STORAGE_MEDIA_REMOVAL here with FALSE as third parameter, which should set PreventMediaRemoval - BOOL error = DeviceIoControl(handle, IOCTL_STORAGE_EJECT_MEDIA, nullptr, 0, nullptr, 0, &deviceControlRetVal, nullptr); - if (error == 0) { - CloseHandle(handle); - BOOST_LOG_TRIVIAL(error) << "Alt Ejecting " << volume_access_path << " failed (IOCTL_STORAGE_EJECT_MEDIA)" << deviceControlRetVal << " " << GetLastError(); - return 1; - } - CloseHandle(handle); - BOOST_LOG_TRIVIAL(info) << "Alt Ejecting finished"; - return 0; -} + // From https://github.com/microsoft/Windows-driver-samples/tree/main/usb/usbview @@ -485,7 +460,7 @@ int eject_inner(const std::string& path) DEVINST dev_inst = get_dev_inst_by_device_number(device_number, drive_type, dos_device_name); if (dev_inst == 0) { BOOST_LOG_TRIVIAL(error) << GUI::format("Ejecting of %1%: Invalid device instance handle. Going to try alternative ejecting method.", path); - return eject_alt(volume_access_path); + return 1; } PNP_VETO_TYPE veto_type = PNP_VetoTypeUnknown; @@ -524,7 +499,7 @@ int eject_inner(const std::string& path) if (res == CR_SUCCESS && veto_type == PNP_VetoTypeUnknown) { return 0; } - BOOST_LOG_TRIVIAL(warning) << GUI::format("Ejecting of %1% has failed: Request to eject device has failed. Another request will follow. Veto type: %2%", path, veto_type); + BOOST_LOG_TRIVIAL(error) << GUI::format("Ejecting of %1% has failed: Request to eject device has failed. Another request will follow. Veto type: %2%", path, veto_type); // But on some PC, SD cards ejects only with its own dev_inst. res = CM_Request_Device_EjectW(dev_inst, &veto_type, veto_name, MAX_PATH, 0); @@ -537,6 +512,76 @@ int eject_inner(const std::string& path) return 1; } +// this method should be called in worker thread. It does SLEEP. +// Alternative ejecting to eject_inner method. +// Success or Fail is passed directly to UI by events +void eject_alt(std::string path, wxEvtHandler* callback_evt_handler, DriveData drive_data) +{ + // Transform path to correct form + std::wstring wpath = std::wstring(); + wpath += boost::nowide::widen(path)[0]; // drive letter wide + wpath[0] &= ~0x20; // make sure drive letter is uppercase + std::wstring volume_access_path = L"\\\\.\\" + wpath + L":"; // for CreateFile + + HANDLE handle = CreateFileW(volume_access_path.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr); + if (handle == INVALID_HANDLE_VALUE) { + BOOST_LOG_TRIVIAL(error) << "Alt ejecting " << volume_access_path << " failed (handle == INVALID_HANDLE_VALUE): " << GetLastError(); + assert(callback_evt_handler); + if (callback_evt_handler) + wxPostEvent(callback_evt_handler, RemovableDriveEjectEvent(EVT_REMOVABLE_DRIVE_EJECTED, std::pair(std::move(drive_data), false))); + return; + } + DWORD deviceControlRetVal(0); + //these 3 commands should eject device safely but they dont, the device does disappear from file explorer but the "device was safely remove" notification doesnt trigger. + //sd cards does trigger WM_DEVICECHANGE messege, usb drives dont + BOOL e1; + for (int i = 0; i < 10; i++) { + e1 = DeviceIoControl(handle, FSCTL_LOCK_VOLUME, nullptr, 0, nullptr, 0, &deviceControlRetVal, nullptr); + if (e1) + break; + BOOST_LOG_TRIVIAL(warning) << "Alt Ejecting: FSCTL_LOCK_VOLUME failed. Try " << i << ". " << GetLastError(); + Sleep(500); + } + if (e1 == 0) { + CloseHandle(handle); + BOOST_LOG_TRIVIAL(error) << "Alt Ejecting " << volume_access_path << " failed to Lock the device. Ejecting has failed. " << GetLastError(); + assert(callback_evt_handler); + if (callback_evt_handler) + wxPostEvent(callback_evt_handler, RemovableDriveEjectEvent(EVT_REMOVABLE_DRIVE_EJECTED, std::pair(std::move(drive_data), false))); + return; + } + BOOST_LOG_TRIVIAL(info) << "Alt Ejecting: FSCTL_LOCK_VOLUME success."; + + BOOL e2 = DeviceIoControl(handle, FSCTL_DISMOUNT_VOLUME, nullptr, 0, nullptr, 0, &deviceControlRetVal, nullptr); + if (e2 == 0) { + CloseHandle(handle); + BOOST_LOG_TRIVIAL(error) << "Alt Ejecting " << volume_access_path << " failed to dismount the volume. Ejecting has failed. " << GetLastError(); + assert(callback_evt_handler); + if (callback_evt_handler) + wxPostEvent(callback_evt_handler, RemovableDriveEjectEvent(EVT_REMOVABLE_DRIVE_EJECTED, std::pair(std::move(drive_data), false))); + return; + } + BOOST_LOG_TRIVIAL(info) << "Alt Ejecting: FSCTL_DISMOUNT_VOLUME success."; + + // some implemenatations also calls IOCTL_STORAGE_MEDIA_REMOVAL here with FALSE as third parameter, which should set PreventMediaRemoval + BOOL error = DeviceIoControl(handle, IOCTL_STORAGE_EJECT_MEDIA, nullptr, 0, nullptr, 0, &deviceControlRetVal, nullptr); + if (error == 0) { + CloseHandle(handle); + BOOST_LOG_TRIVIAL(error) << "Alt Ejecting " << volume_access_path << " failed (IOCTL_STORAGE_EJECT_MEDIA)" << deviceControlRetVal << " " << GetLastError(); + assert(callback_evt_handler); + if (callback_evt_handler) + wxPostEvent(callback_evt_handler, RemovableDriveEjectEvent(EVT_REMOVABLE_DRIVE_EJECTED, std::pair(std::move(drive_data), false))); + + return; + } + CloseHandle(handle); + + BOOST_LOG_TRIVIAL(info) << "Alt Ejecting finished"; + assert(callback_evt_handler); + if (callback_evt_handler) + wxPostEvent(callback_evt_handler, RemovableDriveEjectEvent(EVT_REMOVABLE_DRIVE_EJECTED, std::pair< DriveData, bool >(std::move(drive_data), true))); +} + } // namespace // Called from UI therefore it blocks the UI thread. // It also blocks updates at the worker thread. @@ -561,12 +606,18 @@ void RemovableDriveManager::eject_drive() if (m_callback_evt_handler) wxPostEvent(m_callback_evt_handler, RemovableDriveEjectEvent(EVT_REMOVABLE_DRIVE_EJECTED, std::pair< DriveData, bool >(std::move(*it_drive_data), true))); } else { + if (m_eject_thread.joinable()) + m_eject_thread.join(); + m_eject_thread = boost::thread(eject_alt, m_last_save_path, m_callback_evt_handler, std::move(*it_drive_data)); + // failed to eject // this should not happen, throwing exception might be the way here + /* BOOST_LOG_TRIVIAL(error) << "Ejecting has failed."; assert(m_callback_evt_handler); if (m_callback_evt_handler) wxPostEvent(m_callback_evt_handler, RemovableDriveEjectEvent(EVT_REMOVABLE_DRIVE_EJECTED, std::pair(*it_drive_data, false))); + */ } } else { // drive not found in m_current_drives diff --git a/src/slic3r/GUI/RemovableDriveManager.hpp b/src/slic3r/GUI/RemovableDriveManager.hpp index 29363647c..264066c32 100644 --- a/src/slic3r/GUI/RemovableDriveManager.hpp +++ b/src/slic3r/GUI/RemovableDriveManager.hpp @@ -93,6 +93,7 @@ private: bool m_initialized { false }; wxEvtHandler* m_callback_evt_handler { nullptr }; + #ifndef REMOVABLE_DRIVE_MANAGER_OS_CALLBACKS // Worker thread, worker thread synchronization and callbacks to the UI thread. void thread_proc(); @@ -105,6 +106,12 @@ private: #endif /* _WIN32 */ #endif // REMOVABLE_DRIVE_MANAGER_OS_CALLBACKS +#ifdef _WIN32 + // Another worker thread, used only to perform alt_eject method (external SD cards only). + // Does not share data with m_thread + boost::thread m_eject_thread; +#endif /* _WIN32 */ + // Called from update() to enumerate removable drives. std::vector search_for_removable_drives() const; From f56cffe546d82ec045f998222db94078feb86112 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 5 Apr 2023 10:57:02 +0200 Subject: [PATCH 019/103] Follow-up of 439e9bc7e79abd473f04886045cf325c36c4ece5 - Added a button into Preview legend to quickly switch between showing/hiding custom gcode toolpaths for visualization which use different color ranges for the two cases --- src/slic3r/GUI/GCodeViewer.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 7dd1db935..36ac85df9 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -3945,6 +3945,20 @@ void GCodeViewer::render_legend(float& legend_height) } } + if (m_view_type == EViewType::Width || m_view_type == EViewType::VolumetricRate) { + const auto custom_it = std::find(m_roles.begin(), m_roles.end(), GCodeExtrusionRole::Custom); + if (custom_it != m_roles.end()) { + const bool custom_visible = is_visible(GCodeExtrusionRole::Custom); + const wxString btn_text = custom_visible ? _u8L("Hide Custom GCode") : _u8L("Show Custom GCode"); + ImGui::Separator(); + if (imgui.button(btn_text, ImVec2(-1.0f, 0.0f), true)) { + m_extrusions.role_visibility_flags = custom_visible ? m_extrusions.role_visibility_flags & ~(1 << int(GCodeExtrusionRole::Custom)) : + m_extrusions.role_visibility_flags | (1 << int(GCodeExtrusionRole::Custom)); + wxGetApp().plater()->refresh_print(); + } + } + } + // total estimated printing time section if (show_estimated_time) { ImGui::Spacing(); From 996a72a2737c95ccd965aee3e1f26275ffe02a3b Mon Sep 17 00:00:00 2001 From: Mimoja Date: Wed, 5 Apr 2023 15:04:06 +0200 Subject: [PATCH 020/103] format/AnycubicSLA: refactor Anycubic SLA format and add Photon Mono and Mono SE printers (#9929) * format/sla: Rename pwmx format to AnycubicSLA The Anycubic Photon SLA printer familiy is shipped with the PhotonWorkshop slicer. This slicer generates the sliced archives. These archives have per-printer extensions, not only pwmx. The name is -most of the times- comprised of the PhontonWorkshop "pw" bit and the model name. "mo" for the Photon Mono, "mx" for the Mono X. Therefore the format name "pwmx" is incorrect and we are renaming it to AnycubicSLA. On top of it we are introducing a helper macro to connect file extension and printer definition as most printers use extremely similiar formats. Signed-off-by: Mimoja * format/AnycubicSLA: Add missing fields The AnycubicSLA format description is derived from reverse engineering of the PhotonWorkshop output files. While the initial Photon devices had their binary headers with version 1 we have seen newer models with additional versions. Namely 515, 516 and 517. We are adding the version handling to the AnycubicSLA exporter to prepare for future version handling. Some fields were missing for Version 1 which are added. Signed-off-by: Mimoja * PrintConfig/sla: Move material notes to simple view As the Anycubic Photon Mono X uses the material notes to configure the printers parameters we need to change the visibility to allow every user acces. This will change the default behaviour for non Anycubic SLA printers. Signed-off-by: Mimoja * format/AnycubicSLA: Expose Antialiasing via material notes Similiar to how the other machine configurations are exposed via the material notes we are adding the Antialiasing config. Signed-off-by: Mimoja * Printer/sla: Add Anycubic Photon Mono and Mono SE The Photon Mono and Mono SE are format compatible printers with the Mono X. They support Version 1 and Version 515 binary archives. We are implementing them as Version 1 priters to reduce the overhead and keep the code in line with the Mono X. The addition as Version 1 printers leaves some features unexposed, most noteably the Antialiasing level configuration which is now always the maximum. Given that the printers check the eligability of sliced files by file extension we are poised to create per-printer sla_print default configurations to overwrite output_filename_format. Tested: Successfully printed multiple objects. Changing the layer parameter on the onscreen display succeeded. --------- Signed-off-by: Mimoja --- resources/profiles/Anycubic.idx | 4 + resources/profiles/Anycubic.ini | 103 ++++++++- .../Anycubic/PHOTON MONO SE_thumbnail.png | Bin 0 -> 39203 bytes .../Anycubic/PHOTON MONO_thumbnail.png | Bin 0 -> 35707 bytes src/libslic3r/CMakeLists.txt | 4 +- .../Format/{pwmx.cpp => AnycubicSLA.cpp} | 217 +++++++++--------- src/libslic3r/Format/AnycubicSLA.hpp | 81 +++++++ src/libslic3r/Format/SLAArchiveWriter.cpp | 9 +- src/libslic3r/Format/pwmx.hpp | 37 --- src/libslic3r/PrintConfig.cpp | 4 +- 10 files changed, 303 insertions(+), 156 deletions(-) create mode 100644 resources/profiles/Anycubic/PHOTON MONO SE_thumbnail.png create mode 100644 resources/profiles/Anycubic/PHOTON MONO_thumbnail.png rename src/libslic3r/Format/{pwmx.cpp => AnycubicSLA.cpp} (72%) create mode 100644 src/libslic3r/Format/AnycubicSLA.hpp delete mode 100644 src/libslic3r/Format/pwmx.hpp diff --git a/resources/profiles/Anycubic.idx b/resources/profiles/Anycubic.idx index e2cf7b087..a941cef9c 100644 --- a/resources/profiles/Anycubic.idx +++ b/resources/profiles/Anycubic.idx @@ -1,3 +1,7 @@ +min_slic3r_version = 2.6.0-alpha4 +0.2.4 Enable pad for Anycubic SLA profiles +0.2.3 Added Photon Mono printer. +0.2.2 Added Photon Mono SE printer. min_slic3r_version = 2.6.0-alpha2 0.2.1 Added Eolas Prints filaments. 0.2.0 Added Photon Mono X printer. diff --git a/resources/profiles/Anycubic.ini b/resources/profiles/Anycubic.ini index 9bebbbe76..771b38d01 100644 --- a/resources/profiles/Anycubic.ini +++ b/resources/profiles/Anycubic.ini @@ -73,6 +73,13 @@ technology = FFF family = PREDATOR default_materials = Generic PLA @PREDATOR; Generic PETG @PREDATOR; Generic ABS @PREDATOR +[printer_model:PHOTON MONO] +name = Photon Mono +variants = default +technology = SLA +family = PHOTON MONO +default_materials = Generic Blue Resin @MONO 0.05 + [printer_model:PHOTON MONO X] name = Photon Mono X variants = default @@ -80,6 +87,13 @@ technology = SLA family = PHOTON MONO default_materials = Generic Blue Resin @MONO 0.05 +[printer_model:PHOTON MONO SE] +name = Photon Mono SE +variants = default +technology = SLA +family = PHOTON MONO +default_materials = Generic Blue Resin @MONO 0.05 + # All presets starting with asterisk, for example *common*, are intermediate and they will # not make it into the user interface. @@ -2327,11 +2341,10 @@ z_offset = 0 ## SLA printers [sla_print:*common print ANYCUBIC SLA*] -compatible_printers_condition = printer_notes=~/.*PHOTONMONOX.*/ +compatible_printers_condition = printer_notes=~/.*VENDOR_ANYCUBIC.*/ and printer_notes=~/.*SLA.*/ layer_height = 0.05 -output_filename_format = [input_filename_base].pwmx pad_edge_radius = 0.5 -pad_enable = 0 +pad_enable = 1 pad_max_merge_distance = 50 pad_wall_height = 0 pad_wall_thickness = 1 @@ -2355,20 +2368,38 @@ support_pillar_widening_factor = 0 supports_enable = 1 support_small_pillar_diameter_percent = 60% -[sla_print:0.05 Normal @ANYCUBIC] +[sla_print:0.05 Normal @ANYCUBIC ABSTRACT] inherits = *common print ANYCUBIC SLA* +compatible_printers_condition = printer_notes=~/.*ABSTRACT_ONLY.*/ layer_height = 0.05 +[sla_print:0.05 Normal @ANYCUBIC MONO] +inherits = 0.05 Normal @ANYCUBIC ABSTRACT +compatible_printers_condition = printer_notes=~/.*PHOTONMONO\n.*/ +output_filename_format = [input_filename_base].pwmo + +[sla_print:0.05 Normal @ANYCUBIC MONO X] +inherits = 0.05 Normal @ANYCUBIC ABSTRACT +compatible_printers_condition = printer_notes=~/.*PHOTONMONOX\n.*/ +output_filename_format = [input_filename_base].pwmx + +[sla_print:0.05 Normal @ANYCUBIC MONO SE] +inherits = 0.05 Normal @ANYCUBIC ABSTRACT +compatible_printers_condition = printer_notes=~/.*PHOTONMONOSE\n.*/ +output_filename_format = [input_filename_base].pwma + + ## SLA materials +#MONO series printer need a significantly reduced exposure time but are otherwise compatible [sla_material:*common ANYCUBIC SLA*] -compatible_printers_condition = printer_notes=~/.*PHOTONMONOX.*/ +compatible_printers_condition = printer_notes=~/.*VENDOR_ANYCUBIC.*/ and printer_notes=~/.*SLA.*/ compatible_prints_condition = layer_height == 0.05 exposure_time = 7 initial_exposure_time = 40 initial_layer_height = 0.05 material_correction = 1,1,1 -material_notes = LIFT_DISTANCE=8.0\nLIFT_SPEED=2.5\nRETRACT_SPEED=3.0\nBOTTOM_LIFT_SPEED=2.0\nBOTTOM_LIFT_DISTANCE=9.0\nDELAY_BEFORE_EXPOSURE=0.5 +material_notes = #Distances are defined in mm, speeds are defined in mm/s.\n#Delay is defined in s.\nLIFT_DISTANCE=8.0\nLIFT_SPEED=2.5\nRETRACT_SPEED=3.0\nBOTTOM_LIFT_SPEED=2.0\nBOTTOM_LIFT_DISTANCE=9.0\nDELAY_BEFORE_EXPOSURE=0.5\nANTIALIASING=1 [sla_material:*common 0.05 ANYCUBIC SLA*] inherits = *common ANYCUBIC SLA* @@ -2380,10 +2411,66 @@ initial_exposure_time = 40 material_type = Tough material_vendor = Generic material_colour = #6080EC -compatible_printers_condition = printer_notes=~/.*PHOTONMONOX.*/ +compatible_printers_condition = printer_notes=~/.*MONO.*/ and printer_notes=~/.*VENDOR_ANYCUBIC.*/ and printer_notes=~/.*SLA.*/ ## Printers +[printer:Anycubic Photon Mono] +printer_technology = SLA +printer_model = PHOTON MONO +printer_variant = default +default_sla_material_profile = Generic Blue Resin @MONO 0.05 +default_sla_print_profile = 0.05 Normal @ANYCUBIC +thumbnails = 224x168 +sla_archive_format = pwmo +bed_shape = 0x0,82.62x0,82.62x130.56,0x130.56 +display_orientation = landscape +display_mirror_x = 1 +display_mirror_y = 0 +display_pixels_x = 1620 +display_pixels_y = 2560 +display_width = 82.62 +display_height = 130.56 +max_print_height = 165 +elefant_foot_compensation = 0.2 +elefant_foot_min_width = 0.2 +min_exposure_time = 0.8 +max_exposure_time = 120 +min_initial_exposure_time = 0.8 +max_initial_exposure_time = 300 +printer_correction = 1,1,1 +gamma_correction = 1 +area_fill = 50 +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.'\nPRINTER_VENDOR_ANYCUBIC\nPRINTER_MODEL_PHOTONMONO\nPRINTER_TECHNOLOGY_SLA\n + +[printer:Anycubic Photon Mono SE] +printer_technology = SLA +printer_model = PHOTON MONO SE +printer_variant = default +default_sla_material_profile = Generic Blue Resin @MONO 0.05 +default_sla_print_profile = 0.05 Normal @ANYCUBIC +thumbnails = 224x168 +sla_archive_format = pwms +bed_shape = 0x0,82.62x0,82.62x130.56,0x130.56 +display_orientation = landscape +display_mirror_x = 1 +display_mirror_y = 0 +display_pixels_x = 1620 +display_pixels_y = 2560 +display_width = 82.62 +display_height = 130.56 +max_print_height = 160 +elefant_foot_compensation = 0.2 +elefant_foot_min_width = 0.2 +min_exposure_time = 0.8 +max_exposure_time = 120 +min_initial_exposure_time = 0.8 +max_initial_exposure_time = 300 +printer_correction = 1,1,1 +gamma_correction = 1 +area_fill = 45 +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.'\nPRINTER_VENDOR_ANYCUBIC\nPRINTER_MODEL_PHOTONMONOSE\nPRINTER_TECHNOLOGY_SLA\n + [printer:Anycubic Photon Mono X] printer_technology = SLA printer_model = PHOTON MONO X @@ -2410,4 +2497,4 @@ max_initial_exposure_time = 300 printer_correction = 1,1,1 gamma_correction = 1 area_fill = 45 -printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.'\nPRINTER_VENDOR_ANYCUBIC\nPRINTER_MODEL_PHOTONMONOX\n \ No newline at end of file +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.'\nPRINTER_VENDOR_ANYCUBIC\nPRINTER_MODEL_PHOTONMONOX\nPRINTER_TECHNOLOGY_SLA\n \ No newline at end of file diff --git a/resources/profiles/Anycubic/PHOTON MONO SE_thumbnail.png b/resources/profiles/Anycubic/PHOTON MONO SE_thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..9fa39cb4f1b859572bbb59f6f29c205ff70a2f74 GIT binary patch literal 39203 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 D;k<`{ literal 0 HcmV?d00001 diff --git a/resources/profiles/Anycubic/PHOTON MONO_thumbnail.png b/resources/profiles/Anycubic/PHOTON MONO_thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..0aaf5c4cd6484315f7f99e187b670eb87dbed7cb GIT binary patch literal 35707 zcmcFq1yfsHv<(ggio3hJySuwf(Nf%%0>;5#8L+NwyD&qZBXK zJO6gR{ms~DoEoe@AhWFQ{1&%7l+TpY3H<1sI&_}TGIlgY`r@CccMd!euqQGd{twc%mH(&_c)D$vdebKrj-)f-gX zCWh2~thZ_GZ;1@Lu zzHUQ7ok!I6M6l7bJ|#9cptX7aVSvcob8BqD{LZ{4a47-_yY4X-w4o(gh{s&Y1?|t* z19MP_jpwL?0|+4hN^Qq63aMcrB*zjmD5no2S+1W`+{3}Q45)}olgqysP>dmSby5G} zqwft{kT7zmi@#W)Rn}2XwQc-ysVMYZ`!NNd^L%Yop;Q!EB1a_Pw;$w}D419xc!c)I z+_$Qg$J_3CSl;y@^m4=Wngld=JhV>GQ!?7#N)s!B36(E4rJO@acm@y!UE^y!@^*Tp z{Sug)G$_0UY`l$R0_Dg75#_?l;^?kW?iC1LEP9*Ku%)Xx{;6bH5)Qv|Q?)sv2Z*I{ z3DuAtfBD#2EO(R3z{b3!^6HD6zWq^4o*Xz*>PKn15L+V{szW)A3K{CT3jK}d@{urZ zA*)S@qNK=^v7YiYax?1WmScfwzvlO5ez9o{{PD56`J`19+6Vkth|GBbj@0>{zkJ-i z8XH>z7z1YV*_#aCCdWQpoIk$|a4+YTr7RXd-@M3`4Ozr?yjL%uVj8XgQe~i_v6_6d z01VLktUU;qO05c0rV<_DpCpo|N;o~TkPO2Z_~m!%jdFqyLza}P1_$X=YalIb1I;(? zGFe!RG7Qpm7(bg@PLgC{(q3?afWLuc5PgLR(Z-x&G@C<{@NrAt1nWk`MwXn4GP>mA zWgVwy=Tv2Qqu;oPC8yB!zju6lI6(m(c0E_pfi_;pg`7{FjUIv0-0!(Wg-8r;&|9ee zoGK$uUS5~)dk1q|uv7RZ+yzVi*owlixhS>Cx``+;)WOVwJziZW3!6wC7e+>N5V;kV z4IT9G;socGwn41TYg%%@Cec`ezlXG7XbF15dBTyfPk@&uDnpI8-FwoH!Ak&={G~Z5 zMIhlKf_)TwQ)$oQ%n}j(ZQ2M&J4IV~rw$p`$Ay>y1AN^C_sJ`WAl$#fV}aygWO<|| zqIkHHO!6%^{g6mLtd6g$>Km`QIfY1c|1MlT66(i>JZ9uVQEsbo13GL>=^YEly>u2W za)|*f?r6F@)b*vnnv)+EN<2!|pDq1_%Z53ib@#~5ZixrO)ceTjn5)Uw96gzy9JYd$eYsRm|bGJ4AI z-|X%sx|yfInrF0`W3f33yn;jgjl=jv~ zg@+m6TW4k4+3?P5kqS%h^DOZq!<3bn$o z3B6q`3LZ%vi$!LEHAAW@$s9o}`;M+$ij3BbD_Mpp1e04!#5963Ghm=kn;8BD{`mC5;fhYB(52%T3}SAI@? z5nhz07CTsiyAv33U=ti7cz!H69z1=iCBCqO6IdB#y#00Ww&s0^0?7$wh#?v5azNOe zi7F?fAkU;zAKc;cy#wi4<$2oVA4Ft)8;KteMI0GJ znpz7vdxOO#pBgk-ra~1_UsQG9hsT?Y-dmFRaC28Q$TX2QWnHW2hd^bFL&lHP?9E-D zXv$Xpoo?Ti9_4>E?wqrx4?I_{VFRQPb29Xh7g|{1qN9nxlB07K^r}o)Ti$<(gTAI{ z-s@gM&It0RlRIaH_+@#0UwQkiJ)b7uz0OQG;|s?f(eew%ZJ4c|yBQ-0S@_4Aa}9B| zh12uo^=l`$MY;fVYzaAXPdnr*d9rn-NLjlQ7xiz#WhYMq8?aes5gqWPzG$_NpOYRf zlg_O(Qp$BG;v$v3>^Y_kD57Kc*MDN`xjP*lJX}+tngM(e4mk6UGN#gG6IKWKt#k>0 zDCpum8JxAV2gYx8J-B0!7kIwQmBq?6u4-#V?llK}LxxPEWqZ*_DHQ>S%d;}>^k23z z>tr@TU&}1A3acf7EG#?S*e8ts_AwgP@s>WKja{z%4HiQ*qk5uD^uUURYi+1RvHPT# z?|S5X*g zom0~6JLGX2&GRxxH~*KAYDn&4gKziV8~$R`$iZp8PTyZMpsWfmugEIOVwt!s>I4@i zIg-L|@H==ysq_6xYKGR&j75;=*&sfs$Oc-^*BVC-o+K)R!C-~H z!_&-NMoMO4aJZ0X^f?r(G}v7fkzAG<&ZH%&BJoIC!syJ!wVwdS-cJw)Z}PVmE+_!I z5>}P!P&Sn)4Mgjpgpw*_>&JY`JN0NI$ni6EyL9B#lGnnGj7rc^a*9jrX+dr~;Ph9IX6trUadT7TXCxWzKL0q!?wh zZ0Io?k!PmQl;oEsD#KZX@0-9uu0b{>%J9XnTR=WM6gA_F?Z+2!;Ic{P?W~Aw4FNAx zt+gc8{|XxZ33dpP%YtD|zy0XMnLe$u)o}jh44-VTZ;!7z7Oa|*8ZNp2A_ciFmZrAP?F^xtLYn2l(_CfUVJt7BN5EC70#yIh$|Pi5=R)87SMdl7 zigoD%e<51pfH1gsB39k$3#-@=T9uK4&rKoSGC=w_Ax(kGu%Wz*D!$4hnyQN^)UBsj zVQ3W9XkEvY89z9RNrhELa^pyd1FO|?#yIxhLiS~{0F)#pHnQ3-_2rsx5EqU>3Iepc za)YDB#Cz|Cb4HH?=G6Vf&6DOV90zp>+o=AhOt+RtW1`~g|AlcQTIE3INH(Kw5xB5_So&!k_g?fe^4|k;H-jIQqvOQFB@RJ8^o)b z(W&CaZ+W$eQNn&aadQsh?EC&uYX)I3f?<*5)At27@X#e@9&f}^~3tom{ht`gw zAH}?K_(w}go03F6Gec=(%k0AER?nUV_k8AeSgn~lO+pzZWM6r(mMtTiBnoP{zQoad zvlcmvJnIlK%!Vr5hX%LCvL=g)eC3CXYFw&`p(SYo)7W#4^M*2KMX|>d-yLX~;5;Zi z1e&C9*hkP*Fb6mtI~$7GM$t_%J%IlglMMo{<#8yfEdzckTSIJ81Ef*DetEhcesPhk{oqxCA)c8d*+( zy5z^HCs(`jAW$w(@0%X1WFJXEU-85&$J;}>{ncE~tE1U$jrPVqcc*MeW-Zqn$w^Rb zK|5k38asVzDYB%J<9@B4x&|aLp~O_)h+CKH z-5q+1^o-wMu11?)n;tXGpxDcglXj}8@XQI9C!)jTEK83q@lV_I^W4!?ln&a>LaaIkic$q6=xj1g%R@WB1|dw9apVref`idNCb{D+tNTMtvCAQspbW$0@7B?K%l9pXmbe<2<%u+KBsyy48{; z|J$1p5z*^**TzjCmEcLiN%2Q}2+@a5x!)tb6B-Dm7xCm-$|&I()Q)q`y94(pJvF;` zAAYm6Pw4e9@O3!cG0W=}sh|0o3|Rd)CzGj#w?g>>=ooPo2Dw(|cl-y3Wm*|hjy`U- zNO1Qn!9RVFnU&?}<(7=tt$AYi09i4=y@vo$4rXKwY*TUm$=m+8DkAhIcQJKfoDX5R zD=tpx-A4FP094wy{JtCMk)(_oLJ6-0oNT$qSkva*tVAmY6?+qX5Wr228mL%V?t;~5uTB~k9&L+E>IZJ_y{qUxoW4o2ajj@;L zc!$?`yaOKHTrZd--F^XG=eT#>0EZ|&WE+ktX^m=B?hAnMMb$c)Ftrp5*(hanpyC2c zldP&kgeaRM4C#I=6gXIpj-?UF85IoL@u);p+cq`;*0(0eX2@(*X^k|GQfS7c6VdE_ zisCGOIQ7!;ouEXu4`Da*9RC(DHFD0H=9g$@uZQZ4j=L9rUx;g-N9;1PeBf#8&E1B_ z9n9p){&oj8@I7s7Q{s+2cOpSsHffT%vt*>|qYc%lp{Qff%oik(@`A9*OMBwu_Jm4!ZlWD`PjgGNtS(_K@kIHdxm<3=x11IhDliS?Mw(zpAp z=9Xzt$`QpBL8B*m;Azd~Bdn{}|40NxGo<%iIsf~eoY0=>IvPhm_}xy52HY@Qwq#f~ zCdg%k2UUIuqsIRQJAsJum?8b(PmWCRl=qvMQT$_VFCFtVj>4-hhtX@++i=Ha$o1iS zad6I~;%*N4lV9+hQRuFH>tFTG-v+M}9dmOzLchFXPqyl9>+f>7J3&c8ySMge;P{5V z7w6ZH2UF|5FI?GzEpL(P@4Ry}fwCKW?^#_V9o}8iJ7Fr=DpUsQ%+*wjf@m({Wr!!i z5?!$)ZOa={53tQS5QerR&kmkpd0cf%8$I~j8 zM9y>0(A9PQ-lDVoDpk~AY*5F5$%A#ymB(RDRxogDli)fO81!%}>ElE0cRTPp%=g1t z9W^v`%ID7)?=H{F_v-h^+x=>Va^C!?QnP2*tAhHc)2SbCn0XQNe?=yXju95s*x5@K z!Y&nRPn?Ip8~v7KKSgLQhBk{ScR3MhCRUU$%RZsP{+sh+UGQA`F*}R*q1w=7mq%s3 z)5yd_V9hRXe`tWAWW3pBVLI zhWj!gnW3Uc8?BcY96mJ+$qj2pELXdk?`a`sL*vX)F@@R;!eBfB`tdBxDP5 z4*ZPtr!I=9K@W}D3l7&mU9ll}lyafcSceWIDaWq!(yd zro-CSRZo1cW%@y%big9j|B*W7{cBoK23JD?wfC`SjSLTf9hNtH;}Z_ln2MHsUV{9R z1uK?30V&<8qb{mV^l=@oohagBSWxuX$sL#kzP1iks(*8S$QKLEb5{G{W>cK`o@J2e zzx~n8c~K&7PmR|~gE!vh3gQQK5m_rtRLj`BUnnuH+P|w*?qi8RY)E3pTkneSgNIPO zvHl*SGg3{`4*ovo^K^oCK&54G_%d!UUp8gJ*|Qhb`1AM(ZGPKRwG72J{2I2v*O9%` zNzE)GEs&^&tA$66MPF%1x(zd}`a5=Eb;1r4!S6dajxS0d=dE4`<8va#Q}>?jWnfV_ zBbGu3y1tEdQ?IQ}OabCoEHnfr*A(;u8HjRJcX&T`RKvuU#pua2y_hjgUYs2p)^})KJ zEZZ0}Cu}0+{VmvsQ$44}_8sL4SLQw4deuW4HI*fT$+W%;cjbZWeMrUb{f+3D(|ch@{fp5@4!-_NzmfOra?TUq-=rzDd)iomTcoRbxsU!1 zOTqitsVaddnl-)T+ePh3qZzN8C8JdRkMPZyXL_xiSApY$-JF-P+kxK`gU=S5P zFw}oHAsBL(SJjvI?{>n+wZ~oD$ME<_QesRTzM}?s68TyeU&Qerp~8Y#zqA=FL+eL& z7D2@;qHx^zEnx9qA*a3LV^t>gz3wk@vStCgK_O^YDluc|g~Vu542vX?sFZTwEj*kb zM1*9CbU&qnxipF@y_ajC&T+F^*<9V~r8(t~;{o<2dW^MdwppbvTEVwU&QCwx^^rE) zFMR)%$*02V9rz&@%k)uO*7^j}5M0!KK|!|@UU4p!Q6>3{QZSS9J)`SE-Uj$2^hMB{ zF_olFn4g$awC{1KrwZqnXm2k~5u%UNxfU(TkXIu zBfJBO8;idjvgueIdb~?n_xjbv+GvFykz+Csd<|k%$$YBsTT9W~!3XBY=wB4)e#>C5 zm}QdRPlh-4?<9w|aSnKkMsFjWe!K8TToJXUaM-3#n0A)2+l#Q`!89g_qpS2Oz|)*U z#BPBoh<+(ZcggYV{c6o>;Q8G_PbJY)C?pzqUH5URvYkeQ+v{n%x7;998pa@vDs(WG{Q&TGecbBhoN7O)rZ)l z8?0Y7B;DA&-8WVA;~#%H1{3u~knhmznRbrpNH3xSNv^Xgt%u4a zhi^KXCBZ|kzD(Mx!V)Xc^mi!fzPjhba>SW&^kiY{Uis4U6xw!;jAT@g9$!Q9y%BUi zpMKCN37scxHIIi=6H2z!F~1umgRD;yn}L`1uoKj03I~vb{wUmQO13; zP+i<==$>_!6c&(;+|B^iZWgqyQ-KykoEj24Je#uiAVI+PvZy0RzqHb!jJNsAw68y5Y?&_Qn7?lu3m}8)HASI)mgIitj9duLygrBBTG=Y zvs9J)<7q`AxkP_rkrEm+B1~pbDxmm&I%Z+pd?R!dSjLRO`8=O`2$Ju zXtEkhTqel-mn@kov=^*cuR1NXvnf&GU6bDNVTCMsG98I>Eh-EP>`(sR>E6(cn2#}V z9weXY1l;9!cGXv7YV$al1alOGsU4gH#Jrr^P>FXW{#jbJ8q9?I0M!_qWdkY-PHtTz z2jI11Tc@|^knM0UE=%DWLWBJmr+2^aEl32n7kSfiq==u92&>E?ZAE6@^L4s~vAYsK z-moHqnFsBrKJs%aUdwO3H zCOF!%_p!E3fEPTOoLOh$;xi}GEZb8rOY)au5+%jsG^4ngfdhKDzvGz{oeuFDAuhw` z*>n&$p2_4Q`_(mi(Y{dMeAA5D8(b76u^VWlSgyi8BK^d3i6cT>d$y0Rlm^_%;RrqI z4!|mFDdOD)(H_7h8d`s$YZ2&qr0|&hhG;pX!Li0Hr@|Ow2lva@KhgQ@Yq+@6Y5ey) zRD+HZDV0;#08&QM`9fq%s-~2VsrNAytcdb>QN=z^e}@%TA(_* z_SsC#ju^!GM03CuYSYUv94mGr45 zk7e1xoC4|WY~j$R7YM&FiDiPFs(-F7 zag*NFk2)TeifYJyr-kOK^*A>`9U`HEH~EqK>S=KH2!3hKpuI$}xF`ftV9H;!ACY6@ zI`0TnjF_Mq!$3hy=;#|5Sr2X3iY0WD?}kUlT80R#HNQQc@V$K|HBwiLJGxj%x0cO0 z1U@hGchnQLn042tKlzf;a=OsBZkk5uoY>?l`>0SExew5TB-ATkoVEGzJg2=ZcRMrj zUOD-Xo|Qga>Bd;--Vn6X0ULOs7cS_Cu1jtAaZ~*U-dFIow*W@r)rKhldCaVjZQ>LC zjmfbE@l7OS+hacRQk!0nM;-juG;P8YzRv5_A0@3x3pxhYw}O7trGzq@o!;iitXv|l z>fEqF&L>Fh?2?^tiM+AJ&M)TE&)y7jHJ&(qioFKKTW2e6GIq$bo>1B0!ud~l7DbLy zP@Tx1B>*8&42{OH0&^l%F;T)3UD2bsU z7DO{7yBKq8qIo`acJxEG&KpyMlhm}lYIkfjFHNWyBQ*BF5c;v`TYHa0bQwtGK90f* zla?i$|DB@1;|I~8kPJ%K>w)WseL^T;mu|6p_4IyYd&ou8X|qu_`F`rjobahp*umSl8zUVe(lFdnZq%cIGiQ9js7VMStGPP z<#1zrY5emc0soS>ID`03YBKvaB^M>^l&^XuE{vEGhJ3u|&uI!PkTBs=&DG$ zb}jdf+BmN*GLF5&(PQj~L$#{9`bjwJ$V$eg>Khb+2byaaI*4%a!Cq1D+RUmnt&aB2 z#!h-{$~eq-_?lL;7q5JpL~rB3m_uP)pJLMwiGC3Q60eXW*EKO3bk#)lA0$y*gC$k= z@3$YtT@>fX=a)fGZ$1Ao8o0#*$zfA6k5+ODAyrV{o+>-aJ!yV5xKq@+QCW(}-}x zQ0-UvzqHs%pDz1<>+kC7Dc|~LN#EXPxeqW2T8d2U5;=j=VLg`ZamE+((WoaetF~lc zexX)2ZHw=={fAmx3h)|{h4YaCfc+l%pRQD4ly&;9C*_+?#IImzlR*b$!088wlS=*e z2g6-x4~t3HYBnU|yEga9MU+$?kv~Qshi?_~8EWxl8TvW!rUZH>00cMX1o$4+hXvDgcOmpTZQLZ=dYHai_?cy&lWz$vD@|*_{nh9 zqof77>++3RXSGP$!X3>Thd$q&Qc1F~rPS(zAy>zyPfTZ9+zGzs%Z#IAS>tpyG1FO(sz$j-R{nKTIG z*_coE{)t*7q)Vtxm~e^hPKFB6*<=RJJ1ekkz9(i_XXl_{`Y z?kcY_TGK|R?zfsR!w-~6OF=uhjshYQGBS~0;!M+(;gKQXz-?z-Y*L8sAnx=2ZEgq_ z5w0-pyKnGhW!eDiC0zuoU=UgKJidfCGyisTwgH#uFsXHEf_kuyeI5an9n4h+D{AO( z$Em0!fuzuBf;@?eO)BdRfX=s@+z7(kDPJsq^U&$E5hvu-f_d|f{)i7Pj*Tz*#y+r9!&%i zn=88y=9L_#TaYFc&I6O_=h6}i*M!w>l(2~BI|Yfm+%7|Owcb*BJ4tBq7TeOUk7T`G z{0$gc1P{p#YBZ;6!_IWfTI%q#>7-+|>(X!On(-|K+1t#x2^vREdjHgZvXm zU#gPV~ay zd?%i7>5JQj)as`tS8tX{#;eJ^O%cRo)lbgc-L-SdS!@)LvL*Cl3pzcvc>Crf_Pbx8 zok3(NMrf%PDiRF+dnBntTLScE#6RS&f66SwSXs(zlF_jgB7=6Wd%$fT`z5{VQk z)J<&pf`Psi_r{yhY|)^olJ}#?66=s!k$*Q+h1f7rMPrzz6ai0_!$jp65K|}{VlNU$ zN)wf*-`iHO@8{HiCuu#`q807fI_wsP)kKg>Q3?0`Rlixi7ntKu*4Q1Ms+H;$Goh_R zD}A9g-)Nu%ngsmEGX0ktn>jitbCK3gsw}f)0lMw#bBK%kmpp{S<)LS3W-`}_nM>qF zkt}3<{e7_MAr|Uv_H^xmo~P_%aHVkchoI8IfCqV;r$l9qk_$4yo($sz!rYhPk4*pu0^$n-{ssu2 zLCWWZ{fzR||Cm?=BXbUOq`{To{z7)NhOx5q1O@@cD)Qs^H!)pgG8DBr$&$yqt&L)R zURLcL>w?w$M7OS<1&4|`9?IB^Hpu>RCvOgS1cXF2!p+aS%3;_3 z?P&oN1VNPj;KlehUv3Nf>7a?L)hmnG+y~RmjTfJ&d|)|x&XqZ^9BlM7s^@c$C#R@e z$Xn9lmh0m}E6#xnzb2rU4-T(km%~jJIUN1wtGJ5O0aJwW`MK;Ex#Q$f= zWo>_NaoMx3@nF*oSPR@zBo6^Soa<&j89OV22oi}S) zsNAg$UljZ5(ww~s(-zfmNly|)RqSXKVoLb;CwVSXMQ0<_8Ge14J^JOXvOO%L0sog} z3h31PO?mz|JLPeh2wsoi`Qw`-88$cdRe(se@B^|Y`iZ`Si(MB4MES;f*pxZ9rW>e@P%s;sc#{Z`|taV5fg1X)FIw>>D%K`zM?q>7= z;Ze|-`#im}_RXY-8btu5FP=NQcD}qJe?D&_&A&lPare)%gd0f_CbI^x*HW@9Po$_h+D(4z{J(iwsux?;eD!AeGm|vlA-Pc91MeR6b_d?Bb$Uc{3a5sk~J|^|Ue4np+{N~9sG8n~nt?xGlc!1b=*7rbJ(NZ{hoM<}v$;VzLzj&pN zk}xjrChTYeB}5e)F;&eF>)#CSJuo&x3u*CvEFJeIi(~!^(YA~AA~0WimSG_g`%`Ng z2UZM_9q|6i&{PU?%C%#?@0EQhu4{PWHpIzKf)?PDSv|DwcFDv%WGsWGeGL>NVN>ne zj?dm(d-NvJHQKyJo=+T<-ppwWrT=KWvqV{OoHK*c|F7b3LfCUpB+B0`zI;e>fZt z0bP18-vqd7e$y*vZ9e_j0)l|A-twmz`T`{_&LD+{hblBlMEmT_>%JVnK;U!EEBB{W zTv%oOfMgQ4rP(i9+M@K+Romk!JHmf>i=T_*O9<4Ep%Uh2qd>RXHPe`{|bs3O-GkDs& zZ3>_5wE@d&u?*c_#+*i$eg70PD$wD7lwQ@9D7<0ubrX8?bi-&Q5D8sqi~}Qo+Tj4x zQo#3U3$e@?HNxq3R1ZyqpK`}U=7-yW@ajKGlWR*hRzJ5$FCnVU(Kn1_Wd{dsOjwWb zH|S;usbA84%bD$aPxnw#9?Ow>U<78aH5C4hbyw{k*2kc=blKAkQQOGfU@Cds5BySV zbN(_o4GHXM*aH5hO`eTIF5o`HMrZxjb#WB%ib}Bez8quHN%`C|NbMxhqYv5telbp| z_GPjR_InAa2^0BfuXiCmFwqy`vOY!Kc=BnV_}o~CR#Y}TakkH$&9A@a6u|{-rE&jg zGGkMLrIEx1eSz^56PX=UbgPYR^+5QFK)fgYvNAO&Gpc*mA)QQPa67g8as>jOf8Bgu zV4)hi;_LG5J>3+LU?I;YjZ~v-6@Pclcf_FT#@BZ-Ow6fUTF$C#|idB^waf_;aQ@qSAJ#s#|+Y z6SBx=pc4DO&ukFj$aA>0va7VEX;mDr)=$HVo&-&iocwUvgRpzJIF5%`(GlO-$(hvN zbk|G7BR+k#bBpnk5K#^j_hvQWQN0iP*ckGB9Zd}q*k7pZ_R#&txP~43baboL;waJL zwsm&dx4n%~g}e(WNSOW_ z!aVk4XqEv>wkMvijF@J&5uW}c8Sq~jbgxnjRr{WD{E+egPxn()zWFf+3@$j;eCn*MF0^e^n++j`Cm z`+mVDB?SFqsjf-`c`|R?gcBIrQCnWY z5S}?#hir-g1SL-F**jj3pQ=!rYT_5MOnX3LK-5#0F*i4SoZ;^IUO3c!_;z3I31(vb zH9Cb+Xq{Eq%=8NMQ`6}^J=AJ&gOK+ZyuWygXLvqE8?nx;J_NDq;tR~n?0A_rJzaxDNIzOkl~|hA$9%aew#&q^Xmr0f^ImH*M+ObCE$V~L1w^| z3+b0~GL0)S(l2~$JkHlMNY;87<+x#jz`#p3!t-5TUDM0fCt;lB7AKEklQe5s(W{-G z2=+5xW}7ep9gOBnzzvuOex$9K6@GssW9gq|*#11d4f^(Ci&(j;#kjH6O7?3WG>nH` zPrlwBe*XUjM_!Pj$wO7O=0iTky5_fru0VJ{!SEPgnX zXquo;eZ@eB>6j}t2>^qXO#I~C*%IUBsCuS;8;wJMMRZ^Siiee7IcN70L6heCeDQUC zi)h#fan8TvamXR(kZGMEkP)RNelua#3Y+eWC}s0ckVM|GJJ^3?M{3q7My8(*BKA8D zyNPj9zSkG4kZ|`6j~*zRb{Lb#EXH-WYtEScmU-}L)9^`oM1E$2~7PQkps2bJC z^Ea)l!>ETEg|KiO%mMRIn}rEqr*y+IGT+Y_Yz|Iggf2>uBK;FJehb0>Y{sb@EIi`R zm%AvSk7Mt(aH|y@b@r~f0#A^TkZjNLutv!V(GR(du+Hov| zzm-J=-G~BgwBKDHrztNC#9~2Khg~rr7JGmkHLmiJahNipM0Ick2Q!Tr6|_Tx_UbdA zonja|(v4f5?nd-@Ccyf}{RpEcjm3}Sc1wS_(RHQ)Y&9@+Kzqr;C1uESdanHWm&3Gq zf5DP3bLhV@Vx`TP((GLdMeX%$#vpu-C`{!Fb-IIbIiNHAl+t;RHjZlF={UYzksq#@ zksD3S0s^Q{tour#s{GVphb2=UP=!Q1JLd}XDnhf2hq#ao+NQ>hx;Bg?@1qwzbOSBE z+6^c}Unfhc7yR-n?a^PVfVUO#0m-g!TUEn_Q)g`+-7<%-LK2sq3;- zAv%Jz|Jhk6~ze$m@`;+jwyFz@($VG-hzyKK)Bs9%*VQPi9m}g-`HjE-)ZdGbc&@DB-soGQ$v)`c zk8ir{{5j8&^LVCV_&U!(T)PQLE1JW~ZXcHZ9o+{Vx%B#hhH`G`7qh4+lpz{@?lo^u z5)a}C#!T|{My7>h|GQtZqPf|=&RM!BreG54Fn7Yd{C6C#j?uEVXAX5!W@ z2%>HvJd8;$y)J>5Nnu68(NVlFI$^ciS6FE_6z{Vm;7p>zTo}6fR_Oe5d*mGDu^7FD zC@foh5=KqLWmF!MjC>;7?0XLV_IQ=}Q9O=Km5@)qYkR>YYBXmA@nXQDhC^;=%*kU zY7EE}(&NyW5E;afGKQzxSp$s;`slP`#V&ASVW`^h@O6Uzj7$`79v1Jzp#9^Gs!h^UaX_S!h55hO6e?O|>SP5f5(bOX zrON7n5ugcZd|XZg!US?kT=bSDpM&IA+o?3DBZ(ihL|%L(JEKsc7msaKN`DwIkcK7- z<+K1I&@a52-2#2<5Pb4g@?f!{v+M3Qb_p230C}Dl?f(I)@p5Zf|0Y49C_E|#U9_W6 zdK2A|{b!um1WaxCAEVvieLPJGbhKw>XlPdlo(lhzjdXhh@Xk(3)Rut-+23zaP!R_q z`h3@yeb;w}h#JwxQNm)Mz+o2s?##zzk}YGvrdPFE>AwmKVjXKcP|sax=Dl!U8~UC9 zN>+8JdI(_vJYMz*`1Ek9)tlWwRFR>N4xh@8$IYfpq^+hC(u3h+6Q}dS?MSzXvc2+RUDmxm7!Ks7s8BO&<%k4I zS|aM9dZTnnP(0#KxiMWdx9RezJ zyDly{Y0zz~RNUh&kVsPJWH|vRAtf8_FXz=hn}j(;PBA21qQ#YW)CfAxz9G%0`t0|KOvEn@I1-0jo07x$oLqw zdvwK+mj&hWr;=j?h|QR|b%$Fc>v8_jj+ln`)gbuFa(I|j&`_GcLtiPu#v zT^T}h$x;;&OZDXI+M5&KY!)TQ*CRVN#i)>-3{|YO8yZ@P8k1%3F0yO7+j%UHT6d@- z!znL5SLWa9pwEUC20zG6+F788(_O8h4D*?<3Q7{6UO0JTVG&9`xt)^;V(|@EwYju7 zRihBqX2OB&xXz-bf|dJue9Qr@0pznd<@_%hFyI^qJdL?^s)F%$m{UKfAiV4aV+UA8 z>4F`JAz?~;v>4j71_ajKLrQxt@SVbIa#r2&%%*uf9E`42;iu8PoT~^E`Q<$of~<)naCAQm@#;I1-qRUv4rUV={cK#fmA&I z;>5^+;fOXfT`QbT^NE$x<>aNmKfqJ`1Xh`dHUgOU9fUF{YS($23VK7zEP%k?GP7@4 zdR=ZdI_8HGLt&bywu8fqO@viz%-_KaR1|)lJ?*{2ilI(P3Vb|8(((X}6kf9yh9E9f z+zChxdQ>?X6(Q;t)qX?4k_^$(7k=7S;Ge%7CjFsv(H`cu2nX8tHQ51!&wsvzpqw*s zQ^8!-;P^9ao-e#vy9C)S$1P4DXr4ST&NOj?>;}##Dof{iCozJ}yRw{qWncw0B?lh4 z!D1gcCSfz+#8(Y0dezU{VRi1`EFzBeJu38FnLOMek`DnOX;^TboN}+YRHHh`m%pBV zRx(2kGJw4(5$Mb$jz+DddC_05+1$Ib0?erUi_2JwVL0)48dPv*Wh~pOzLJYOqjgbl zlTJKE80qSy%S$^hq{kN{P2;e^f4oDJy82@p&1=!{H9xEqY0}4)MQ}K(A{6i{Dn=}p zL4MaGR!@-xEv?E8TmE896YoaY{VyKEjH+7qf9lY?cXfk@c{|pftr( zZK7B6$FB8QQor3Zj|H5sA}o%R>O5JjAE)@$xQFD^^|_0V%)~0w#Ov-@pT2kYC^FrZ z(iLs^$5B(r<#yuUy@lVa8*A2g&6VDvpE8?ieN_VzuwBLT>4u3g5?jgdTXv_Ky#${S z?!euD>G_@x<+zCF@ENrm!+(ht9GGux`I`*7ws4S!pWGNu~ePC1Kx-*z`~Orj%f zWaz_`YHyE&H?BhtBU9ZU9hjWF^L^2T7~uyYDHEVI^ww|$&BELTGw4K~ftzvh!9Wxu z1AVTjc_3VKs#wAfh4Vr4Tk5Tdm3HxR-_oBlf&L-RDi0 zP=$Z1$KGM+VPzLxRZ3X#p2$ zuxjcKFXUj>o498&xaR56Y2ukl;7VV?R|cC}wN7>4Iy6ORB=w=L5qbj|;eVxdtqWg# z0jhqxK}-Q(vE*Kuv|4@cxMa^n4AF`F%83TXaMnNIG-6WgQB9PviSZBqkb4*u*n`F5 z@5a~u#xK<I?MIme%|7E7!S%I`&kUwZSLF-~I#hi{LKM!tywiYZr9A zN_ONw-Vo2*Ah1Si|JD_Sh1qp9WwoiAb<31!k4#tWkgKi)o!0^Wx%Ya+2gZ_rIi)pp z4QIf?zQw*8GmXj{o@^Sl_>7b_Lv_9&rA_gRXqZoQ zYDA2`tnmq}`oHbec|S;p;QDUB#a6`ZTay!l(Ozr_>>e44y%|tNrK1{Mb+XNrg^Hw@ z+Gu)y%u2MV8JzF|towR2wtvPNkVPGoTen|z9yFSYM!r-?Q$kh@2krlW4o&40*Kk7ChS{UpEyo53PT7s^pl30Lq2;Vf3|8*zx-*-3=PQ784WB25yG zM&}nLP9x(ja_0w%z&A|1)(Y&hn1&iq+tFl1>}6tk%Chr11ph)yI|@VjV^Htj;4 z10nj1FzD0^8e2MT+@$%fU&sw2tF8?E5sa?st5u=Qwp?WsX8MP3M5R+ z|HINXuvgmk+}gH{X*;!TTT^>#+qP|M+No{ZwrzLXQ};X1dwqYP-RIs(_R31K($Qy3 zgNsF=4$eLT{Z9PK`MuD3;s1Em{0qr#fJ7ANE09dANt73H`kZ4%yoh9_l$!kxpF7hn zm9q_!TO6L#Rd5)&{XLJ@hGExG{6wAfhlOwkQNvsb!Z^Y6c}$z z0Y9*;_+la?;!LDy*CA%aC*B2uxqg|syO&uy&&h+8t7pPpqy9wZxuru4Up4Jc-|K#u z?GuwiO6Z8?=C}0RdP^C4E>+3jE{`&b)S42b<%ugSCr4ZFb$bN#AGDAByCnYS{A2_q z7;S{DwD}-ZVnURT`_fH^anYj=6ts}tX@dIq5(l<)BuQH6?Gi}>t47!ferEqmxBJWk zP^L&P(MSl(NIT^zcVnh;EoHtNsJrgeim{~_l0~UC92_85-s#HzD!BH9*q)PBz#^7^ z&BfX=sEdu=bdROItIRw^mNlH`{(kx`!lLiFH%L?j{GFG zQ2SIZe@X1G)Ph;%5qm1pfl~kzq$N`p!BG<|ibH$6w&r{Qr8$!lB?ciVK&%wAnB;{` z)~g+M#Z}zR;WjHd%{v+FAjA;IoT4qWF?lx`+G9=RYeJUV?^XXObI=4qYHjenkUG1m-dN3!Fo*Wg3j3N z)V^bER7o~twgT^52d_x~qaubr(^Jr#|Ewx7Q+T4$`A2Xyei5z6CL}0-S_;-CydKo; z-yelo5`iCj(^+1)0x#5^bM0xqCcVCOH9xRC!BkJUruj}ta#wTma3(Vyc?1WBGv!9h z_6dZ`>+0c{ZAGyl| zmMG9Gti%$!MV|jUM0?g@J0dPkI8m;CM3?9)Dpeq8K$KuYlcE=f4bfcAHplyWM7p?y z)fWFd$=QEaxk&QDly4K8(yr(zmc$Mwbh?7Va&K~uc26jA4OuSlz$iY4@c&*Ob_HvvHGPx3tUAN1!ZH)yHijby|$#s4`5XY*{>G)Ck>)fahTzfu3 zoJX2<8Zc;rd&cANeEczNaL@rQvg^VIyp4;Wt}~X2oX8%FK$_g5~ zrj{*N-&mr!O&I7+^evSzeBt4AlHNlo@a}wC`)SEzuO}cBD(6jyztmi2Eth=-_ks;c zKks>^YyIdZX}VA@a>$9DoVS>RF*hv_w?-L<^6;6?at&B)J8al8Sur*NP)*F z?Lj~JuCB4Qp9FZfgqX2Dsh+*k<3VizRjMAZ@od2xuhN)Bg)RT3sjVE)T|)l0?vq+& zo{+8?-gtqth5aujD3^f`38>aSn1xBwvNCgl!9T2~vYp zI#w+?=NBt!snEX!a4#^F`XkK=sG0IUbWp8QPT7+kK2v02A4&odW-4(fK6k|Nq}^Q7 zbA?U~BquuIL0hYFWJlun0&8rgDyIBnu3$OG=~YG}X+rD}x4@>x+eP4^pn>C;SS7%s zA%|dj7QEbpXzuFJO*FyhPQ-8jZ>FDY+K4skQ@L_^=QzIFs29?C=dhWEzh7n37DxyZ z*pa6xy^(__x)qSX+@0DZvJ`!oobaZGSo8q)mg*kqKwRp&Z`Ka)Bj;o`DiK81qf|Z_hpQ9R0kEO zr$BY<%OofRlYiac$l1{(McsSl?a5LZDYT%dx*-MRd^A|3Aq?!dzggnO84h5#U@}BE7o4K?76a`I~sR>pL^Ml9+`hJcV3t-tW4q zC&{W*9eD<3WR6Vg1OSC)sxwBx@cWnFo0^n1T>$>A;2R{5R+oE9vg9kshya;c^R-m) zd?5P{d<{`M&63C{Trs9g`z<9TRrPe5kydk0B`CVEd)U?lRC z!SK&c>0R1rBL3USWt8X#LGM?A&5JZunHO?k_22c*Cdsz@&N@4NmrR>z6_(ZgBTPAJ zlT#IoVnWd(fBxJx2y92KgM3UTuMzOPj*5u3a`Tp_Me zC6x^*XieP>mh9P4ZnL01=wp$@gcfDF#!}*d#G?M1$?}g!vi)iIpyfSX^$kqF?%<4*H$LnN45wW*cQuI+ zD-joR&w+q)?hax&Whv1WYF)$~+ELawBmS`;C{U$6dlXwbXPbew=^*xbpWcxR4b6AV z7=>WY6uCxtP6(o8Me3G^X5IT6MxnXTz>l$xOY zHrco5N)Y_?@-@NxqMv@08**OoK*|a8_ad*WR11SbNge&52FC)(AJ;)5Dp=tQ%?*4l zDmYDQzj@YU6xY%GS$BR2yX^yTVB10Lm&mqPpS5VoHMI@tD{H8(1fhXvf|0KBWgH*J zf(v8HTB;YG4*u{bwgNvJ7wTrdupIx3&7g@bGi@nxpIQ^xd$_rOsvqf&D8{2m2~-uq zunQ$AFe+LcS8Z8qhTa4=)Y%9(+SH|NYtI+`x-2-Lhz~Z>ZP>_`VP|53>%dDMgnl8M z^#iJdg`0TQbHsNU+Ny3y?geP1=C-JJ+Gw;BLxDa}Fthf2J zlD{FuvvwWG@`HeOjaVwH`#q(TeV=|0T3=lo?0;=bZBpcUSq-c^PX4RgH+WxUyc?)~ z&!8WxkQIj!CvAydKL-wtb3`v9p9H?4n509lfyqX;ST?png=Lco^k*Ko81{^I^Ll^0 z)zZHlq^ECj6FCD)j8T!>{8^4Q{EuT8zgLp>g2sI1%wNo?_6VaRY!vb@htdQRG&shs z1jH?@tK~g@7!%^(G-?`smi82smqELS-_plbLgct?RrpdYyU43=#rIn76IC#0| zH{uRWNVyuHz&{hiGqLP+Z9DLB8fTdNl-HMmC%(bO-W@vXasA`*8x!v`3ovBeulGHai)qME#Q{(0a&Jypd_?PXZW)Nr}nQvxB{(G4Wp%hGl+^RLl^PBw-yBG7e@mzAU;QdRl{KPVc^B(fb4kc{O#EQg|Zz((mkhubLJVi+ut6N^*{ka^i{ zh5L)dxXSF9Bt&s9njgOVcnIxtdE9Do3MIdnYCp}@;XgwBm5JZ=Z-6FCK{=aLEWxq| zAFVIlgLm*y0p<69z8fhA`fCWFn-f5G(4P5+Is}V#e zz@&xi7}%{NICA@a4qilT^xLxDZmQQC*^uio?SPoB!&g6o7wJR2NPWvFi>((*1@k&-GosVv6AECN}U7?7>qMi_MAw`rPbuyKQDQ zANFkn_zw1kuHR?Ma$NG1$6NcD;Ju{afQiQbsK7e~akp#kX}(|9eaS)mM zMpWr(A6L4wEQtMmTi?M)uwMu8z92z4mGh2p4hyDR3c#|ogT#}d#T46}9$kCxR3n*s zrhTy5Jx#`0X9?~W%5KOHz58&R-uH^P6uK^-T1Qa5IJZE`F6s94R<}hq`ei@;mwE*A zqm-62X>deHN~IEp*4M#IWeS7`W6t*@GE&C-ArMtwV&ht)UclxC3iD1}Iwg7uo#d9TD zrgR@OwY&RtjQMDv3>5=DEkYD^76|cyqqQwvcMGGLH}p!kS^mGD4e}rGf7AUwyBVAC z$V7tv2%v9MzFneZrpb%Y61Ao;QoT@)Z5YDu8}bAc^gi--SII!hxdr?_OPpu67omlFam1LRwfk#Qn zYbcoK(GI_MAJLXr%f4Y*`fYMn`;ls*qKdQzBjARcVFKC=X#${!bh1V=uJlYBIESaW zc)f#zyXn-}x43!sOQhS>Bf{liSZHLVR_Gw3OXyc+?k}RHlQ1bgJbbMq7o55mFiEw9 zrcgIUEnroSvmjN6Dnt zXmEs4RkHzBln9$7OwT zd5HNI@r-wbhHUtQV^01}uTI`8YL-?>3nL&l>qzDy2j{9M?HwPEN`fpYfP*H?n8=Zp zDw%mKY12L4A{(#znVUNsJ)pbWS1rBdx8?*7m|G1>qyR~q8HgB1jgdIwSt}j}?Nh#e z9js~QdlMDh$CU|)=myJ_|ea^7~k*F0y>d+&_sL`R!`SiGox!v8UG>|_T z8X}}P$5f{fr3%U+@K+o$p@xWY>tJ|A3y%SIvA0~b*{n_2xExWOC=GMrr%_WmRzHiX z#neGOC3+Bt!*uAcF($Br@f}W@9Gwcs=5k#c6{A zi+PVMBeg<^Ge!)-xn!MsD5`N0Jgy9>p+q5Yd_2O@T4;*2Zvc!<<(9Pkhv;{iJ4DPt zL&|{B*gtD$QBk3x@?fS4GzXbZ8=P?>{o{fv=;0tz8hkA=B}=8==uo8#bZxMi{^jia z_buYjbjz<$!AD_1V`5q$vjwFxHE-M)v67%}TbWc%OF3|o@XRHEH~%MXiOaqx3;g$Z zni~1bH6w5DN4B{SfPQ%R?`RNu27PQsQ-ZorY-O+0<>W=drNB}@cH*@EVQGByNk!H!5+f(`2tq#4#PQpPi1J?)L_bYX+RW%lusLo zcMyMK!H-0X+oA9*>+6c}D9cMjWFEqq%kDpSXAS>k68u{}5TV2!9r}Q8&@syRksG!N zuzG&ZIn58hFF&bXHJ>_3SYSFh7FYTSA1^b$U0D<4Y$g#!nXt+~NIL7&pX0@2#zMqg zS*g;X^M>i6x3VkVqSIu4Cp;!UOPB*k+Q5lAbRis|7)4zN5l=8n=n5ULS@?1ghGqn8 zWXoIO!K}O}AfYkWnHDi(J7F_hlz0Y6UM)lC5amBiYmkE;?rcYyImVXdq0K?k;6@K8 zeM(P0Rle(Xvmd|4)2=?pIoU&4moL|;pq>fYM3I~^px6D^Um%859@!afksfwQL@}ua z;OZZY5r_9buH7+O9*Xv3R(p$QgAq2-Dx(==GC0?T;IZ9A$QKaaFYodk zvT^X;Pa)P^WZ3%xB6eT3Ws9^SI|+1E6_uu0O=(%QSqkP6ivk-e%y|`Y@6@&DLP;=* zxw$BE7#wANL4Mlvls{5?quC8cbtX~%aE0`oJ6$k*49P~HzE|Uc^d{5JUbND)6#*hZ zbug7TSUX1enRIm*2w=<~{H%-_2w7jxIZQs>TEQp(lcLik`l zCk6zpg1UHxl4)zJ%2KCJCqtIr(#vFjlVUNVx#WFX$$hOp6 zION2J30--CUA2K&|L236iwSbl<1mF{!^&3CZQ?NmcS$McWoN&jey>^-%g$HR{^OC# ztk*a+O2*|XnLYt+P82Y(DdN>1@Bg zb9F~bU*W9|f|8O7Nl;BA3skUImciB7*!@aDyawYdd zu7Z7gn!kU1Ksa5q77aov5YCjNHs#NQ4_;Xh&%U0VWqsoDx;=4ZWJBv`JwbItD@18PJZf`k44cKY|5Z|_}4N(ZmffhOEMK+k`p z;as_3bx^9>?HN%%)>nXErGZvjBmLxGcni|TSdt#M$o}wkPWX{AjXEt>7=TW3)$V;< zW`C<2@E@cFXO_^#WTj&zCkgybPb+R*!FQxLQR&_H7!WwEW#&wE{iU}&{qBbCfA@9g z{do#=c%RaNciOjQ=m3Zt7y+z6(@;9uBNN@(;6%||e@{PsAFuYl>53^KmcK!+=Cn4- zVNZ1Y(VeEKm!0IAQ$)uK(Z+i%KP^!s| zyzj}JQj(uS=hrHfFENHJgGj7EeYXhvBs${wje0qu=EVEv2$FcjIG$ zAICs|4_^5WCb!6ESk_=Fk;4av?exl+!9~0>z%%_vxRH!d_UCANoZoT$#(J z3GV;7^#pm=70~Y(<`}e(w>ve9o76)v?rSTU`!IiU&$%4)U|CfzOecE!c^|%eixH-q zlP6ptq7EB`ssmcfj7>s@m8gwEk0{Tp?c~LYX4C5pED7M>d4z?g{lZo&%7{VLL$e8t zHg1YojllV<&L>P5B138QHzR5p~s|vN|krKVAa0?A)8=k z4iffXaFZ$r+7JZlbZH6H!f);BnEotdm=Fu`u+F|!sE*{#!(4+-K;xeF(3W@H#B0Y> zA#V?DRi+%3ntJno4f5A^c|CF7DMuAF^yk8@Yj3{JJK#RyQ8sbMHQev-f%WJO3+Xn$ z&awysAx>BRocF|#6RI>8Y`^k|P;|sW9&V<*a|?(H!`VjRe}SxEqj%uwZlkOTPj7sf zOH0em?_}zRA)<-z>0by>j71Xqq#U9`Orly7M0X_$tM8R&-&|M#aC$n&1?NN5BT43O*!-t7DMea|$`cV~SXf>RsWXE7cQ zlh%?6mVMjJ1Az%dr4U_E%gDw>NSb8={Y*y_dk6vXAP299XZ)B<)^+{|(W{#gC_d+7 z>n?aMnrP3X<=d@6GsJfd$%tPCGBFbP-BrAll1s^^xg9L5h|rJX=RJM}?%-A?oztr~ z3hL@|RLKiSRmCf5hvP!h=6AX5Xo}dfO-8>3zg@h17fPRPmj4wyJ90FFfL(6dv@h*% z4pIu2<@|Ou)qUVjF7ufNPyj4-owFy{g=9Q$>tPADqjPe)rgt!f2O&T&y1V0(vcQ*s zy0Wa#MnDZ^o4(BmPE&dgQHY2BXR1e$RfHo!g6hpaiD z3K{QLp+Tc9>$U6sm5G860S(^`O+PGoqhJ!s!;d1N#jeD-iUzLkQYE8_9ziAMO2$qt zd-`lSXjHA{I&8#mhJiP8!@$j*mwcu9v-F&>*U!V-4JgvK(+~aG=KBK}Pa&^AVhp|& zzsUU#6u%h-yicurKd29L(QkTwHchu?3M{KkpL%4EQ$H4`jl|C1!wP;(Wn6O7A*CZ) zA~9akK*!@+tmZTlkT~l&JJ={8P+;8fvJ`a5(|XkUD`zN=~0!27sKDEUXF`xi`yxcZjC2($U}XtJ~5 z_pRud!+VOj5)>I(&~GfWiLgn&R?E$791{Z;tC;wh*mwiyylw0aZ}_ZFvKqq%l~xXFW#vG>9Te#HirCkemvhS zI!nYw?E9s4QKsPeu!zXwA3VGbgOmt~SH>3IS4Ef@(8pW|JJeD{X)IptMqhUt?AlW$ zB~sL$FqWfV)X7^b;L3^fK|`sAiKwI=IBy{blNF8TBBN(6p3l+ZF*YG|Z8PK40}@|K zTR%C79OSb7jSxl=W)oj^hT-NIwoLO^iqWhg>I>V-MM-5sq-4Q5mz$vhO#ti7J1N@z zfrxfqH&N>VUAb==7VmGcDWMQC`rizN*x+DFDGIre&q2|fZqMM=Uc@p<_xA6Vj7!F^ zK}o?U>aX|jhxtwW?_uIEaQ~}dzbgR@prm4-!MnA8?C0la{1@l(v^d zl;|!rP}=~+?@7Zy75ICx{r0iHp3TjvS?&10A!~N|ezPc_OegJr95B1zZ0-O1m(pS@ zO_PemrT*%;H5&YMS@C9waZt z_}rk6E$TI4hwW()>0;yOJT1G{QoBQfQCx3DmQf6z_JsLh9t=aRl~~S+ljLAb#Y$S< z5|M)%#FQDs%@+bZDs$$}0PP@=!NbLPQ0@0q?`Lu`P+R8f(Elp_vse5m(H^Uf6{L@@ zwrgN#hsbu5WqtQ>?+ua2`$GEpd1T6rHSJtJ&Ma{Y-8r-Ugl@$qUI1DU=Jyk^J?b#C zX7;^?PXHonLhNmxec|$_J}pXX3ZN$L(z@(=|DeB@`!0O7-PZc8*{^KWA9SlT0j3gI zd}b_kC!DE|sCEzLv0!k=iXeoR#>6FN>VD%=6nIy*+i4Syp||h-q`F_eb@F;giXJ|2 zG+b)Ao&oWhqrNlhGp-ksg)~&>MybcBXPY2&=O+`pgX)63g$vch54g!J%gYz-zVldD z()|9ME?n?j%*O4nd;PG#(I9bUhtjElhn!RUJ$;@ zUw=ncPJ&Q*)qqDN;-mPB$Z~pFZ!3ZFv3o0%y-eC`k7USdCOYiBrYpfAf7(LHdD4Q~GB-rx9 z27k{*n2UybGgF>5eQ!JKmYub%R+)36A9RUF5kVE;?tL9J>MQf;Yj@cMvQIlckJGuJ zzzs@X1cZeATRd%sYbO`pft{+W>FSDZJ#>bHJSTsm-2V|?qH+z)GJLZpNh235)(M0sUKJt&ot%%<&xeb~> zowVu&BZ?luA%L4YoM9hH#@aoJdlkcI+q_?P+#iGEfqwv`?fonNv(f)~vG;Sc7x4}S z`Q%Nz?Gb~h35(dTha05Bx14tIJOxAJy1DiGvgosB(y=!GQgka^R?uIGV4o#~Rw-WM zG$mzsoaMB;4N3i)6{N))y@eIz*a((1geE6HkU>WWnRsV9u*HI?{d2*T5t0oZev z)f%YJTcJ{Llvu(X7@n>sT-j?;ib6BLt$Lp(m-)#EZk#`Tx@pSYr;O`GHtEz}oSYHfMn_RtM@ zaDeDCHXMdmy%`u{q02c)`fH-8I1!!@5^RH-s>JC%EG>a382SSq2@r`kB?yD`~k%-eV|-n&}u`7$_kmr zkT3`7u-0YQYd?G2#kcw5hhMk;J}=An+yDA}?|sfAJcGuF?*jGsdOgLBA5}Ey_If(M zZg=uOYrZh;Z?|1DgJF<0vaNV$z~5hjR28O1PlD5f712PmNm?;qRry|6^xRg)|0zrX z|8?Y>Vk_q@y*Q=sqK&R{c*Qxw#okt>;=7GH!}{=h5M``=wg-UI7kt}Z@hf@!=)R`( ztvCi~x+N*VU~|wTFkKC%t~dVXDY4z@smTb34nE2RT`Cvtf_d|YMga? zdIR0{y_P=#DAcv;@X;%`V6X7S>~H0~GM?z`etntx$M5%;#Qdk|@8tAwLzJ~E{-1H$ zaEPjFF{GbL3E>7mq&F83>X^6f2fI0f17#q*TjSLg`yMxKpnlkQy*Or#=2LV(QO)u@ zref#&W8K3t<24S$)^7EVa`wFAE#8w#w$5fuXZODzU*e;=^2jrR4k0KGE*PG=EA+-e zL@?;~dPT%Zc&N_E`Y_?FLc3ubUcY@;ZzNP3ymJh8&{sOme{Md%Qen2)>L@5Ga`ON= zCgO5pCcX!#J>WG)z{oFFA$~1`q*Q}H^B0cV`Is;E@mci#t;ir9`VrE7+0`T0^?bdm zM5UD=s$CZqOxGGgEqh`Nds=tjZFYQmAtoIksJzkBJ9|DOZ-_#o8LPHtxywN4Mzp?$Pj zn0eu-%$;zE9xhJLkzKy)5X(vEPPIQ?YutMVFQ}A({NbWklYqlyiyKP1h421n=gzX} z3M-?ec$v_QI1zGPs@6(8=2ALLSMzO}H#^sfhZpQ_tvUP1@R0*(w*0->atwS$I-LxG z5!oGlv!%Ol*vCJva~$GdPoU&Xo?r|sXT;jQljPB3K=n%_uNRrQ^ z{MbHl-KW$*@7vq+3nBrJ&OerJK_piQLI#jHCS6L<1*)apHhSL*j(%wq#`$#Hfzh~; zzO97Q%HH%fo^VkLbSk<9uJVgG8$|awF%rR(a2q#ZPI1?`)R^Hvtyf@hr-OO%l&t*b(g zEQXbg#!W`ev53AhD73Q5^K_bu?`GRqWfa={sqA<$2nH=fok^H}xS?jeSB` zZ9Ss)el%~&QG3T+W*H{d-=I$4-^+`EcSKllP543w8zu@wM5yvSy95y-yW2PYQ=U3s z2HbodzksEuIk{)ZQ7N`}c0H}=#JdRhk=ui8v`~&{+Cvrd9PxnYI8*K-gEQ%33pT+- zqzV<9;o4z~i^Qsh(OnV7toL&4d+gj*^P-BBSX%$Ybp$iu-zk2Zg4d zVCMA)6w|;-?RLcobQ_ec^!crw}sydrjSRAA@CLS%x3DjVcx$l$8 z&l0`sKs~SN^zCrK0E|nhPP_Hn^Py|Uf9f2-7!O?Y@)Ik1;o5w&&%1Y}ViQWt=)%B( zXq)5HFmX#l7Mx;|TU0fnL!=0OguU2OUYFyF6xV z0+U)39Wgn$1U3LIvFkTmD8&R0wbn*~?5?w%yw|EUS%MI`vdjTL{>@gqC`@3BZJ@19 zPbxK&E?gHj&WvBKwU?u4;uBal-^;_;W%2HTh2AkkFi}P`Q?{Jv`MAl~*(1DNEWY3U za$l`4@7?*S#MEu~^kpOWvd4hS1yH_4G>LT~EK{M4)1FH}&zNUKJwX|=*pveYd(Lgw zHsrfo|CgZ-M!f;pd~!-!4M(lV^}wq!{V z*Fe_+!|LlWWB++~z^d!Y5wS+hv$n!^9ow1RlELKLt8N$y7|nVo2M%&{^Z~88xq06V z2rym^9FX)|z| zSqyQ=WN;o}zv}k_Sidj02J@fZGgS^YnyrZ;k^D}~{Z108!1)%S_Fi5x=rn5X%)hVl zyW67_dcQ+bNqGSN=$n7*>8za0Bc{l(SoKzoCqIrbrW~aS;;jA{<#XD_zcTtcj^mt` zXD)DzOxAMM#!IyIulpbm^;suDU=>rsmSP=V>=U)d$JhNX5vRz_IiP_LNkQ!`8}Hh(d-~#m3RhV56grx zs!0u!yL%$icFNb4`fGO@4UHSAE{4s%cPcWT8`EK(Tw2yPa&JHyhJvg=i$%>O5inuN z-^i-M)D+9z5I4{;t^Ccj_b;D!*A90vRdrHg!(RR3Lq9qQX|k>LoYf_t5ewd|ah+Td z+OGt$@4s;7`DkW%>%62*ZGDO>BVm5W^e6!oTI|@dm{@=x1x9*;p5WnM4Q&U?b8;?SI)E0kj z-W5~~$meX1ppmibA@-Jru*>T1o%xA6drYV*=0Cg({wki{^f|pE{}{NPvgEtVq)r{B zLagn&hm&votv1+f9p3fI(N({8I7Js1f0K|!rGe8CQ1(;PsuaC}J2!4WjB)P!MBK+= ztmZ1FZ^im8ua2Q?<=WpFmL-3--T8DT4*Cwb`xrX12Isdxe!lgudg<2gDU>dJ@_Kpb zTNZMMM6^Utie`CzKst|~JgR%yazK4Jn#aDxWc4S%{%fl09P^1nqo&hg-Tt~F*Soh# zmZvdamh<}x=GW$~E|YaF>Sh2ZYO?|}mvIuziIh!4P6nL_PE7lCQCT*?TJ!6}?)F$* zH%cy)QX1lXh2kbv?LAc2IJ8e?VWj!UH=;WJmpfeTHTal!ci&`pj37<{& zcl*>J$&db8luz-Vm+5yt*UuCyr_=>hkI)ZJ|I6xDudmVKD9H^FpMCgVY@xJC)3z~i zP00Eq-)Kk##HqonUO`FsAowO&X($SjMU^tKMw>P-+x*HL$M5E$-JV>jfXx_}G#IYY zGFb)wyN61J1m>(iLgK;6CB`M@x`GH@ZvL=;mXeumH%ZdUN7%g=na!_rYy%g<6E7|x zM8ax!|AMy3NjkLc?)%c|Wg^>ecerj(?fA`p7YGV@0F8KO2efZ_l-U0RT~DX(v5hbH zpMpbX`Eh6FE3N8MN+N8}RM6Kb;08@ZXj?0f!NYl-1>U_MTWVv}Gz2A3O-vAT5z0~2 z77mTs)-mVLPx3D5y^-~K*uS>8@m79Ek^-~%yB?X1$*MI5nLU^94+WI7ox>cq1N)yu z@uQpo03e^GF}=48{4?M%kptxMHE7*Nc{t-)NCtHLjU(G-55S_`eeT$G_{11Rx1>Rb zl;;z~0n&`Bcsg~nMa_U`3|^#v*3X(CEC>zC6>W?#dF(|0@?L%b{NF$^LXPZ4Ty~-Zqb1*6 zny{POmP0#y3hTTeUUgn`&r@|IyX}LKX8fk+B8V&qQu^}Z`Cl~gPo$J?@;znn8Hu~P zhYuKu$Q$~+brrPwuhj-8|9135I!lORVPf8Vbtyc1JadtqUUzGgE|KIH9d38Jb3tDU z6!(IMa(Uf>s-V6`V=dYN@SYU#5+su6xT+M^uLECQPVS&NcBdR%)t$e}k%ARI`3_*xwY*itfRMw&!*2bkhTIwyX)|Ts;c&+R9(JBl9~K@nm{! z^kiYr==QoZmeXZg7^paeI&!_)`uDQq7M5wKLVD!lX;4bD7hQLV7sqeK$ax&NO0*o; zmC!GU1n*8p39xW$ugRv?)9Q!uaF^EnyeDdsxO+?XG-#i?`-a`)`?tYOaCUGT*2o*~ z-n+dQS%r#&Rhniw-pd~3bI1KYIQrg0PF>xbdF0*=Z0_Cts_V^0SYgeU4iZ)iU9gze~Eqx`{QF!Y)Duw1&);$DwNeX1+D-RX_F->&7R+e5iGNPH70kl28n zYq6beh6;bu8nS6zdh&im>Cg|^B$|=e?K<*EFdX+Xnn`e3{PfoU+HzF1zCAA34^H`R z-y>)>u~0LK0Vi+$++q~iIX zNT=4_Sy5y6JSa63%Xj=ULeJY*WM*; zLK^qEAh+xCy)F`S(|#Ky;z)Dy&q8BFg35*~^Z_a-h@_-s1kB@W4v-GHzh@?eFpeQ0 zAV4uW0%m$SIXO)EvzD9Bfx^{*AexuDcO|w)(9YY`-IyFGF`??FYg%N#A9*qJ2V`1D zhomkaE9vkp_g;+{FUH(Y;ng3VXGFTsUw4m-i~lJz+8W2iNRR{5=yunFMN;1$GhMzh3nSOBIpyef&1nl3fn9dpVzvI!z7Smb15a z?Or$uXqmv-*Uews^WWPQKR;%_KgOn*ST|a(t%uSl zDdiXETCTr)yIId|562E(YAZDCiYTI5OxAJnK^qJ~ke%*q1w?9IWnS^PkJ!>*8E_2~ z2DvUZ<9@SdbEXt zFv8r0RK4rABCRhsgDAn2CgA1hkDcA70*@{^W6 z;1`Tb>5r03_NBqdm8CKnf^ut6kz@CA1fir1xJiGX{Uv!;%4y?Ttu%IPff7p@WsGGO zt7cGsK0Y5b@kG{Kg;ox%wq_s$URm2iHv0%(-E718f1O=+UFq5%mH(&J76v{F^G}cFkJY$=fe31|{mN5|t$sR*YlNs~oo$s&j zeE)st+;i?;&3` zJIkwjAeEc^;cJc6H9ZMc+4PuVTd+-lFpHjQwYCZJz=2>TAl&6s?UvN!9=Xx3u4{oM zDfYmXvs0lYCldwWi706}*`c6IfRY3qWuNY~w+Etu8FYGKhxz;_+i%P_-79tF`vdO> zTj0rDD?3ebKyRt|vj{7L1YXzCHJJw;a?>g<{YNW|weCveshr2Nmi7|)QoLFgVBB~O z_oTqxhq_`>%e?QQguON6Q7w~<5h0J`r%p7-NeBm~(6LIpqA0!0BL02jT7TOY-3_Ym zQjVq^a6vb9R+O|_HuCIx>%c%ho$cQ@-QXnn5^Yv>8hzt+$e(Q4s0jQ(W=|eJT|;vV zKg{oamj=&OqBSCwH-9b#<;^UXoi5%S2tOO~qtD00GOo)vh;Su1XX4o~Us|1J@^(jD zOq1dA*F5zVkE`M+ukW47)V1H3JJqqa-OBY;D{&N_S0>vYSs7I8Na3oFZaO)&w48Y7 zu9mzjzEwH2k0nsZ94-TZ=JJk|dV%aRhM;B-QTsD<@G_| z84}a7^b(6Vbfm0cH(Jpi@oFyv;b;W_+UA<%4D$&(_})?)eT{R#6lSK3_Rrz}s*&q+ z3Q#du6(pD)?gcGzA^rZCuV^-zuZzWhQ=u>MjW{D$agPu`^pa4ae%kg<$5gCrJQ zYTZ=FZw%+Ax-CY!5%vh3#|9K~CUn@#8*4JrnD~-HEITi)$O-dGo^r8oUXst2dwj2( z9ir~i1YC|4y`_lRD7%L*rkgtuKXi?-!?2FT;=TSEmg$Yb1O%~-P`s$m#r8Lb-bNLd zl+5&7@R1D^DPKfkLrY8VXq6La(p6-1$s>eGX#rUP8SOzQ1qbsHF4=42wAMb}!i2I$sAt{rr7N3H1k(STb*B@ElKS_1&7Y9pRos>- z${BHHCq;+Xqs}hzdB~R(sZzzA))}E*6SO`{E8GjIvm1@nkqExx0sWD1%o@tZG^Le6 z+|8`=K=74JlyPndB0H2cZhrW4LC9pWnp;}vQ@@S}u6`r?<^PQ&-n>eBS> zBDqpIQr}epBU@wTlSA5=YyjEW=6()T`1E6dxE7ycg?5g>Zt3ku;J3uS_MT>-t(`r- zGsN5|-A?-D(JLL9_R3>glIQ9)!Ym=^|`Hp9J$Lt*&G_mp^al@Fy z2rXVbbMrJ3$nbQ1x*&{%`%ztL%2S+i9b67EmQ78D$*>cW?4(7AJD#;KjDC+cNYa06d=deLnH4)4so@EBy{h>dd5pVX|$5}>pt8g<3pX&(U(B#seL$G-mhi5Gt!syITK zbYgCx7aHdyrzvsyAwS$%HAZ5A-)6=ons5*_7!wya`QqTq?9e(FM7H@Ck+s9;_rfdj$iYfBJH*i@6*~7XGb$1rIj{kIm zq`G}qVSXe`ooLiwOFCZbN+f<5bN2G6Ty1ag*)w_HKx3_9A|hVLn4ge2-+0Oord%D? zLS5qQ1<+;$8^UQ7aD~E5`%L0qUBO`BwX2ZtyKed|??a8!ZcB^_yK!!9Ei@8~MVMF$ zPtluHY6GV?IG_~i+&*ht^qAID|EtYfqDH#sa!n+Rjll}uFxP``Tn1@zZZ}EtQb;I# z%z~KsV{`V8UV=uFRK{wf{2-?O8aIa7P`6teIMW=5oeuJ*)Fl$QNlydVoS7s+y~#u5 z{JmQQ%PdVq60!ZziTIDpk(&u6i8T7RZwB-1g0Pkl_0;9a$*uI=dP_?V~_{;#?-BKIf7r`(vrQZ|G7wvPU3i$Uo?bF_ww7nkph|HDdV_S}5= z|GTHx7LuBp#j{lH<#t3wM4*v2S0epyL(ptr); const std::uint8_t *src_end = src + size; while (src < src_end) { - pwx_get_pixel_span(src, src_end, pixel, span_len); + anycubicsla_get_pixel_span(src, src_end, pixel, span_len); src += span_len; // fully transparent of fully opaque pixel if (pixel == 0 || pixel == 0xF0) { @@ -78,27 +80,27 @@ struct PWXRasterEncoder } } - return sla::EncodedRaster(std::move(dst), "pwx"); + return sla::EncodedRaster(std::move(dst), "pwimg"); } }; using ConfMap = std::map; -typedef struct pwmx_format_intro +typedef struct anycubicsla_format_intro { char tag[12]; - std::uint32_t version; // value 1 - std::uint32_t area_num; // unknown - usually 4 + std::uint32_t version; // value 1 (also known as 515, 516 and 517) + std::uint32_t area_num; // Number of tables - usually 4 std::uint32_t header_data_offset; - std::float_t intro24; // unknown - usually 0 + std::uint32_t software_data_offset; // unused in version 1 std::uint32_t preview_data_offset; - std::float_t intro32; // unknown + std::uint32_t layer_color_offset; // unused in version 1 std::uint32_t layer_data_offset; - std::float_t intro40; // unknown + std::uint32_t extra_data_offset; // unused here (only used in version 516) std::uint32_t image_data_offset; -} pwmx_format_intro; +} anycubicsla_format_intro; -typedef struct pwmx_format_header +typedef struct anycubicsla_format_header { char tag[12]; std::uint32_t payload_size; @@ -121,11 +123,11 @@ typedef struct pwmx_format_header std::uint32_t per_layer_override; // ? unknown meaning ? std::uint32_t print_time_s; std::uint32_t transition_layer_count; - std::uint32_t unknown; // ? usually 0 ? + std::uint32_t transition_layer_type; // usually 0 -} pwmx_format_header; +} anycubicsla_format_header; -typedef struct pwmx_format_preview +typedef struct anycubicsla_format_preview { char tag[12]; std::uint32_t payload_size; @@ -134,16 +136,16 @@ typedef struct pwmx_format_preview std::uint32_t preview_h; // raw image data in BGR565 format std::uint8_t pixels[PREV_W * PREV_H * 2]; -} pwmx_format_preview; +} anycubicsla_format_preview; -typedef struct pwmx_format_layers_header +typedef struct anycubicsla_format_layers_header { char tag[12]; std::uint32_t payload_size; std::uint32_t layer_count; -} pwmx_format_layers_header; +} anycubicsla_format_layers_header; -typedef struct pwmx_format_layer +typedef struct anycubicsla_format_layer { std::uint32_t image_offset; std::uint32_t image_size; @@ -153,20 +155,20 @@ typedef struct pwmx_format_layer std::float_t layer_height_mm; std::float_t layer44; // unkown - usually 0 std::float_t layer48; // unkown - usually 0 -} pwmx_format_layer; +} anycubicsla_format_layer; -typedef struct pwmx_format_misc +typedef struct anycubicsla_format_misc { std::float_t bottom_layer_height_mm; std::float_t bottom_lift_distance_mm; std::float_t bottom_lift_speed_mms; -} pwmx_format_misc; +} anycubicsla_format_misc; -class PwmxFormatConfigDef : public ConfigDef +class AnycubicSLAFormatConfigDef : public ConfigDef { public: - PwmxFormatConfigDef() + AnycubicSLAFormatConfigDef() { add(CFG_LIFT_DISTANCE, coFloat); add(CFG_LIFT_SPEED, coFloat); @@ -174,17 +176,18 @@ public: add(CFG_DELAY_BEFORE_EXPOSURE, coFloat); add(CFG_BOTTOM_LIFT_DISTANCE, coFloat); add(CFG_BOTTOM_LIFT_SPEED, coFloat); + add(CFG_ANTIALIASING, coInt); } }; -class PwmxFormatDynamicConfig : public DynamicConfig +class AnycubicSLAFormatDynamicConfig : public DynamicConfig { public: - PwmxFormatDynamicConfig(){}; + AnycubicSLAFormatDynamicConfig(){}; const ConfigDef *def() const override { return &config_def; } private: - PwmxFormatConfigDef config_def; + AnycubicSLAFormatConfigDef config_def; }; namespace { @@ -222,8 +225,8 @@ template void crop_value(T &val, T val_min, T val_max) } } -void fill_preview(pwmx_format_preview &p, - pwmx_format_misc &/*m*/, +void fill_preview(anycubicsla_format_preview &p, + anycubicsla_format_misc &/*m*/, const ThumbnailsList &thumbnails) { @@ -266,9 +269,8 @@ void fill_preview(pwmx_format_preview &p, } } - -void fill_header(pwmx_format_header &h, - pwmx_format_misc &m, +void fill_header(anycubicsla_format_header &h, + anycubicsla_format_misc &m, const SLAPrint &print, std::uint32_t layer_count) { @@ -282,7 +284,7 @@ void fill_header(pwmx_format_header &h, auto mat_opt = cfg.option("material_notes"); std::string mnotes = mat_opt? cfg.option("material_notes")->serialize() : ""; // create a config parser from the material notes - Slic3r::PwmxFormatDynamicConfig mat_cfg; + Slic3r::AnycubicSLAFormatDynamicConfig mat_cfg; SLAPrintStatistics stats = print.print_statistics(); // sanitize the string config @@ -314,6 +316,13 @@ void fill_header(pwmx_format_header &h, h.per_layer_override = 0; // TODO - expose these variables to the UI rather than using material notes + if (mat_cfg.has(CFG_ANTIALIASING)) { + h.antialiasing = get_cfg_value_i(mat_cfg, CFG_ANTIALIASING); + crop_value(h.antialiasing, (uint32_t) 0, (uint32_t) 1); + } else { + h.antialiasing = 1; + } + h.delay_before_exposure_s = get_cfg_value_f(mat_cfg, CFG_DELAY_BEFORE_EXPOSURE, 0.5f); crop_value(h.delay_before_exposure_s, 0.0f, 1000.0f); @@ -356,7 +365,7 @@ void fill_header(pwmx_format_header &h, } // namespace -std::unique_ptr PwmxArchive::create_raster() const +std::unique_ptr AnycubicSLAArchive::create_raster() const { sla::Resolution res; sla::PixelDim pxdim; @@ -389,13 +398,13 @@ std::unique_ptr PwmxArchive::create_raster() const return sla::create_raster_grayscale_aa(res, pxdim, gamma, tr); } -sla::RasterEncoder PwmxArchive::get_encoder() const +sla::RasterEncoder AnycubicSLAArchive::get_encoder() const { - return PWXRasterEncoder{}; + return AnycubicSLARasterEncoder{}; } // Endian safe write of little endian 32bit ints -static void pwmx_write_int32(std::ofstream &out, std::uint32_t val) +static void anycubicsla_write_int32(std::ofstream &out, std::uint32_t val) { const char i1 = (val & 0xFF); const char i2 = (val >> 8) & 0xFF; @@ -407,104 +416,106 @@ static void pwmx_write_int32(std::ofstream &out, std::uint32_t val) out.write((const char *) &i3, 1); out.write((const char *) &i4, 1); } -static void pwmx_write_float(std::ofstream &out, std::float_t val) +static void anycubicsla_write_float(std::ofstream &out, std::float_t val) { std::uint32_t *f = (std::uint32_t *) &val; - pwmx_write_int32(out, *f); + anycubicsla_write_int32(out, *f); } -static void pwmx_write_intro(std::ofstream &out, pwmx_format_intro &i) +static void anycubicsla_write_intro(std::ofstream &out, anycubicsla_format_intro &i) { out.write(TAG_INTRO, sizeof(i.tag)); - pwmx_write_int32(out, i.version); - pwmx_write_int32(out, i.area_num); - pwmx_write_int32(out, i.header_data_offset); - pwmx_write_int32(out, i.intro24); - pwmx_write_int32(out, i.preview_data_offset); - pwmx_write_int32(out, i.intro32); - pwmx_write_int32(out, i.layer_data_offset); - pwmx_write_int32(out, i.intro40); - pwmx_write_int32(out, i.image_data_offset); + anycubicsla_write_int32(out, i.version); + anycubicsla_write_int32(out, i.area_num); + anycubicsla_write_int32(out, i.header_data_offset); + anycubicsla_write_int32(out, i.software_data_offset); + anycubicsla_write_int32(out, i.preview_data_offset); + anycubicsla_write_int32(out, i.layer_color_offset); + anycubicsla_write_int32(out, i.layer_data_offset); + anycubicsla_write_int32(out, i.extra_data_offset); + anycubicsla_write_int32(out, i.image_data_offset); } -static void pwmx_write_header(std::ofstream &out, pwmx_format_header &h) +static void anycubicsla_write_header(std::ofstream &out, anycubicsla_format_header &h) { out.write(TAG_HEADER, sizeof(h.tag)); - pwmx_write_int32(out, h.payload_size); - pwmx_write_float(out, h.pixel_size_um); - pwmx_write_float(out, h.layer_height_mm); - pwmx_write_float(out, h.exposure_time_s); - pwmx_write_float(out, h.delay_before_exposure_s); - pwmx_write_float(out, h.bottom_exposure_time_s); - pwmx_write_float(out, h.bottom_layer_count); - pwmx_write_float(out, h.lift_distance_mm); - pwmx_write_float(out, h.lift_speed_mms); - pwmx_write_float(out, h.retract_speed_mms); - pwmx_write_float(out, h.volume_ml); - pwmx_write_int32(out, h.antialiasing); - pwmx_write_int32(out, h.res_x); - pwmx_write_int32(out, h.res_y); - pwmx_write_float(out, h.weight_g); - pwmx_write_float(out, h.price); - pwmx_write_int32(out, h.price_currency); - pwmx_write_int32(out, h.per_layer_override); - pwmx_write_int32(out, h.print_time_s); - pwmx_write_int32(out, h.transition_layer_count); - pwmx_write_int32(out, h.unknown); + anycubicsla_write_int32(out, h.payload_size); + anycubicsla_write_float(out, h.pixel_size_um); + anycubicsla_write_float(out, h.layer_height_mm); + anycubicsla_write_float(out, h.exposure_time_s); + anycubicsla_write_float(out, h.delay_before_exposure_s); + anycubicsla_write_float(out, h.bottom_exposure_time_s); + anycubicsla_write_float(out, h.bottom_layer_count); + anycubicsla_write_float(out, h.lift_distance_mm); + anycubicsla_write_float(out, h.lift_speed_mms); + anycubicsla_write_float(out, h.retract_speed_mms); + anycubicsla_write_float(out, h.volume_ml); + anycubicsla_write_int32(out, h.antialiasing); + anycubicsla_write_int32(out, h.res_x); + anycubicsla_write_int32(out, h.res_y); + anycubicsla_write_float(out, h.weight_g); + anycubicsla_write_float(out, h.price); + anycubicsla_write_int32(out, h.price_currency); + anycubicsla_write_int32(out, h.per_layer_override); + anycubicsla_write_int32(out, h.print_time_s); + anycubicsla_write_int32(out, h.transition_layer_count); + anycubicsla_write_int32(out, h.transition_layer_type); } -static void pwmx_write_preview(std::ofstream &out, pwmx_format_preview &p) +static void anycubicsla_write_preview(std::ofstream &out, anycubicsla_format_preview &p) { out.write(TAG_PREVIEW, sizeof(p.tag)); - pwmx_write_int32(out, p.payload_size); - pwmx_write_int32(out, p.preview_w); - pwmx_write_int32(out, p.preview_dpi); - pwmx_write_int32(out, p.preview_h); + anycubicsla_write_int32(out, p.payload_size); + anycubicsla_write_int32(out, p.preview_w); + anycubicsla_write_int32(out, p.preview_dpi); + anycubicsla_write_int32(out, p.preview_h); out.write((const char*) p.pixels, sizeof(p.pixels)); } -static void pwmx_write_layers_header(std::ofstream &out, pwmx_format_layers_header &h) +static void anycubicsla_write_layers_header(std::ofstream &out, anycubicsla_format_layers_header &h) { out.write(TAG_LAYERS, sizeof(h.tag)); - pwmx_write_int32(out, h.payload_size); - pwmx_write_int32(out, h.layer_count); + anycubicsla_write_int32(out, h.payload_size); + anycubicsla_write_int32(out, h.layer_count); } -static void pwmx_write_layer(std::ofstream &out, pwmx_format_layer &l) +static void anycubicsla_write_layer(std::ofstream &out, anycubicsla_format_layer &l) { - pwmx_write_int32(out, l.image_offset); - pwmx_write_int32(out, l.image_size); - pwmx_write_float(out, l.lift_distance_mm); - pwmx_write_float(out, l.lift_speed_mms); - pwmx_write_float(out, l.exposure_time_s); - pwmx_write_float(out, l.layer_height_mm); - pwmx_write_float(out, l.layer44); - pwmx_write_float(out, l.layer48); + anycubicsla_write_int32(out, l.image_offset); + anycubicsla_write_int32(out, l.image_size); + anycubicsla_write_float(out, l.lift_distance_mm); + anycubicsla_write_float(out, l.lift_speed_mms); + anycubicsla_write_float(out, l.exposure_time_s); + anycubicsla_write_float(out, l.layer_height_mm); + anycubicsla_write_float(out, l.layer44); + anycubicsla_write_float(out, l.layer48); } -void PwmxArchive::export_print(const std::string fname, +void AnycubicSLAArchive::export_print(const std::string fname, const SLAPrint &print, const ThumbnailsList &thumbnails, const std::string &/*projectname*/) { std::uint32_t layer_count = m_layers.size(); - pwmx_format_intro intro = {}; - pwmx_format_header header = {}; - pwmx_format_preview preview = {}; - pwmx_format_layers_header layers_header = {}; - pwmx_format_misc misc = {}; + anycubicsla_format_intro intro = {}; + anycubicsla_format_header header = {}; + anycubicsla_format_preview preview = {}; + anycubicsla_format_layers_header layers_header = {}; + anycubicsla_format_misc misc = {}; std::vector layer_images; std::uint32_t image_offset; - intro.version = 1; + assert(m_version == ANYCUBIC_SLA_FORMAT_VERSION_1); + + intro.version = m_version; intro.area_num = 4; intro.header_data_offset = sizeof(intro); intro.preview_data_offset = sizeof(intro) + sizeof(header); intro.layer_data_offset = intro.preview_data_offset + sizeof(preview); intro.image_data_offset = intro.layer_data_offset + sizeof(layers_header) + - (sizeof(pwmx_format_layer) * layer_count); + (sizeof(anycubicsla_format_layer) * layer_count); fill_header(header, misc, print, layer_count); fill_preview(preview, misc, thumbnails); @@ -513,21 +524,21 @@ void PwmxArchive::export_print(const std::string fname, // open the file and write the contents std::ofstream out; out.open(fname, std::ios::binary | std::ios::out | std::ios::trunc); - pwmx_write_intro(out, intro); - pwmx_write_header(out, header); - pwmx_write_preview(out, preview); + anycubicsla_write_intro(out, intro); + anycubicsla_write_header(out, header); + anycubicsla_write_preview(out, preview); layers_header.payload_size = intro.image_data_offset - intro.layer_data_offset - sizeof(layers_header.tag) - sizeof(layers_header.payload_size); layers_header.layer_count = layer_count; - pwmx_write_layers_header(out, layers_header); + anycubicsla_write_layers_header(out, layers_header); //layers layer_images.reserve(layer_count * LAYER_SIZE_ESTIMATE); image_offset = intro.image_data_offset; size_t i = 0; for (const sla::EncodedRaster &rst : m_layers) { - pwmx_format_layer l; + anycubicsla_format_layer l; std::memset(&l, 0, sizeof(l)); l.image_offset = image_offset; l.image_size = rst.size(); @@ -543,7 +554,7 @@ void PwmxArchive::export_print(const std::string fname, l.lift_speed_mms = header.lift_speed_mms; } image_offset += l.image_size; - pwmx_write_layer(out, l); + anycubicsla_write_layer(out, l); // add the rle encoded layer image into the buffer const char* img_start = reinterpret_cast(rst.data()); const char* img_end = img_start + rst.size(); diff --git a/src/libslic3r/Format/AnycubicSLA.hpp b/src/libslic3r/Format/AnycubicSLA.hpp new file mode 100644 index 000000000..46eb68d00 --- /dev/null +++ b/src/libslic3r/Format/AnycubicSLA.hpp @@ -0,0 +1,81 @@ +#ifndef _SLIC3R_FORMAT_PWMX_HPP_ +#define _SLIC3R_FORMAT_PWMX_HPP_ + +#include + +#include "SLAArchiveWriter.hpp" + +#include "libslic3r/PrintConfig.hpp" + +#define ANYCUBIC_SLA_FORMAT_VERSION_1 1 +#define ANYCUBIC_SLA_FORMAT_VERSION_515 515 +#define ANYCUBIC_SLA_FORMAT_VERSION_516 516 +#define ANYCUBIC_SLA_FORMAT_VERSION_517 517 + +#define ANYCUBIC_SLA_FORMAT_VERSIONED(FILEFORMAT, NAME, VERSION) \ + { FILEFORMAT, { FILEFORMAT, [] (const auto &cfg) { return std::make_unique(cfg, VERSION); } } } + +#define ANYCUBIC_SLA_FORMAT(FILEFORMAT, NAME) \ + ANYCUBIC_SLA_FORMAT_VERSIONED(FILEFORMAT, NAME, ANYCUBIC_SLA_FORMAT_VERSION_1) + +/** + // Supports only ANYCUBIC_SLA_VERSION_1 + ANYCUBIC_SLA_FORMAT_VERSIONED("pws", "Photon / Photon S", ANYCUBIC_SLA_VERSION_1), + ANYCUBIC_SLA_FORMAT_VERSIONED("pw0", "Photon Zero", ANYCUBIC_SLA_VERSION_1), + ANYCUBIC_SLA_FORMAT_VERSIONED("pwx", "Photon X", ANYCUBIC_SLA_VERSION_1), + + // Supports ANYCUBIC_SLA_VERSION_1 and ANYCUBIC_SLA_VERSION_515 + ANYCUBIC_SLA_FORMAT_VERSIONED("pwmo", "Photon Mono", ANYCUBIC_SLA_VERSION_1), + ANYCUBIC_SLA_FORMAT_VERSIONED("pwms", "Photon Mono SE", ANYCUBIC_SLA_VERSION_1), + ANYCUBIC_SLA_FORMAT_VERSIONED("dlp", "Photon Ultra", ANYCUBIC_SLA_VERSION_1), + ANYCUBIC_SLA_FORMAT_VERSIONED("pwmx", "Photon Mono X", ANYCUBIC_SLA_VERSION_1), + ANYCUBIC_SLA_FORMAT_VERSIONED("pmsq", "Photon Mono SQ", ANYCUBIC_SLA_VERSION_1), + + // Supports ANYCUBIC_SLA_VERSION_515 and ANYCUBIC_SLA_VERSION_516 + ANYCUBIC_SLA_FORMAT_VERSIONED("pwma", "Photon Mono 4K", ANYCUBIC_SLA_VERSION_515), + ANYCUBIC_SLA_FORMAT_VERSIONED("pm3", "Photon M3", ANYCUBIC_SLA_VERSION_515), + ANYCUBIC_SLA_FORMAT_VERSIONED("pm3m", "Photon M3 Max", ANYCUBIC_SLA_VERSION_515), + + // Supports NYCUBIC_SLA_VERSION_515 and ANYCUBIC_SLA_VERSION_516 and ANYCUBIC_SLA_VERSION_517 + ANYCUBIC_SLA_FORMAT_VERSIONED("pwmb", "Photon Mono X 6K / Photon M3 Plus", ANYCUBIC_SLA_VERSION_515), + ANYCUBIC_SLA_FORMAT_VERSIONED("dl2p", "Photon Photon D2", ANYCUBIC_SLA_VERSION_515), + ANYCUBIC_SLA_FORMAT_VERSIONED("pmx2", "Photon Mono X2", ANYCUBIC_SLA_VERSION_515), + ANYCUBIC_SLA_FORMAT_VERSIONED("pm3r", "Photon M3 Premium", ANYCUBIC_SLA_VERSION_515), +*/ + +namespace Slic3r { + +class AnycubicSLAArchive: public SLAArchiveWriter { + SLAPrinterConfig m_cfg; + uint16_t m_version; + +protected: + std::unique_ptr create_raster() const override; + sla::RasterEncoder get_encoder() const override; + + SLAPrinterConfig & cfg() { return m_cfg; } + const SLAPrinterConfig & cfg() const { return m_cfg; } + +public: + + AnycubicSLAArchive() = default; + explicit AnycubicSLAArchive(const SLAPrinterConfig &cfg): + m_cfg(cfg), m_version(ANYCUBIC_SLA_FORMAT_VERSION_1) {} + explicit AnycubicSLAArchive(SLAPrinterConfig &&cfg): + m_cfg(std::move(cfg)), m_version(ANYCUBIC_SLA_FORMAT_VERSION_1) {} + + explicit AnycubicSLAArchive(const SLAPrinterConfig &cfg, uint16_t version): + m_cfg(cfg), m_version(version) {} + explicit AnycubicSLAArchive(SLAPrinterConfig &&cfg, uint16_t version): + m_cfg(std::move(cfg)), m_version(version) {} + + void export_print(const std::string fname, + const SLAPrint &print, + const ThumbnailsList &thumbnails, + const std::string &projectname = "") override; +}; + + +} // namespace Slic3r::sla + +#endif // _SLIC3R_FORMAT_PWMX_HPP_ diff --git a/src/libslic3r/Format/SLAArchiveWriter.cpp b/src/libslic3r/Format/SLAArchiveWriter.cpp index b28c2c680..7546d7c46 100644 --- a/src/libslic3r/Format/SLAArchiveWriter.cpp +++ b/src/libslic3r/Format/SLAArchiveWriter.cpp @@ -2,7 +2,7 @@ #include "SL1.hpp" #include "SL1_SVG.hpp" -#include "pwmx.hpp" +#include "AnycubicSLA.hpp" #include "libslic3r/libslic3r.h" @@ -33,10 +33,9 @@ static const std::map REGISTERED_ARCHIVES { "SL2", { "sl1_svg", [] (const auto &cfg) { return std::make_unique(cfg); } } }, - { - "pwmx", - { "pwmx", [] (const auto &cfg) { return std::make_unique(cfg); } } - } + ANYCUBIC_SLA_FORMAT("pwmo", "Photon Mono"), + ANYCUBIC_SLA_FORMAT("pwmx", "Photon Mono X"), + ANYCUBIC_SLA_FORMAT("pwms", "Photon Mono SE"), }; std::unique_ptr diff --git a/src/libslic3r/Format/pwmx.hpp b/src/libslic3r/Format/pwmx.hpp deleted file mode 100644 index 6d667fab7..000000000 --- a/src/libslic3r/Format/pwmx.hpp +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef _SLIC3R_FORMAT_PWMX_HPP_ -#define _SLIC3R_FORMAT_PWMX_HPP_ - -#include - -#include "SLAArchiveWriter.hpp" - -#include "libslic3r/PrintConfig.hpp" - -namespace Slic3r { - -class PwmxArchive: public SLAArchiveWriter { - SLAPrinterConfig m_cfg; - -protected: - std::unique_ptr create_raster() const override; - sla::RasterEncoder get_encoder() const override; - - SLAPrinterConfig & cfg() { return m_cfg; } - const SLAPrinterConfig & cfg() const { return m_cfg; } - -public: - - PwmxArchive() = default; - explicit PwmxArchive(const SLAPrinterConfig &cfg): m_cfg(cfg) {} - explicit PwmxArchive(SLAPrinterConfig &&cfg): m_cfg(std::move(cfg)) {} - - void export_print(const std::string fname, - const SLAPrint &print, - const ThumbnailsList &thumbnails, - const std::string &projectname = "") override; -}; - - -} // namespace Slic3r::sla - -#endif // _SLIC3R_FORMAT_PWMX_HPP_ diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index fdf97cbe2..dffd94321 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3868,7 +3868,9 @@ void PrintConfigDef::init_sla_params() def->multiline = true; def->full_width = true; def->height = 13; - def->mode = comAdvanced; + // TODO currently notes are the only way to pass data + // for non-PrusaResearch printers. We therefore need to always show them + def->mode = comSimple; def->set_default_value(new ConfigOptionString("")); def = this->add("material_vendor", coString); From e93b222d33cf4a7392aab16d6f65a9448a6e4ddf Mon Sep 17 00:00:00 2001 From: Mimoja Date: Wed, 5 Apr 2023 15:04:06 +0200 Subject: [PATCH 021/103] format/AnycubicSLA: refactor Anycubic SLA format and add Photon Mono and Mono SE printers (#9929) * format/sla: Rename pwmx format to AnycubicSLA The Anycubic Photon SLA printer familiy is shipped with the PhotonWorkshop slicer. This slicer generates the sliced archives. These archives have per-printer extensions, not only pwmx. The name is -most of the times- comprised of the PhontonWorkshop "pw" bit and the model name. "mo" for the Photon Mono, "mx" for the Mono X. Therefore the format name "pwmx" is incorrect and we are renaming it to AnycubicSLA. On top of it we are introducing a helper macro to connect file extension and printer definition as most printers use extremely similiar formats. Signed-off-by: Mimoja * format/AnycubicSLA: Add missing fields The AnycubicSLA format description is derived from reverse engineering of the PhotonWorkshop output files. While the initial Photon devices had their binary headers with version 1 we have seen newer models with additional versions. Namely 515, 516 and 517. We are adding the version handling to the AnycubicSLA exporter to prepare for future version handling. Some fields were missing for Version 1 which are added. Signed-off-by: Mimoja * PrintConfig/sla: Move material notes to simple view As the Anycubic Photon Mono X uses the material notes to configure the printers parameters we need to change the visibility to allow every user acces. This will change the default behaviour for non Anycubic SLA printers. Signed-off-by: Mimoja * format/AnycubicSLA: Expose Antialiasing via material notes Similiar to how the other machine configurations are exposed via the material notes we are adding the Antialiasing config. Signed-off-by: Mimoja * Printer/sla: Add Anycubic Photon Mono and Mono SE The Photon Mono and Mono SE are format compatible printers with the Mono X. They support Version 1 and Version 515 binary archives. We are implementing them as Version 1 priters to reduce the overhead and keep the code in line with the Mono X. The addition as Version 1 printers leaves some features unexposed, most noteably the Antialiasing level configuration which is now always the maximum. Given that the printers check the eligability of sliced files by file extension we are poised to create per-printer sla_print default configurations to overwrite output_filename_format. Tested: Successfully printed multiple objects. Changing the layer parameter on the onscreen display succeeded. --------- Signed-off-by: Mimoja --- resources/profiles/Anycubic.idx | 4 + resources/profiles/Anycubic.ini | 103 ++++++++- .../Anycubic/PHOTON MONO SE_thumbnail.png | Bin 0 -> 39203 bytes .../Anycubic/PHOTON MONO_thumbnail.png | Bin 0 -> 35707 bytes src/libslic3r/CMakeLists.txt | 4 +- .../Format/{pwmx.cpp => AnycubicSLA.cpp} | 217 +++++++++--------- src/libslic3r/Format/AnycubicSLA.hpp | 81 +++++++ src/libslic3r/Format/SLAArchiveWriter.cpp | 9 +- src/libslic3r/Format/pwmx.hpp | 37 --- src/libslic3r/PrintConfig.cpp | 4 +- 10 files changed, 303 insertions(+), 156 deletions(-) create mode 100644 resources/profiles/Anycubic/PHOTON MONO SE_thumbnail.png create mode 100644 resources/profiles/Anycubic/PHOTON MONO_thumbnail.png rename src/libslic3r/Format/{pwmx.cpp => AnycubicSLA.cpp} (72%) create mode 100644 src/libslic3r/Format/AnycubicSLA.hpp delete mode 100644 src/libslic3r/Format/pwmx.hpp diff --git a/resources/profiles/Anycubic.idx b/resources/profiles/Anycubic.idx index e2cf7b087..a941cef9c 100644 --- a/resources/profiles/Anycubic.idx +++ b/resources/profiles/Anycubic.idx @@ -1,3 +1,7 @@ +min_slic3r_version = 2.6.0-alpha4 +0.2.4 Enable pad for Anycubic SLA profiles +0.2.3 Added Photon Mono printer. +0.2.2 Added Photon Mono SE printer. min_slic3r_version = 2.6.0-alpha2 0.2.1 Added Eolas Prints filaments. 0.2.0 Added Photon Mono X printer. diff --git a/resources/profiles/Anycubic.ini b/resources/profiles/Anycubic.ini index 9bebbbe76..771b38d01 100644 --- a/resources/profiles/Anycubic.ini +++ b/resources/profiles/Anycubic.ini @@ -73,6 +73,13 @@ technology = FFF family = PREDATOR default_materials = Generic PLA @PREDATOR; Generic PETG @PREDATOR; Generic ABS @PREDATOR +[printer_model:PHOTON MONO] +name = Photon Mono +variants = default +technology = SLA +family = PHOTON MONO +default_materials = Generic Blue Resin @MONO 0.05 + [printer_model:PHOTON MONO X] name = Photon Mono X variants = default @@ -80,6 +87,13 @@ technology = SLA family = PHOTON MONO default_materials = Generic Blue Resin @MONO 0.05 +[printer_model:PHOTON MONO SE] +name = Photon Mono SE +variants = default +technology = SLA +family = PHOTON MONO +default_materials = Generic Blue Resin @MONO 0.05 + # All presets starting with asterisk, for example *common*, are intermediate and they will # not make it into the user interface. @@ -2327,11 +2341,10 @@ z_offset = 0 ## SLA printers [sla_print:*common print ANYCUBIC SLA*] -compatible_printers_condition = printer_notes=~/.*PHOTONMONOX.*/ +compatible_printers_condition = printer_notes=~/.*VENDOR_ANYCUBIC.*/ and printer_notes=~/.*SLA.*/ layer_height = 0.05 -output_filename_format = [input_filename_base].pwmx pad_edge_radius = 0.5 -pad_enable = 0 +pad_enable = 1 pad_max_merge_distance = 50 pad_wall_height = 0 pad_wall_thickness = 1 @@ -2355,20 +2368,38 @@ support_pillar_widening_factor = 0 supports_enable = 1 support_small_pillar_diameter_percent = 60% -[sla_print:0.05 Normal @ANYCUBIC] +[sla_print:0.05 Normal @ANYCUBIC ABSTRACT] inherits = *common print ANYCUBIC SLA* +compatible_printers_condition = printer_notes=~/.*ABSTRACT_ONLY.*/ layer_height = 0.05 +[sla_print:0.05 Normal @ANYCUBIC MONO] +inherits = 0.05 Normal @ANYCUBIC ABSTRACT +compatible_printers_condition = printer_notes=~/.*PHOTONMONO\n.*/ +output_filename_format = [input_filename_base].pwmo + +[sla_print:0.05 Normal @ANYCUBIC MONO X] +inherits = 0.05 Normal @ANYCUBIC ABSTRACT +compatible_printers_condition = printer_notes=~/.*PHOTONMONOX\n.*/ +output_filename_format = [input_filename_base].pwmx + +[sla_print:0.05 Normal @ANYCUBIC MONO SE] +inherits = 0.05 Normal @ANYCUBIC ABSTRACT +compatible_printers_condition = printer_notes=~/.*PHOTONMONOSE\n.*/ +output_filename_format = [input_filename_base].pwma + + ## SLA materials +#MONO series printer need a significantly reduced exposure time but are otherwise compatible [sla_material:*common ANYCUBIC SLA*] -compatible_printers_condition = printer_notes=~/.*PHOTONMONOX.*/ +compatible_printers_condition = printer_notes=~/.*VENDOR_ANYCUBIC.*/ and printer_notes=~/.*SLA.*/ compatible_prints_condition = layer_height == 0.05 exposure_time = 7 initial_exposure_time = 40 initial_layer_height = 0.05 material_correction = 1,1,1 -material_notes = LIFT_DISTANCE=8.0\nLIFT_SPEED=2.5\nRETRACT_SPEED=3.0\nBOTTOM_LIFT_SPEED=2.0\nBOTTOM_LIFT_DISTANCE=9.0\nDELAY_BEFORE_EXPOSURE=0.5 +material_notes = #Distances are defined in mm, speeds are defined in mm/s.\n#Delay is defined in s.\nLIFT_DISTANCE=8.0\nLIFT_SPEED=2.5\nRETRACT_SPEED=3.0\nBOTTOM_LIFT_SPEED=2.0\nBOTTOM_LIFT_DISTANCE=9.0\nDELAY_BEFORE_EXPOSURE=0.5\nANTIALIASING=1 [sla_material:*common 0.05 ANYCUBIC SLA*] inherits = *common ANYCUBIC SLA* @@ -2380,10 +2411,66 @@ initial_exposure_time = 40 material_type = Tough material_vendor = Generic material_colour = #6080EC -compatible_printers_condition = printer_notes=~/.*PHOTONMONOX.*/ +compatible_printers_condition = printer_notes=~/.*MONO.*/ and printer_notes=~/.*VENDOR_ANYCUBIC.*/ and printer_notes=~/.*SLA.*/ ## Printers +[printer:Anycubic Photon Mono] +printer_technology = SLA +printer_model = PHOTON MONO +printer_variant = default +default_sla_material_profile = Generic Blue Resin @MONO 0.05 +default_sla_print_profile = 0.05 Normal @ANYCUBIC +thumbnails = 224x168 +sla_archive_format = pwmo +bed_shape = 0x0,82.62x0,82.62x130.56,0x130.56 +display_orientation = landscape +display_mirror_x = 1 +display_mirror_y = 0 +display_pixels_x = 1620 +display_pixels_y = 2560 +display_width = 82.62 +display_height = 130.56 +max_print_height = 165 +elefant_foot_compensation = 0.2 +elefant_foot_min_width = 0.2 +min_exposure_time = 0.8 +max_exposure_time = 120 +min_initial_exposure_time = 0.8 +max_initial_exposure_time = 300 +printer_correction = 1,1,1 +gamma_correction = 1 +area_fill = 50 +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.'\nPRINTER_VENDOR_ANYCUBIC\nPRINTER_MODEL_PHOTONMONO\nPRINTER_TECHNOLOGY_SLA\n + +[printer:Anycubic Photon Mono SE] +printer_technology = SLA +printer_model = PHOTON MONO SE +printer_variant = default +default_sla_material_profile = Generic Blue Resin @MONO 0.05 +default_sla_print_profile = 0.05 Normal @ANYCUBIC +thumbnails = 224x168 +sla_archive_format = pwms +bed_shape = 0x0,82.62x0,82.62x130.56,0x130.56 +display_orientation = landscape +display_mirror_x = 1 +display_mirror_y = 0 +display_pixels_x = 1620 +display_pixels_y = 2560 +display_width = 82.62 +display_height = 130.56 +max_print_height = 160 +elefant_foot_compensation = 0.2 +elefant_foot_min_width = 0.2 +min_exposure_time = 0.8 +max_exposure_time = 120 +min_initial_exposure_time = 0.8 +max_initial_exposure_time = 300 +printer_correction = 1,1,1 +gamma_correction = 1 +area_fill = 45 +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.'\nPRINTER_VENDOR_ANYCUBIC\nPRINTER_MODEL_PHOTONMONOSE\nPRINTER_TECHNOLOGY_SLA\n + [printer:Anycubic Photon Mono X] printer_technology = SLA printer_model = PHOTON MONO X @@ -2410,4 +2497,4 @@ max_initial_exposure_time = 300 printer_correction = 1,1,1 gamma_correction = 1 area_fill = 45 -printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.'\nPRINTER_VENDOR_ANYCUBIC\nPRINTER_MODEL_PHOTONMONOX\n \ No newline at end of file +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.'\nPRINTER_VENDOR_ANYCUBIC\nPRINTER_MODEL_PHOTONMONOX\nPRINTER_TECHNOLOGY_SLA\n \ No newline at end of file diff --git a/resources/profiles/Anycubic/PHOTON MONO SE_thumbnail.png b/resources/profiles/Anycubic/PHOTON MONO SE_thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..9fa39cb4f1b859572bbb59f6f29c205ff70a2f74 GIT binary patch literal 39203 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 D;k<`{ literal 0 HcmV?d00001 diff --git a/resources/profiles/Anycubic/PHOTON MONO_thumbnail.png b/resources/profiles/Anycubic/PHOTON MONO_thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..0aaf5c4cd6484315f7f99e187b670eb87dbed7cb GIT binary patch literal 35707 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 const std::uint8_t *src_end = src + size; while (src < src_end) { - pwx_get_pixel_span(src, src_end, pixel, span_len); + anycubicsla_get_pixel_span(src, src_end, pixel, span_len); src += span_len; // fully transparent of fully opaque pixel if (pixel == 0 || pixel == 0xF0) { @@ -78,27 +80,27 @@ struct PWXRasterEncoder } } - return sla::EncodedRaster(std::move(dst), "pwx"); + return sla::EncodedRaster(std::move(dst), "pwimg"); } }; using ConfMap = std::map; -typedef struct pwmx_format_intro +typedef struct anycubicsla_format_intro { char tag[12]; - std::uint32_t version; // value 1 - std::uint32_t area_num; // unknown - usually 4 + std::uint32_t version; // value 1 (also known as 515, 516 and 517) + std::uint32_t area_num; // Number of tables - usually 4 std::uint32_t header_data_offset; - std::float_t intro24; // unknown - usually 0 + std::uint32_t software_data_offset; // unused in version 1 std::uint32_t preview_data_offset; - std::float_t intro32; // unknown + std::uint32_t layer_color_offset; // unused in version 1 std::uint32_t layer_data_offset; - std::float_t intro40; // unknown + std::uint32_t extra_data_offset; // unused here (only used in version 516) std::uint32_t image_data_offset; -} pwmx_format_intro; +} anycubicsla_format_intro; -typedef struct pwmx_format_header +typedef struct anycubicsla_format_header { char tag[12]; std::uint32_t payload_size; @@ -121,11 +123,11 @@ typedef struct pwmx_format_header std::uint32_t per_layer_override; // ? unknown meaning ? std::uint32_t print_time_s; std::uint32_t transition_layer_count; - std::uint32_t unknown; // ? usually 0 ? + std::uint32_t transition_layer_type; // usually 0 -} pwmx_format_header; +} anycubicsla_format_header; -typedef struct pwmx_format_preview +typedef struct anycubicsla_format_preview { char tag[12]; std::uint32_t payload_size; @@ -134,16 +136,16 @@ typedef struct pwmx_format_preview std::uint32_t preview_h; // raw image data in BGR565 format std::uint8_t pixels[PREV_W * PREV_H * 2]; -} pwmx_format_preview; +} anycubicsla_format_preview; -typedef struct pwmx_format_layers_header +typedef struct anycubicsla_format_layers_header { char tag[12]; std::uint32_t payload_size; std::uint32_t layer_count; -} pwmx_format_layers_header; +} anycubicsla_format_layers_header; -typedef struct pwmx_format_layer +typedef struct anycubicsla_format_layer { std::uint32_t image_offset; std::uint32_t image_size; @@ -153,20 +155,20 @@ typedef struct pwmx_format_layer std::float_t layer_height_mm; std::float_t layer44; // unkown - usually 0 std::float_t layer48; // unkown - usually 0 -} pwmx_format_layer; +} anycubicsla_format_layer; -typedef struct pwmx_format_misc +typedef struct anycubicsla_format_misc { std::float_t bottom_layer_height_mm; std::float_t bottom_lift_distance_mm; std::float_t bottom_lift_speed_mms; -} pwmx_format_misc; +} anycubicsla_format_misc; -class PwmxFormatConfigDef : public ConfigDef +class AnycubicSLAFormatConfigDef : public ConfigDef { public: - PwmxFormatConfigDef() + AnycubicSLAFormatConfigDef() { add(CFG_LIFT_DISTANCE, coFloat); add(CFG_LIFT_SPEED, coFloat); @@ -174,17 +176,18 @@ public: add(CFG_DELAY_BEFORE_EXPOSURE, coFloat); add(CFG_BOTTOM_LIFT_DISTANCE, coFloat); add(CFG_BOTTOM_LIFT_SPEED, coFloat); + add(CFG_ANTIALIASING, coInt); } }; -class PwmxFormatDynamicConfig : public DynamicConfig +class AnycubicSLAFormatDynamicConfig : public DynamicConfig { public: - PwmxFormatDynamicConfig(){}; + AnycubicSLAFormatDynamicConfig(){}; const ConfigDef *def() const override { return &config_def; } private: - PwmxFormatConfigDef config_def; + AnycubicSLAFormatConfigDef config_def; }; namespace { @@ -222,8 +225,8 @@ template void crop_value(T &val, T val_min, T val_max) } } -void fill_preview(pwmx_format_preview &p, - pwmx_format_misc &/*m*/, +void fill_preview(anycubicsla_format_preview &p, + anycubicsla_format_misc &/*m*/, const ThumbnailsList &thumbnails) { @@ -266,9 +269,8 @@ void fill_preview(pwmx_format_preview &p, } } - -void fill_header(pwmx_format_header &h, - pwmx_format_misc &m, +void fill_header(anycubicsla_format_header &h, + anycubicsla_format_misc &m, const SLAPrint &print, std::uint32_t layer_count) { @@ -282,7 +284,7 @@ void fill_header(pwmx_format_header &h, auto mat_opt = cfg.option("material_notes"); std::string mnotes = mat_opt? cfg.option("material_notes")->serialize() : ""; // create a config parser from the material notes - Slic3r::PwmxFormatDynamicConfig mat_cfg; + Slic3r::AnycubicSLAFormatDynamicConfig mat_cfg; SLAPrintStatistics stats = print.print_statistics(); // sanitize the string config @@ -314,6 +316,13 @@ void fill_header(pwmx_format_header &h, h.per_layer_override = 0; // TODO - expose these variables to the UI rather than using material notes + if (mat_cfg.has(CFG_ANTIALIASING)) { + h.antialiasing = get_cfg_value_i(mat_cfg, CFG_ANTIALIASING); + crop_value(h.antialiasing, (uint32_t) 0, (uint32_t) 1); + } else { + h.antialiasing = 1; + } + h.delay_before_exposure_s = get_cfg_value_f(mat_cfg, CFG_DELAY_BEFORE_EXPOSURE, 0.5f); crop_value(h.delay_before_exposure_s, 0.0f, 1000.0f); @@ -356,7 +365,7 @@ void fill_header(pwmx_format_header &h, } // namespace -std::unique_ptr PwmxArchive::create_raster() const +std::unique_ptr AnycubicSLAArchive::create_raster() const { sla::Resolution res; sla::PixelDim pxdim; @@ -389,13 +398,13 @@ std::unique_ptr PwmxArchive::create_raster() const return sla::create_raster_grayscale_aa(res, pxdim, gamma, tr); } -sla::RasterEncoder PwmxArchive::get_encoder() const +sla::RasterEncoder AnycubicSLAArchive::get_encoder() const { - return PWXRasterEncoder{}; + return AnycubicSLARasterEncoder{}; } // Endian safe write of little endian 32bit ints -static void pwmx_write_int32(std::ofstream &out, std::uint32_t val) +static void anycubicsla_write_int32(std::ofstream &out, std::uint32_t val) { const char i1 = (val & 0xFF); const char i2 = (val >> 8) & 0xFF; @@ -407,104 +416,106 @@ static void pwmx_write_int32(std::ofstream &out, std::uint32_t val) out.write((const char *) &i3, 1); out.write((const char *) &i4, 1); } -static void pwmx_write_float(std::ofstream &out, std::float_t val) +static void anycubicsla_write_float(std::ofstream &out, std::float_t val) { std::uint32_t *f = (std::uint32_t *) &val; - pwmx_write_int32(out, *f); + anycubicsla_write_int32(out, *f); } -static void pwmx_write_intro(std::ofstream &out, pwmx_format_intro &i) +static void anycubicsla_write_intro(std::ofstream &out, anycubicsla_format_intro &i) { out.write(TAG_INTRO, sizeof(i.tag)); - pwmx_write_int32(out, i.version); - pwmx_write_int32(out, i.area_num); - pwmx_write_int32(out, i.header_data_offset); - pwmx_write_int32(out, i.intro24); - pwmx_write_int32(out, i.preview_data_offset); - pwmx_write_int32(out, i.intro32); - pwmx_write_int32(out, i.layer_data_offset); - pwmx_write_int32(out, i.intro40); - pwmx_write_int32(out, i.image_data_offset); + anycubicsla_write_int32(out, i.version); + anycubicsla_write_int32(out, i.area_num); + anycubicsla_write_int32(out, i.header_data_offset); + anycubicsla_write_int32(out, i.software_data_offset); + anycubicsla_write_int32(out, i.preview_data_offset); + anycubicsla_write_int32(out, i.layer_color_offset); + anycubicsla_write_int32(out, i.layer_data_offset); + anycubicsla_write_int32(out, i.extra_data_offset); + anycubicsla_write_int32(out, i.image_data_offset); } -static void pwmx_write_header(std::ofstream &out, pwmx_format_header &h) +static void anycubicsla_write_header(std::ofstream &out, anycubicsla_format_header &h) { out.write(TAG_HEADER, sizeof(h.tag)); - pwmx_write_int32(out, h.payload_size); - pwmx_write_float(out, h.pixel_size_um); - pwmx_write_float(out, h.layer_height_mm); - pwmx_write_float(out, h.exposure_time_s); - pwmx_write_float(out, h.delay_before_exposure_s); - pwmx_write_float(out, h.bottom_exposure_time_s); - pwmx_write_float(out, h.bottom_layer_count); - pwmx_write_float(out, h.lift_distance_mm); - pwmx_write_float(out, h.lift_speed_mms); - pwmx_write_float(out, h.retract_speed_mms); - pwmx_write_float(out, h.volume_ml); - pwmx_write_int32(out, h.antialiasing); - pwmx_write_int32(out, h.res_x); - pwmx_write_int32(out, h.res_y); - pwmx_write_float(out, h.weight_g); - pwmx_write_float(out, h.price); - pwmx_write_int32(out, h.price_currency); - pwmx_write_int32(out, h.per_layer_override); - pwmx_write_int32(out, h.print_time_s); - pwmx_write_int32(out, h.transition_layer_count); - pwmx_write_int32(out, h.unknown); + anycubicsla_write_int32(out, h.payload_size); + anycubicsla_write_float(out, h.pixel_size_um); + anycubicsla_write_float(out, h.layer_height_mm); + anycubicsla_write_float(out, h.exposure_time_s); + anycubicsla_write_float(out, h.delay_before_exposure_s); + anycubicsla_write_float(out, h.bottom_exposure_time_s); + anycubicsla_write_float(out, h.bottom_layer_count); + anycubicsla_write_float(out, h.lift_distance_mm); + anycubicsla_write_float(out, h.lift_speed_mms); + anycubicsla_write_float(out, h.retract_speed_mms); + anycubicsla_write_float(out, h.volume_ml); + anycubicsla_write_int32(out, h.antialiasing); + anycubicsla_write_int32(out, h.res_x); + anycubicsla_write_int32(out, h.res_y); + anycubicsla_write_float(out, h.weight_g); + anycubicsla_write_float(out, h.price); + anycubicsla_write_int32(out, h.price_currency); + anycubicsla_write_int32(out, h.per_layer_override); + anycubicsla_write_int32(out, h.print_time_s); + anycubicsla_write_int32(out, h.transition_layer_count); + anycubicsla_write_int32(out, h.transition_layer_type); } -static void pwmx_write_preview(std::ofstream &out, pwmx_format_preview &p) +static void anycubicsla_write_preview(std::ofstream &out, anycubicsla_format_preview &p) { out.write(TAG_PREVIEW, sizeof(p.tag)); - pwmx_write_int32(out, p.payload_size); - pwmx_write_int32(out, p.preview_w); - pwmx_write_int32(out, p.preview_dpi); - pwmx_write_int32(out, p.preview_h); + anycubicsla_write_int32(out, p.payload_size); + anycubicsla_write_int32(out, p.preview_w); + anycubicsla_write_int32(out, p.preview_dpi); + anycubicsla_write_int32(out, p.preview_h); out.write((const char*) p.pixels, sizeof(p.pixels)); } -static void pwmx_write_layers_header(std::ofstream &out, pwmx_format_layers_header &h) +static void anycubicsla_write_layers_header(std::ofstream &out, anycubicsla_format_layers_header &h) { out.write(TAG_LAYERS, sizeof(h.tag)); - pwmx_write_int32(out, h.payload_size); - pwmx_write_int32(out, h.layer_count); + anycubicsla_write_int32(out, h.payload_size); + anycubicsla_write_int32(out, h.layer_count); } -static void pwmx_write_layer(std::ofstream &out, pwmx_format_layer &l) +static void anycubicsla_write_layer(std::ofstream &out, anycubicsla_format_layer &l) { - pwmx_write_int32(out, l.image_offset); - pwmx_write_int32(out, l.image_size); - pwmx_write_float(out, l.lift_distance_mm); - pwmx_write_float(out, l.lift_speed_mms); - pwmx_write_float(out, l.exposure_time_s); - pwmx_write_float(out, l.layer_height_mm); - pwmx_write_float(out, l.layer44); - pwmx_write_float(out, l.layer48); + anycubicsla_write_int32(out, l.image_offset); + anycubicsla_write_int32(out, l.image_size); + anycubicsla_write_float(out, l.lift_distance_mm); + anycubicsla_write_float(out, l.lift_speed_mms); + anycubicsla_write_float(out, l.exposure_time_s); + anycubicsla_write_float(out, l.layer_height_mm); + anycubicsla_write_float(out, l.layer44); + anycubicsla_write_float(out, l.layer48); } -void PwmxArchive::export_print(const std::string fname, +void AnycubicSLAArchive::export_print(const std::string fname, const SLAPrint &print, const ThumbnailsList &thumbnails, const std::string &/*projectname*/) { std::uint32_t layer_count = m_layers.size(); - pwmx_format_intro intro = {}; - pwmx_format_header header = {}; - pwmx_format_preview preview = {}; - pwmx_format_layers_header layers_header = {}; - pwmx_format_misc misc = {}; + anycubicsla_format_intro intro = {}; + anycubicsla_format_header header = {}; + anycubicsla_format_preview preview = {}; + anycubicsla_format_layers_header layers_header = {}; + anycubicsla_format_misc misc = {}; std::vector layer_images; std::uint32_t image_offset; - intro.version = 1; + assert(m_version == ANYCUBIC_SLA_FORMAT_VERSION_1); + + intro.version = m_version; intro.area_num = 4; intro.header_data_offset = sizeof(intro); intro.preview_data_offset = sizeof(intro) + sizeof(header); intro.layer_data_offset = intro.preview_data_offset + sizeof(preview); intro.image_data_offset = intro.layer_data_offset + sizeof(layers_header) + - (sizeof(pwmx_format_layer) * layer_count); + (sizeof(anycubicsla_format_layer) * layer_count); fill_header(header, misc, print, layer_count); fill_preview(preview, misc, thumbnails); @@ -513,21 +524,21 @@ void PwmxArchive::export_print(const std::string fname, // open the file and write the contents std::ofstream out; out.open(fname, std::ios::binary | std::ios::out | std::ios::trunc); - pwmx_write_intro(out, intro); - pwmx_write_header(out, header); - pwmx_write_preview(out, preview); + anycubicsla_write_intro(out, intro); + anycubicsla_write_header(out, header); + anycubicsla_write_preview(out, preview); layers_header.payload_size = intro.image_data_offset - intro.layer_data_offset - sizeof(layers_header.tag) - sizeof(layers_header.payload_size); layers_header.layer_count = layer_count; - pwmx_write_layers_header(out, layers_header); + anycubicsla_write_layers_header(out, layers_header); //layers layer_images.reserve(layer_count * LAYER_SIZE_ESTIMATE); image_offset = intro.image_data_offset; size_t i = 0; for (const sla::EncodedRaster &rst : m_layers) { - pwmx_format_layer l; + anycubicsla_format_layer l; std::memset(&l, 0, sizeof(l)); l.image_offset = image_offset; l.image_size = rst.size(); @@ -543,7 +554,7 @@ void PwmxArchive::export_print(const std::string fname, l.lift_speed_mms = header.lift_speed_mms; } image_offset += l.image_size; - pwmx_write_layer(out, l); + anycubicsla_write_layer(out, l); // add the rle encoded layer image into the buffer const char* img_start = reinterpret_cast(rst.data()); const char* img_end = img_start + rst.size(); diff --git a/src/libslic3r/Format/AnycubicSLA.hpp b/src/libslic3r/Format/AnycubicSLA.hpp new file mode 100644 index 000000000..46eb68d00 --- /dev/null +++ b/src/libslic3r/Format/AnycubicSLA.hpp @@ -0,0 +1,81 @@ +#ifndef _SLIC3R_FORMAT_PWMX_HPP_ +#define _SLIC3R_FORMAT_PWMX_HPP_ + +#include + +#include "SLAArchiveWriter.hpp" + +#include "libslic3r/PrintConfig.hpp" + +#define ANYCUBIC_SLA_FORMAT_VERSION_1 1 +#define ANYCUBIC_SLA_FORMAT_VERSION_515 515 +#define ANYCUBIC_SLA_FORMAT_VERSION_516 516 +#define ANYCUBIC_SLA_FORMAT_VERSION_517 517 + +#define ANYCUBIC_SLA_FORMAT_VERSIONED(FILEFORMAT, NAME, VERSION) \ + { FILEFORMAT, { FILEFORMAT, [] (const auto &cfg) { return std::make_unique(cfg, VERSION); } } } + +#define ANYCUBIC_SLA_FORMAT(FILEFORMAT, NAME) \ + ANYCUBIC_SLA_FORMAT_VERSIONED(FILEFORMAT, NAME, ANYCUBIC_SLA_FORMAT_VERSION_1) + +/** + // Supports only ANYCUBIC_SLA_VERSION_1 + ANYCUBIC_SLA_FORMAT_VERSIONED("pws", "Photon / Photon S", ANYCUBIC_SLA_VERSION_1), + ANYCUBIC_SLA_FORMAT_VERSIONED("pw0", "Photon Zero", ANYCUBIC_SLA_VERSION_1), + ANYCUBIC_SLA_FORMAT_VERSIONED("pwx", "Photon X", ANYCUBIC_SLA_VERSION_1), + + // Supports ANYCUBIC_SLA_VERSION_1 and ANYCUBIC_SLA_VERSION_515 + ANYCUBIC_SLA_FORMAT_VERSIONED("pwmo", "Photon Mono", ANYCUBIC_SLA_VERSION_1), + ANYCUBIC_SLA_FORMAT_VERSIONED("pwms", "Photon Mono SE", ANYCUBIC_SLA_VERSION_1), + ANYCUBIC_SLA_FORMAT_VERSIONED("dlp", "Photon Ultra", ANYCUBIC_SLA_VERSION_1), + ANYCUBIC_SLA_FORMAT_VERSIONED("pwmx", "Photon Mono X", ANYCUBIC_SLA_VERSION_1), + ANYCUBIC_SLA_FORMAT_VERSIONED("pmsq", "Photon Mono SQ", ANYCUBIC_SLA_VERSION_1), + + // Supports ANYCUBIC_SLA_VERSION_515 and ANYCUBIC_SLA_VERSION_516 + ANYCUBIC_SLA_FORMAT_VERSIONED("pwma", "Photon Mono 4K", ANYCUBIC_SLA_VERSION_515), + ANYCUBIC_SLA_FORMAT_VERSIONED("pm3", "Photon M3", ANYCUBIC_SLA_VERSION_515), + ANYCUBIC_SLA_FORMAT_VERSIONED("pm3m", "Photon M3 Max", ANYCUBIC_SLA_VERSION_515), + + // Supports NYCUBIC_SLA_VERSION_515 and ANYCUBIC_SLA_VERSION_516 and ANYCUBIC_SLA_VERSION_517 + ANYCUBIC_SLA_FORMAT_VERSIONED("pwmb", "Photon Mono X 6K / Photon M3 Plus", ANYCUBIC_SLA_VERSION_515), + ANYCUBIC_SLA_FORMAT_VERSIONED("dl2p", "Photon Photon D2", ANYCUBIC_SLA_VERSION_515), + ANYCUBIC_SLA_FORMAT_VERSIONED("pmx2", "Photon Mono X2", ANYCUBIC_SLA_VERSION_515), + ANYCUBIC_SLA_FORMAT_VERSIONED("pm3r", "Photon M3 Premium", ANYCUBIC_SLA_VERSION_515), +*/ + +namespace Slic3r { + +class AnycubicSLAArchive: public SLAArchiveWriter { + SLAPrinterConfig m_cfg; + uint16_t m_version; + +protected: + std::unique_ptr create_raster() const override; + sla::RasterEncoder get_encoder() const override; + + SLAPrinterConfig & cfg() { return m_cfg; } + const SLAPrinterConfig & cfg() const { return m_cfg; } + +public: + + AnycubicSLAArchive() = default; + explicit AnycubicSLAArchive(const SLAPrinterConfig &cfg): + m_cfg(cfg), m_version(ANYCUBIC_SLA_FORMAT_VERSION_1) {} + explicit AnycubicSLAArchive(SLAPrinterConfig &&cfg): + m_cfg(std::move(cfg)), m_version(ANYCUBIC_SLA_FORMAT_VERSION_1) {} + + explicit AnycubicSLAArchive(const SLAPrinterConfig &cfg, uint16_t version): + m_cfg(cfg), m_version(version) {} + explicit AnycubicSLAArchive(SLAPrinterConfig &&cfg, uint16_t version): + m_cfg(std::move(cfg)), m_version(version) {} + + void export_print(const std::string fname, + const SLAPrint &print, + const ThumbnailsList &thumbnails, + const std::string &projectname = "") override; +}; + + +} // namespace Slic3r::sla + +#endif // _SLIC3R_FORMAT_PWMX_HPP_ diff --git a/src/libslic3r/Format/SLAArchiveWriter.cpp b/src/libslic3r/Format/SLAArchiveWriter.cpp index b28c2c680..7546d7c46 100644 --- a/src/libslic3r/Format/SLAArchiveWriter.cpp +++ b/src/libslic3r/Format/SLAArchiveWriter.cpp @@ -2,7 +2,7 @@ #include "SL1.hpp" #include "SL1_SVG.hpp" -#include "pwmx.hpp" +#include "AnycubicSLA.hpp" #include "libslic3r/libslic3r.h" @@ -33,10 +33,9 @@ static const std::map REGISTERED_ARCHIVES { "SL2", { "sl1_svg", [] (const auto &cfg) { return std::make_unique(cfg); } } }, - { - "pwmx", - { "pwmx", [] (const auto &cfg) { return std::make_unique(cfg); } } - } + ANYCUBIC_SLA_FORMAT("pwmo", "Photon Mono"), + ANYCUBIC_SLA_FORMAT("pwmx", "Photon Mono X"), + ANYCUBIC_SLA_FORMAT("pwms", "Photon Mono SE"), }; std::unique_ptr diff --git a/src/libslic3r/Format/pwmx.hpp b/src/libslic3r/Format/pwmx.hpp deleted file mode 100644 index 6d667fab7..000000000 --- a/src/libslic3r/Format/pwmx.hpp +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef _SLIC3R_FORMAT_PWMX_HPP_ -#define _SLIC3R_FORMAT_PWMX_HPP_ - -#include - -#include "SLAArchiveWriter.hpp" - -#include "libslic3r/PrintConfig.hpp" - -namespace Slic3r { - -class PwmxArchive: public SLAArchiveWriter { - SLAPrinterConfig m_cfg; - -protected: - std::unique_ptr create_raster() const override; - sla::RasterEncoder get_encoder() const override; - - SLAPrinterConfig & cfg() { return m_cfg; } - const SLAPrinterConfig & cfg() const { return m_cfg; } - -public: - - PwmxArchive() = default; - explicit PwmxArchive(const SLAPrinterConfig &cfg): m_cfg(cfg) {} - explicit PwmxArchive(SLAPrinterConfig &&cfg): m_cfg(std::move(cfg)) {} - - void export_print(const std::string fname, - const SLAPrint &print, - const ThumbnailsList &thumbnails, - const std::string &projectname = "") override; -}; - - -} // namespace Slic3r::sla - -#endif // _SLIC3R_FORMAT_PWMX_HPP_ diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 353142029..2d5a3b150 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3867,7 +3867,9 @@ void PrintConfigDef::init_sla_params() def->multiline = true; def->full_width = true; def->height = 13; - def->mode = comAdvanced; + // TODO currently notes are the only way to pass data + // for non-PrusaResearch printers. We therefore need to always show them + def->mode = comSimple; def->set_default_value(new ConfigOptionString("")); def = this->add("material_vendor", coString); From b5514120e5e5b4a0abb7c6446335b38107f60880 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 5 Apr 2023 18:06:02 +0200 Subject: [PATCH 022/103] Ensuring improvements and fixes: Fix issue https://github.com/prusa3d/PrusaSlicer/issues/9978 - too agressive filtering caused holes in top surfaces Fix issue https://github.com/prusa3d/PrusaSlicer/issues/10231 - thin bridges not anchored, again due to too aggressive ensuring filter Fix issue https://github.com/prusa3d/PrusaSlicer/issues/9988 - Slicing with 1 top/bottom surface and very low min shell thickness caused non-anchored solid fills. Fixed by handling as a special case. --- .../BeadingStrategyFactory.cpp | 8 +-- src/libslic3r/PrintObject.cpp | 65 ++++++++++--------- 2 files changed, 37 insertions(+), 36 deletions(-) diff --git a/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategyFactory.cpp b/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategyFactory.cpp index 4044c9013..37d3d4eba 100644 --- a/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategyFactory.cpp +++ b/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategyFactory.cpp @@ -32,20 +32,20 @@ BeadingStrategyPtr BeadingStrategyFactory::makeStrategy( ) { BeadingStrategyPtr ret = std::make_unique(preferred_bead_width_inner, preferred_transition_length, transitioning_angle, wall_split_middle_threshold, wall_add_middle_threshold, inward_distributed_center_wall_count); - BOOST_LOG_TRIVIAL(debug) << "Applying the Redistribute meta-strategy with outer-wall width = " << preferred_bead_width_outer << ", inner-wall width = " << preferred_bead_width_inner << "."; + BOOST_LOG_TRIVIAL(trace) << "Applying the Redistribute meta-strategy with outer-wall width = " << preferred_bead_width_outer << ", inner-wall width = " << preferred_bead_width_inner << "."; ret = std::make_unique(preferred_bead_width_outer, minimum_variable_line_ratio, std::move(ret)); if (print_thin_walls) { - BOOST_LOG_TRIVIAL(debug) << "Applying the Widening Beading meta-strategy with minimum input width " << min_feature_size << " and minimum output width " << min_bead_width << "."; + BOOST_LOG_TRIVIAL(trace) << "Applying the Widening Beading meta-strategy with minimum input width " << min_feature_size << " and minimum output width " << min_bead_width << "."; ret = std::make_unique(std::move(ret), min_feature_size, min_bead_width); } if (outer_wall_offset > 0) { - BOOST_LOG_TRIVIAL(debug) << "Applying the OuterWallOffset meta-strategy with offset = " << outer_wall_offset << "."; + BOOST_LOG_TRIVIAL(trace) << "Applying the OuterWallOffset meta-strategy with offset = " << outer_wall_offset << "."; ret = std::make_unique(outer_wall_offset, std::move(ret)); } //Apply the LimitedBeadingStrategy last, since that adds a 0-width marker wall which other beading strategies shouldn't touch. - BOOST_LOG_TRIVIAL(debug) << "Applying the Limited Beading meta-strategy with maximum bead count = " << max_bead_count << "."; + BOOST_LOG_TRIVIAL(trace) << "Applying the Limited Beading meta-strategy with maximum bead count = " << max_bead_count << "."; ret = std::make_unique(max_bead_count, std::move(ret)); return ret; } diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 3f21a80c9..aec104e10 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1178,19 +1178,6 @@ void PrintObject::discover_vertical_shells() }; bool spiral_vase = this->print()->config().spiral_vase.value; size_t num_layers = spiral_vase ? std::min(size_t(this->printing_region(0).config().bottom_solid_layers), m_layers.size()) : m_layers.size(); - coordf_t min_layer_height = this->slicing_parameters().min_layer_height; - // Does this region possibly produce more than 1 top or bottom layer? - auto has_extra_layers_fn = [min_layer_height](const PrintRegionConfig &config) { - auto num_extra_layers = [min_layer_height](int num_solid_layers, coordf_t min_shell_thickness) { - if (num_solid_layers == 0) - return 0; - int n = num_solid_layers - 1; - int n2 = int(ceil(min_shell_thickness / min_layer_height)); - return std::max(n, n2 - 1); - }; - return num_extra_layers(config.top_solid_layers, config.top_solid_min_thickness) + - num_extra_layers(config.bottom_solid_layers, config.bottom_solid_min_thickness) > 0; - }; std::vector cache_top_botom_regions(num_layers, DiscoverVerticalShellsCacheEntry()); bool top_bottom_surfaces_all_regions = this->num_printing_regions() > 1 && ! m_config.interface_shells.value; // static constexpr const float top_bottom_expansion_coeff = 1.05f; @@ -1199,18 +1186,6 @@ void PrintObject::discover_vertical_shells() if (top_bottom_surfaces_all_regions) { // This is a multi-material print and interface_shells are disabled, meaning that the vertical shell thickness // is calculated over all materials. - // Is the "ensure vertical wall thickness" applicable to any region? - bool has_extra_layers = false; - for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) { - const PrintRegionConfig &config = this->printing_region(region_id).config(); - if (has_extra_layers_fn(config)) { - has_extra_layers = true; - break; - } - } - if (! has_extra_layers) - // The "ensure vertical wall thickness" feature is not applicable to any of the regions. Quit. - return; BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells in parallel - start : cache top / bottom"; //FIXME Improve the heuristics for a grain size. size_t grain_size = std::max(num_layers / 16, size_t(1)); @@ -1280,11 +1255,6 @@ void PrintObject::discover_vertical_shells() } for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { - const PrintRegion ®ion = this->printing_region(region_id); - if (! has_extra_layers_fn(region.config())) - // Zero or 1 layer, there is no additional vertical wall thickness enforced. - continue; - //FIXME Improve the heuristics for a grain size. size_t grain_size = std::max(num_layers / 16, size_t(1)); @@ -1395,13 +1365,25 @@ void PrintObject::discover_vertical_shells() coordf_t print_z = layer->print_z; int i = int(idx_layer) + 1; int itop = int(idx_layer) + n_top_layers; + bool at_least_one_top_projected = false; for (; i < int(cache_top_botom_regions.size()) && (i < itop || m_layers[i]->print_z - print_z < region_config.top_solid_min_thickness - EPSILON); ++ i) { + at_least_one_top_projected = true; const DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[i]; combine_holes(cache.holes); combine_shells(cache.top_surfaces); } + if (!at_least_one_top_projected && i < int(cache_top_botom_regions.size())) { + // Lets consider this a special case - with only 1 top solid and minimal shell thickness settings, the + // boundaries of solid layers are not anchored over/under perimeters, so lets fix it by adding at least one + // perimeter width of area + Polygons anchor_area = intersection(expand(cache_top_botom_regions[idx_layer].top_surfaces, + layerm->flow(frExternalPerimeter).scaled_spacing()), + to_polygons(m_layers[i]->lslices)); + combine_shells(anchor_area); + } + if (one_more_layer_below_top_bottom_surfaces) if (i < int(cache_top_botom_regions.size()) && (i <= itop || m_layers[i]->bottom_z() - print_z < region_config.top_solid_min_thickness - EPSILON)) @@ -1412,13 +1394,23 @@ void PrintObject::discover_vertical_shells() coordf_t bottom_z = layer->bottom_z(); int i = int(idx_layer) - 1; int ibottom = int(idx_layer) - n_bottom_layers; + bool at_least_one_bottom_projected = false; for (; i >= 0 && (i > ibottom || bottom_z - m_layers[i]->bottom_z() < region_config.bottom_solid_min_thickness - EPSILON); -- i) { + at_least_one_bottom_projected = true; const DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[i]; combine_holes(cache.holes); combine_shells(cache.bottom_surfaces); } + + if (!at_least_one_bottom_projected && i >= 0) { + Polygons anchor_area = intersection(expand(cache_top_botom_regions[idx_layer].bottom_surfaces, + layerm->flow(frExternalPerimeter).scaled_spacing()), + to_polygons(m_layers[i]->lslices)); + combine_shells(anchor_area); + } + if (one_more_layer_below_top_bottom_surfaces) if (i >= 0 && (i > ibottom || bottom_z - m_layers[i]->print_z < region_config.bottom_solid_min_thickness - EPSILON)) @@ -1514,10 +1506,19 @@ void PrintObject::discover_vertical_shells() // Finally expand the infill a bit to remove tiny gaps between solid infill and the other regions. narrow_sparse_infill_region_radius - tiny_overlap_radius, ClipperLib::jtSquare); + Polygons internal_volume; + { + Polygons shrinked_bottom_slice = idx_layer > 0 ? to_polygons(m_layers[idx_layer - 1]->lslices) : Polygons{}; + Polygons shrinked_upper_slice = idx_layer > 0 ? to_polygons(m_layers[idx_layer + 1]->lslices) : Polygons{}; + internal_volume = intersection(shrinked_bottom_slice, shrinked_upper_slice); + } + // The opening operation may cause scattered tiny drops on the smooth parts of the model, filter them out regularized_shell.erase(std::remove_if(regularized_shell.begin(), regularized_shell.end(), - [&min_perimeter_infill_spacing](const ExPolygon &p) { - return p.area() < min_perimeter_infill_spacing * scaled(8.0); + [&min_perimeter_infill_spacing, &internal_volume](const ExPolygon &p) { + return p.area() < min_perimeter_infill_spacing * scaled(1.5) || + (p.area() < min_perimeter_infill_spacing * scaled(8.0) && + diff(to_polygons(p), internal_volume).empty()); }), regularized_shell.end()); } From b1081d7ac3e6631f46ced95dd5650e1be355fdfe Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Thu, 6 Apr 2023 09:46:12 +0200 Subject: [PATCH 023/103] Fix segfault in ensuring regularization - wrong check for unbounded access --- src/libslic3r/PrintObject.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index aec104e10..591abd952 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1509,7 +1509,9 @@ void PrintObject::discover_vertical_shells() Polygons internal_volume; { Polygons shrinked_bottom_slice = idx_layer > 0 ? to_polygons(m_layers[idx_layer - 1]->lslices) : Polygons{}; - Polygons shrinked_upper_slice = idx_layer > 0 ? to_polygons(m_layers[idx_layer + 1]->lslices) : Polygons{}; + Polygons shrinked_upper_slice = (idx_layer + 1) < m_layers.size() ? + to_polygons(m_layers[idx_layer + 1]->lslices) : + Polygons{}; internal_volume = intersection(shrinked_bottom_slice, shrinked_upper_slice); } From 030cfaf4b32667e9731ae2f1e7c3f6aba37a3d4d Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 6 Apr 2023 10:40:07 +0200 Subject: [PATCH 024/103] Fix of SPE-1630 crash when referencing an invalid variable name using the old placeholder parser syntax. Also implemented unescaping of \r\n\"\\ in string syntax. --- src/libslic3r/PlaceholderParser.cpp | 54 +++++++++++++++++++-- tests/libslic3r/test_placeholder_parser.cpp | 2 + 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index 11ffd4a54..44b9d1ee9 100644 --- a/src/libslic3r/PlaceholderParser.cpp +++ b/src/libslic3r/PlaceholderParser.cpp @@ -1,6 +1,7 @@ #include "PlaceholderParser.hpp" #include "Exception.hpp" #include "Flow.hpp" +#include "Utils.hpp" #include #include #include @@ -204,6 +205,7 @@ namespace client explicit expr(double d, const Iterator &it_begin, const Iterator &it_end) : m_type(TYPE_DOUBLE), it_range(it_begin, it_end) { m_data.d = d; } explicit expr(const char *s) : m_type(TYPE_STRING) { m_data.s = new std::string(s); } explicit expr(const std::string &s) : m_type(TYPE_STRING) { m_data.s = new std::string(s); } + explicit expr(std::string &&s) : m_type(TYPE_STRING) { m_data.s = new std::string(std::move(s)); } explicit expr(const std::string &s, const Iterator &it_begin, const Iterator &it_end) : m_type(TYPE_STRING), it_range(it_begin, it_end) { m_data.s = new std::string(s); } explicit expr(expr &&rhs, const Iterator &it_begin, const Iterator &it_end) : m_type(rhs.type()), it_range{ it_begin, it_end } @@ -904,9 +906,12 @@ namespace client const ConfigOption *opt = ctx->resolve_symbol(opt_key_str); if (opt == nullptr) { // Check whether the opt_key ends with '_'. - if (opt_key_str.back() == '_') + if (opt_key_str.back() == '_') { opt_key_str.resize(opt_key_str.size() - 1); - opt = ctx->resolve_symbol(opt_key_str); + opt = ctx->resolve_symbol(opt_key_str); + } + if (opt == nullptr) + ctx->throw_exception("Variable does not exist", opt_key); } if (! opt->is_vector()) ctx->throw_exception("Trying to index a scalar variable", opt_key); @@ -1790,8 +1795,49 @@ namespace client if (ctx->skipping()) { out.reset(); out.it_range = it_range; - } else - out = expr(std::string(it_range.begin() + 1, it_range.end() - 1), it_range.begin(), it_range.end()); + } else { + // Unescape the string, UTF-8 safe. + std::string s; + auto begin = std::next(it_range.begin()); + auto end = std::prev(it_range.end()); + assert(begin <= end); + { + // 1) Get the size of the string after unescaping. + size_t len = 0; + for (auto it = begin; it != end;) { + if (*it == '\\') { + if (++ it == end || + (*it != 'r' && *it != 'n' && *it != '"' && *it != '\\')) + ctx->throw_exception("Invalid escape sequence", {std::prev(it), std::next(it) }); + ++ len; + ++ it; + } else { + size_t n = get_utf8_sequence_length(&*it, end - it); + len += n; + it += n; + } + } + // and reserve the string. + s.reserve(len); + } + // 2) Copy & unescape the string. + for (auto it = begin; it != end;) { + if (*it == '\\') { + char c = *(++ it); + if (c == 'r') + c = '\r'; + else if (c == 'n') + c = '\n'; + s += c; + ++ it; + } else { + size_t n = get_utf8_sequence_length(&*it, end - it); + s.append(&*it, n); + it += n; + } + } + out = expr(std::move(s), it_range.begin(), it_range.end()); + } } static void expr_(expr &value, Iterator &end_pos, expr &out) { auto begin_pos = out.it_range.begin(); out = expr(std::move(value), begin_pos, end_pos); } diff --git a/tests/libslic3r/test_placeholder_parser.cpp b/tests/libslic3r/test_placeholder_parser.cpp index a6da48bf3..e6fe5bfe2 100644 --- a/tests/libslic3r/test_placeholder_parser.cpp +++ b/tests/libslic3r/test_placeholder_parser.cpp @@ -44,6 +44,8 @@ SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") { SECTION("multiple expressions with semicolons 2") { REQUIRE(parser.process("{temperature[foo];;temperature[foo];}") == "357357"); } SECTION("multiple expressions with semicolons 3") { REQUIRE(parser.process("{temperature[foo];;;temperature[foo];;}") == "357357"); } + SECTION("parsing string with escaped characters") { REQUIRE(parser.process("{\"hu\\nha\\\\\\\"ha\\\"\"}") == "hu\nha\\\"ha\""); } + // Test the math expressions. SECTION("math: 2*3") { REQUIRE(parser.process("{2*3}") == "6"); } SECTION("math: 2*3/6") { REQUIRE(parser.process("{2*3/6}") == "1"); } From 5185e760ce31dc69d670686296f4b89a71523aa1 Mon Sep 17 00:00:00 2001 From: rtyr <36745189+rtyr@users.noreply.github.com> Date: Thu, 6 Apr 2023 10:48:19 +0200 Subject: [PATCH 025/103] Sync with PrusaSlicer-settings. Updated g-code flavor. --- resources/profiles/Voron.idx | 2 ++ resources/profiles/Voron.ini | 8 +++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/resources/profiles/Voron.idx b/resources/profiles/Voron.idx index a169eb751..7c519c08c 100644 --- a/resources/profiles/Voron.idx +++ b/resources/profiles/Voron.idx @@ -1,3 +1,5 @@ +min_slic3r_version = 2.6.0-alpha6 +1.0.2 Updated g-code flavor and travel accelerations. min_slic3r_version = 2.4.2 1.0.1 Added 350mm Voron v1 variant. Updated max print heights. Removed redundant v1 volcano nozzle variants. min_slic3r_version = 2.4.0-beta0 diff --git a/resources/profiles/Voron.ini b/resources/profiles/Voron.ini index e34da5010..921fd375e 100644 --- a/resources/profiles/Voron.ini +++ b/resources/profiles/Voron.ini @@ -7,7 +7,7 @@ name = Voron # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the PrusaSlicer configuration to be downgraded. -config_version = 1.0.1 +config_version = 1.0.2 # Where to get the updates from? config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/Voron/ @@ -183,7 +183,8 @@ deretract_speed = 25 end_gcode = print_end ;end script from macro extruder_colour = #FFE3CA extruder_offset = 0x0 -gcode_flavor = marlin +gcode_flavor = klipper +autoemit_temperature_commands = 1 layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z] machine_max_acceleration_e = 10000 machine_max_acceleration_extruding = 1500 @@ -672,7 +673,8 @@ brim_width = 0 clip_multipart_objects = 1 compatible_printers = complete_objects = 0 -default_acceleration = 3000 +default_acceleration = 2000 +travel_acceleration = 3000 dont_support_bridges = 1 ensure_vertical_shell_thickness = 1 external_perimeters_first = 0 From fe292cc45d588f96f500f21eea55f6869770072b Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Thu, 6 Apr 2023 11:24:26 +0200 Subject: [PATCH 026/103] Fix SPE-1642 - wrong flow used for spacing of internal bridging over infill, also caused error when using thin nozzle (0.1) --- src/libslic3r/PrintObject.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 591abd952..86355fac2 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1770,7 +1770,7 @@ void PrintObject::bridge_over_infill() if (clustered_layers_for_threads.empty() || this->get_layer(clustered_layers_for_threads.back().back())->print_z < this->get_layer(pair.first)->print_z - - this->get_layer(pair.first)->regions()[0]->flow(frSolidInfill, true).height() * target_flow_height_factor - + this->get_layer(pair.first)->regions()[0]->bridging_flow(frSolidInfill, true).height() * target_flow_height_factor - EPSILON || intersection(layer_area_covered_by_candidates[clustered_layers_for_threads.back().back()], layer_area_covered_by_candidates[pair.first]) @@ -2105,9 +2105,10 @@ void PrintObject::bridge_over_infill() } // Gather deep infill areas, where thick bridges fit - coordf_t spacing = surfaces_by_layer[lidx].front().region->flow(frSolidInfill, true).scaled_spacing(); - coordf_t target_flow_height = surfaces_by_layer[lidx].front().region->flow(frSolidInfill, true).height() * target_flow_height_factor; - Polygons deep_infill_area = gather_areas_w_depth(po, lidx, target_flow_height); + coordf_t spacing = surfaces_by_layer[lidx].front().region->bridging_flow(frSolidInfill, true).scaled_spacing(); + coordf_t target_flow_height = surfaces_by_layer[lidx].front().region->bridging_flow(frSolidInfill, true).height() * + target_flow_height_factor; + Polygons deep_infill_area = gather_areas_w_depth(po, lidx, target_flow_height); { // Now also remove area that has been already filled on lower layers by bridging expansion - For this From c2baa9ad4febcf0a7deccf227f306548b8eb4a51 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 6 Apr 2023 13:30:16 +0200 Subject: [PATCH 027/103] PlaceholderParser: Throw if valid or invalid UTF-8 sequence is encountered inside a code block. --- src/libslic3r/PlaceholderParser.cpp | 89 ++++++++++++++------- tests/libslic3r/test_placeholder_parser.cpp | 19 +++++ 2 files changed, 81 insertions(+), 27 deletions(-) diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index 44b9d1ee9..1dbf4120e 100644 --- a/src/libslic3r/PlaceholderParser.cpp +++ b/src/libslic3r/PlaceholderParser.cpp @@ -1706,7 +1706,7 @@ namespace client // This parser is to be used inside a raw[] directive to accept a single valid UTF-8 character. // If an invalid UTF-8 sequence is encountered, a qi::expectation_failure is thrown. - struct utf8_char_skipper_parser : qi::primitive_parser + struct utf8_char_parser : qi::primitive_parser { // Define the attribute type exposed by this parser component template @@ -1715,9 +1715,10 @@ namespace client typedef wchar_t type; }; - // This function is called during the actual parsing process + // This function is called during the actual parsing process to skip whitespaces. + // Also it throws if it encounters valid or invalid UTF-8 sequence. template - bool parse(Iterator& first, Iterator const& last, Context& context, Skipper const& skipper, Attribute& attr) const + bool parse(Iterator &first, Iterator const &last, Context &context, Skipper const &skipper, Attribute& attr) const { // The skipper shall always be empty, any white space will be accepted. // skip_over(first, last, skipper); @@ -1767,6 +1768,38 @@ namespace client } }; + // This parser is to be used inside a raw[] directive to accept a single valid UTF-8 character. + // If an invalid UTF-8 sequence is encountered, a qi::expectation_failure is thrown. + struct ascii_char_skipper_parser : public utf8_char_parser + { + // This function is called during the actual parsing process + template + bool parse(Iterator &first, Iterator const &last, Context &context, Skipper const &skipper, Attribute &attr) const + { + Iterator it = first; + // Let the UTF-8 parser throw if it encounters an invalid UTF-8 sequence. + if (! utf8_char_parser::parse(it, last, context, skipper, attr)) + return false; + char c = *first; + if (it - first > 1 || c < 0) + MyContext::throw_exception("Non-ASCII7 characters are only allowed inside text blocks and string literals, not inside code blocks.", IteratorRange(first, it)); + if (c == '\r' || c == '\n' || c == '\t' || c == ' ') { + // Skip the whitespaces + ++ first; + return true; + } else + // Stop skipping, let this 7bit ASCII character be processed. + return false; + } + + // This function is called during error handling to create a human readable string for the error context. + template + spirit::info what(Context&) const + { + return spirit::info("ASCII7_char"); + } + }; + struct FactorActions { static void set_start_pos(Iterator &start_pos, expr &out) { out.it_range = IteratorRange(start_pos, start_pos); } @@ -1853,11 +1886,13 @@ namespace client static void noexpr(expr &out) { out.reset(); } }; + using skipper = ascii_char_skipper_parser; + /////////////////////////////////////////////////////////////////////////// // Our macro_processor grammar /////////////////////////////////////////////////////////////////////////// // Inspired by the C grammar rules https://www.lysator.liu.se/c/ANSI-C-grammar-y.html - struct macro_processor : qi::grammar, spirit_encoding::space_type> + struct macro_processor : qi::grammar, skipper> { macro_processor() : macro_processor::base_type(start) { @@ -1871,7 +1906,7 @@ namespace client qi::no_skip_type no_skip; qi::real_parser strict_double; spirit_encoding::char_type char_; - utf8_char_skipper_parser utf8char; + utf8_char_parser utf8char; spirit::bool_type bool_; spirit::int_type int_; spirit::double_type double_; @@ -2211,22 +2246,22 @@ namespace client } // Generic expression over expr. - typedef qi::rule RuleExpression; + typedef qi::rule RuleExpression; // The start of the grammar. - qi::rule, spirit_encoding::space_type> start; + qi::rule, skipper> start; // A free-form text. - qi::rule text; + qi::rule text; // A free-form text, possibly empty, possibly containing macro expansions. - qi::rule text_block; + qi::rule text_block; // Statements enclosed in curely braces {} - qi::rule block, statement, macros, if_text_block, if_macros, else_macros; + qi::rule block, statement, macros, if_text_block, if_macros, else_macros; // Legacy variable expansion of the original Slic3r, in the form of [scalar_variable] or [vector_variable_index]. - qi::rule legacy_variable_expansion; + qi::rule legacy_variable_expansion; // Parsed identifier name. - qi::rule identifier; + qi::rule identifier; // Ternary operator (?:) over logical_or_expression. - qi::rule, spirit_encoding::space_type> conditional_expression; + qi::rule, skipper> conditional_expression; // Logical or over logical_and_expressions. RuleExpression logical_or_expression; // Logical and over relational_expressions. @@ -2244,27 +2279,27 @@ namespace client // Accepting an optional parameter. RuleExpression optional_parameter; // Rule to capture a regular expression enclosed in //. - qi::rule regular_expression; + qi::rule regular_expression; // Evaluate boolean expression into bool. - qi::rule bool_expr_eval; + qi::rule bool_expr_eval; // Reference of a scalar variable, or reference to a field of a vector variable. - qi::rule, spirit_encoding::space_type> variable_reference; + qi::rule, skipper> variable_reference; // Rule to translate an identifier to a ConfigOption, or to fail. - qi::rule variable; + qi::rule variable; // Evaluating whether a nullable variable is nil. - qi::rule is_nil_test; + qi::rule is_nil_test; // Evaluating "one of" list of patterns. - qi::rule, spirit_encoding::space_type> one_of; - qi::rule one_of_list; + qi::rule, skipper> one_of; + qi::rule one_of_list; // Evaluating the "interpolate_table" expression. - qi::rule, spirit_encoding::space_type> interpolate_table; - qi::rule interpolate_table_list; + qi::rule, skipper> interpolate_table; + qi::rule interpolate_table_list; - qi::rule, spirit_encoding::space_type> if_else_output; - qi::rule, spirit_encoding::space_type> assignment_statement; + qi::rule, skipper> if_else_output; + qi::rule, skipper> assignment_statement; // Allocating new local or global variables. - qi::rule, spirit_encoding::space_type> new_variable_statement; - qi::rule(const MyContext*), spirit_encoding::space_type> initializer_list; + qi::rule, skipper> new_variable_statement; + qi::rule(const MyContext*), skipper> initializer_list; qi::symbols keywords; }; @@ -2275,7 +2310,7 @@ static const client::macro_processor g_macro_processor_instance; static std::string process_macro(const std::string &templ, client::MyContext &context) { std::string output; - phrase_parse(templ.begin(), templ.end(), g_macro_processor_instance(&context), spirit_encoding::space_type{}, output); + phrase_parse(templ.begin(), templ.end(), g_macro_processor_instance(&context), client::skipper{}, output); if (! context.error_message.empty()) { if (context.error_message.back() != '\n' && context.error_message.back() != '\r') context.error_message += '\n'; diff --git a/tests/libslic3r/test_placeholder_parser.cpp b/tests/libslic3r/test_placeholder_parser.cpp index e6fe5bfe2..9a1405f02 100644 --- a/tests/libslic3r/test_placeholder_parser.cpp +++ b/tests/libslic3r/test_placeholder_parser.cpp @@ -46,6 +46,25 @@ SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") { SECTION("parsing string with escaped characters") { REQUIRE(parser.process("{\"hu\\nha\\\\\\\"ha\\\"\"}") == "hu\nha\\\"ha\""); } + WHEN("An UTF-8 character is used inside the code block") { + THEN("A std::runtime_error exception is thrown.") { + // full-width plus sign instead of plain + + REQUIRE_THROWS_AS(parser.process("{1\xEF\xBC\x8B 3}"), std::runtime_error); + } + } + WHEN("An UTF-8 character is used inside a string") { + THEN("UTF-8 sequence is processed correctly when quoted") { + // japanese "cool" or "stylish" + REQUIRE(parser.process("{1+\"\xE3\x81\x8B\xE3\x81\xA3\xE3\x81\x93\xE3\x81\x84\xE3\x81\x84\"+\" \"+3}") == "1\xE3\x81\x8B\xE3\x81\xA3\xE3\x81\x93\xE3\x81\x84\xE3\x81\x84 3"); + } + } + WHEN("An UTF-8 character is used inside a string") { + THEN("UTF-8 sequence is processed correctly outside of code blocks") { + // japanese "cool" or "stylish" + REQUIRE(parser.process("{1+3}\xE3\x81\x8B\xE3\x81\xA3\xE3\x81\x93\xE3\x81\x84\xE3\x81\x84") == "4\xE3\x81\x8B\xE3\x81\xA3\xE3\x81\x93\xE3\x81\x84\xE3\x81\x84"); + } + } + // Test the math expressions. SECTION("math: 2*3") { REQUIRE(parser.process("{2*3}") == "6"); } SECTION("math: 2*3/6") { REQUIRE(parser.process("{2*3/6}") == "1"); } From 32c05aa47a84076ce533c754d22ffdaa7ea0b801 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Thu, 6 Apr 2023 14:54:27 +0200 Subject: [PATCH 028/103] Fix bridging over infill - After wrong flow has been fixed, another problem appeared with small layer heights -the bridging layer was not generated --- src/libslic3r/PrintObject.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 86355fac2..9a47325ef 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1741,7 +1741,7 @@ void PrintObject::bridge_over_infill() // cluster layers by depth needed for thick bridges. Each cluster is to be processed by single thread sequentially, so that bridges cannot appear one on another std::vector> clustered_layers_for_threads; - float target_flow_height_factor = 0.75; + float target_flow_height_factor = 0.9; { std::vector layers_with_candidates; std::map layer_area_covered_by_candidates; @@ -1800,9 +1800,9 @@ void PrintObject::bridge_over_infill() ExPolygons not_sparse_infill{}; double bottom_z = po->get_layer(lidx)->print_z - target_flow_height * target_flow_height_factor - EPSILON; for (int i = int(lidx) - 1; i >= 0; --i) { - // Stop iterating if layer is lower than bottom_z. + // Stop iterating if layer is lower than bottom_z and at least one iteration was made const Layer *layer = po->get_layer(i); - if (layer->print_z < bottom_z) + if (layer->print_z < bottom_z && i < int(lidx) - 1) break; for (const LayerRegion *region : layer->regions()) { From 68d0f2da2bca190f85078c471348e037d4df9f9c Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Thu, 6 Apr 2023 15:54:16 +0200 Subject: [PATCH 029/103] Improve bridge over infill filtering, fix tests --- src/libslic3r/PrintObject.cpp | 11 +++++++++-- tests/fff_print/test_perimeters.cpp | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 9a47325ef..0b0610647 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -2147,20 +2147,27 @@ void PrintObject::bridge_over_infill() expansion_area = closing(expansion_area, SCALED_EPSILON); expansion_area = intersection(expansion_area, deep_infill_area); Polylines anchors = intersection_pl(infill_lines[lidx - 1], shrink(expansion_area, spacing)); + Polygons internal_unsupported_area = shrink(deep_infill_area, spacing * 4.5); #ifdef DEBUG_BRIDGE_OVER_INFILL debug_draw(std::to_string(lidx) + "_" + std::to_string(cluster_idx) + "_" + std::to_string(job_idx) + "_" + "_total_area", to_lines(total_fill_area), to_lines(expansion_area), to_lines(deep_infill_area), to_lines(anchors)); #endif - std::vector expanded_surfaces; expanded_surfaces.reserve(surfaces_by_layer[lidx].size()); for (const CandidateSurface &candidate : surfaces_by_layer[lidx]) { const Flow &flow = candidate.region->bridging_flow(frSolidInfill, true); Polygons area_to_be_bridge = expand(candidate.new_polys, flow.scaled_spacing()); area_to_be_bridge = intersection(area_to_be_bridge, deep_infill_area); - Polygons limiting_area = union_(area_to_be_bridge, expansion_area); + + area_to_be_bridge.erase(std::remove_if(area_to_be_bridge.begin(), area_to_be_bridge.end(), + [internal_unsupported_area](const Polygon &p) { + return intersection({p}, internal_unsupported_area).empty(); + }), + area_to_be_bridge.end()); + + Polygons limiting_area = union_(area_to_be_bridge, expansion_area); if (area_to_be_bridge.empty()) continue; diff --git a/tests/fff_print/test_perimeters.cpp b/tests/fff_print/test_perimeters.cpp index 99f0ddb99..4fa344d86 100644 --- a/tests/fff_print/test_perimeters.cpp +++ b/tests/fff_print/test_perimeters.cpp @@ -529,7 +529,7 @@ SCENARIO("Perimeters3", "[Perimeters]") auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ { "skirts", 0 }, { "perimeters", 3 }, - { "layer_height", 0.4 }, + { "layer_height", 0.15 }, { "bridge_speed", 99 }, { "enable_dynamic_overhang_speeds", false }, // to prevent bridging over sparse infill From 2b85615b31d24a81e60d3bc46fe6cd4afb75ee3c Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Thu, 6 Apr 2023 16:20:56 +0200 Subject: [PATCH 030/103] Fix issue https://github.com/prusa3d/PrusaSlicer/issues/10269 Dynamic overhang speed could sometimes wrongly apply volumtric limits Also, setting speed to zero will now cause the algorithm to use base external perim speed. --- src/libslic3r/GCode.cpp | 13 ++++++++++--- src/libslic3r/GCode/ExtrusionProcessor.hpp | 1 + 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index e82433d47..b0fdcc015 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -3020,9 +3020,16 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de {100, ConfigOptionInts{0}}}; } - double external_perim_reference_speed = std::min(m_config.get_abs_value("external_perimeter_speed"), - std::min(EXTRUDER_CONFIG(filament_max_volumetric_speed) / path.mm3_per_mm, - m_config.max_volumetric_speed.value / path.mm3_per_mm)); + double external_perim_reference_speed = m_config.get_abs_value("external_perimeter_speed"); + if (external_perim_reference_speed == 0) + external_perim_reference_speed = m_volumetric_speed / path.mm3_per_mm; + if (m_config.max_volumetric_speed.value > 0) + external_perim_reference_speed = std::min(external_perim_reference_speed, m_config.max_volumetric_speed.value / path.mm3_per_mm); + if (EXTRUDER_CONFIG(filament_max_volumetric_speed) > 0) { + external_perim_reference_speed = std::min(external_perim_reference_speed, + EXTRUDER_CONFIG(filament_max_volumetric_speed) / path.mm3_per_mm); + } + new_points = m_extrusion_quality_estimator.estimate_extrusion_quality(path, overhangs_with_speeds, overhang_w_fan_speeds, m_writer.extruder()->id(), external_perim_reference_speed, speed); diff --git a/src/libslic3r/GCode/ExtrusionProcessor.hpp b/src/libslic3r/GCode/ExtrusionProcessor.hpp index 1b1748827..625ea695c 100644 --- a/src/libslic3r/GCode/ExtrusionProcessor.hpp +++ b/src/libslic3r/GCode/ExtrusionProcessor.hpp @@ -272,6 +272,7 @@ public: float distance = path.width * (1.0 - (overhangs_w_speeds[i].first / 100.0)); float speed = overhangs_w_speeds[i].second.percent ? (speed_base * overhangs_w_speeds[i].second.value / 100.0) : overhangs_w_speeds[i].second.value; + if (speed < EPSILON) speed = speed_base; speed_sections[distance] = speed; } From 23d74f453eafa90af65cd2bcf82360081fb6d2bd Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 11 Apr 2023 10:21:10 +0200 Subject: [PATCH 031/103] Partial fix of SPE-1605 / GH #9892 Organic supports: bad function call without any obvious reason --- src/libslic3r/TreeModelVolumes.cpp | 2 +- src/libslic3r/TreeModelVolumes.hpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/TreeModelVolumes.cpp b/src/libslic3r/TreeModelVolumes.cpp index 399685e9c..e14ec5a69 100644 --- a/src/libslic3r/TreeModelVolumes.cpp +++ b/src/libslic3r/TreeModelVolumes.cpp @@ -359,7 +359,7 @@ const Polygons& TreeModelVolumes::getCollision(const coord_t orig_radius, LayerI BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate collision at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; tree_supports_show_error("Not precalculated Collision requested."sv, false); } - const_cast(this)->calculateCollision(radius, layer_idx, {}); + const_cast(this)->calculateCollision(radius, layer_idx, []{}); return getCollision(orig_radius, layer_idx, min_xy_dist); } diff --git a/src/libslic3r/TreeModelVolumes.hpp b/src/libslic3r/TreeModelVolumes.hpp index f5b5669bb..139b12328 100644 --- a/src/libslic3r/TreeModelVolumes.hpp +++ b/src/libslic3r/TreeModelVolumes.hpp @@ -512,7 +512,7 @@ private: */ void calculateCollisionHolefree(RadiusLayerPair key) { - calculateCollisionHolefree(std::vector{ RadiusLayerPair(key) }, {}); + calculateCollisionHolefree(std::vector{ RadiusLayerPair(key) }, []{}); } /*! @@ -533,7 +533,7 @@ private: */ void calculateAvoidance(RadiusLayerPair key, bool to_build_plate, bool to_model) { - calculateAvoidance(std::vector{ RadiusLayerPair(key) }, to_build_plate, to_model, {}); + calculateAvoidance(std::vector{ RadiusLayerPair(key) }, to_build_plate, to_model, []{}); } /*! @@ -567,7 +567,7 @@ private: */ void calculateWallRestrictions(RadiusLayerPair key) { - calculateWallRestrictions(std::vector{ RadiusLayerPair(key) }, {}); + calculateWallRestrictions(std::vector{ RadiusLayerPair(key) }, []{}); } /*! From 5e5af56635ca5d8fefbd054dfdadb9d15399b84c Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 11 Apr 2023 10:21:55 +0200 Subject: [PATCH 032/103] Tiny optimization in RegionExpansion wave_seeds() for empty input --- src/libslic3r/Algorithm/RegionExpansion.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Algorithm/RegionExpansion.cpp b/src/libslic3r/Algorithm/RegionExpansion.cpp index 844cda822..e36f5e62c 100644 --- a/src/libslic3r/Algorithm/RegionExpansion.cpp +++ b/src/libslic3r/Algorithm/RegionExpansion.cpp @@ -206,7 +206,7 @@ std::vector wave_seeds( { assert(tiny_expansion > 0); - if (src.empty()) + if (src.empty() || boundary.empty()) return {}; using Intersection = ClipperZUtils::ClipperZIntersectionVisitor::Intersection; From cc5f472caac73601db66df9192e93370a0c8b209 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 11 Apr 2023 10:23:09 +0200 Subject: [PATCH 033/103] FIXME Comment on O(n^2) complexity of compute_intersections() --- src/libslic3r/IntersectionPoints.cpp | 1 + src/libslic3r/IntersectionPoints.hpp | 1 + 2 files changed, 2 insertions(+) diff --git a/src/libslic3r/IntersectionPoints.cpp b/src/libslic3r/IntersectionPoints.cpp index 3537e74ab..78d192397 100644 --- a/src/libslic3r/IntersectionPoints.cpp +++ b/src/libslic3r/IntersectionPoints.cpp @@ -106,6 +106,7 @@ Slic3r::Pointfs Slic3r::intersection_points(const ExPolygons &expolygons) #include namespace priv { +//FIXME O(n^2) complexity! Slic3r::Pointfs compute_intersections(const Slic3r::Lines &lines) { using namespace Slic3r; diff --git a/src/libslic3r/IntersectionPoints.hpp b/src/libslic3r/IntersectionPoints.hpp index 2b1d3aa73..7603765d9 100644 --- a/src/libslic3r/IntersectionPoints.hpp +++ b/src/libslic3r/IntersectionPoints.hpp @@ -6,6 +6,7 @@ namespace Slic3r { // collect all intersecting points +//FIXME O(n^2) complexity! Pointfs intersection_points(const Lines &lines); Pointfs intersection_points(const Polygon &polygon); Pointfs intersection_points(const Polygons &polygons); From 69a842f5708a63689ed311fb8a103dc9989a92b9 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 11 Apr 2023 10:24:24 +0200 Subject: [PATCH 034/103] Bundled ankerl/unordered_dense.h --- src/ankerl/README.txt | 7 + src/ankerl/unordered_dense.h | 1584 ++++++++++++++++++++++++++++++++++ 2 files changed, 1591 insertions(+) create mode 100644 src/ankerl/README.txt create mode 100644 src/ankerl/unordered_dense.h diff --git a/src/ankerl/README.txt b/src/ankerl/README.txt new file mode 100644 index 000000000..0996bc28c --- /dev/null +++ b/src/ankerl/README.txt @@ -0,0 +1,7 @@ +THIS DIRECTORY CONTAINS PIECES OF THE +ankerl::unordered_dense::{map, set} +https://github.com/martinus/unordered_dense +unordered_dense 3.1.1 10782bfc651c2bb75b11bf90491f50da122e5432 +SOURCE DISTRIBUTION. + +THIS IS NOT THE COMPLETE unordered_dense DISTRIBUTION. ONLY FILES NEEDED FOR COMPILING PRUSASLICER WERE PUT INTO THE PRUSASLICER SOURCE DISTRIBUTION. diff --git a/src/ankerl/unordered_dense.h b/src/ankerl/unordered_dense.h new file mode 100644 index 000000000..e294bdb4e --- /dev/null +++ b/src/ankerl/unordered_dense.h @@ -0,0 +1,1584 @@ +///////////////////////// ankerl::unordered_dense::{map, set} ///////////////////////// + +// A fast & densely stored hashmap and hashset based on robin-hood backward shift deletion. +// Version 3.1.1 +// https://github.com/martinus/unordered_dense +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2022-2023 Martin Leitner-Ankerl +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef ANKERL_UNORDERED_DENSE_H +#define ANKERL_UNORDERED_DENSE_H + +// see https://semver.org/spec/v2.0.0.html +#define ANKERL_UNORDERED_DENSE_VERSION_MAJOR 3 // NOLINT(cppcoreguidelines-macro-usage) incompatible API changes +#define ANKERL_UNORDERED_DENSE_VERSION_MINOR 1 // NOLINT(cppcoreguidelines-macro-usage) backwards compatible functionality +#define ANKERL_UNORDERED_DENSE_VERSION_PATCH 1 // NOLINT(cppcoreguidelines-macro-usage) backwards compatible bug fixes + +// API versioning with inline namespace, see https://www.foonathan.net/2018/11/inline-namespaces/ +#define ANKERL_UNORDERED_DENSE_VERSION_CONCAT1(major, minor, patch) v##major##_##minor##_##patch +#define ANKERL_UNORDERED_DENSE_VERSION_CONCAT(major, minor, patch) ANKERL_UNORDERED_DENSE_VERSION_CONCAT1(major, minor, patch) +#define ANKERL_UNORDERED_DENSE_NAMESPACE \ + ANKERL_UNORDERED_DENSE_VERSION_CONCAT( \ + ANKERL_UNORDERED_DENSE_VERSION_MAJOR, ANKERL_UNORDERED_DENSE_VERSION_MINOR, ANKERL_UNORDERED_DENSE_VERSION_PATCH) + +#if defined(_MSVC_LANG) +# define ANKERL_UNORDERED_DENSE_CPP_VERSION _MSVC_LANG +#else +# define ANKERL_UNORDERED_DENSE_CPP_VERSION __cplusplus +#endif + +#if defined(__GNUC__) +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +# define ANKERL_UNORDERED_DENSE_PACK(decl) decl __attribute__((__packed__)) +#elif defined(_MSC_VER) +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +# define ANKERL_UNORDERED_DENSE_PACK(decl) __pragma(pack(push, 1)) decl __pragma(pack(pop)) +#endif + +// exceptions +#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND) +# define ANKERL_UNORDERED_DENSE_HAS_EXCEPTIONS() 1 +#else +# define ANKERL_UNORDERED_DENSE_HAS_EXCEPTIONS() 0 +#endif +#ifdef _MSC_VER +# define ANKERL_UNORDERED_DENSE_NOINLINE __declspec(noinline) +#else +# define ANKERL_UNORDERED_DENSE_NOINLINE __attribute__((noinline)) +#endif + +#if ANKERL_UNORDERED_DENSE_CPP_VERSION < 201703L +# error ankerl::unordered_dense requires C++17 or higher +#else +# include // for array +# include // for uint64_t, uint32_t, uint8_t, UINT64_C +# include // for size_t, memcpy, memset +# include // for equal_to, hash +# include // for initializer_list +# include // for pair, distance +# include // for numeric_limits +# include // for allocator, allocator_traits, shared_ptr +# include // for out_of_range +# include // for basic_string +# include // for basic_string_view, hash +# include // for forward_as_tuple +# include // for enable_if_t, declval, conditional_t, ena... +# include // for forward, exchange, pair, as_const, piece... +# include // for vector +# if ANKERL_UNORDERED_DENSE_HAS_EXCEPTIONS() == 0 +# include // for abort +# endif + +# define ANKERL_UNORDERED_DENSE_PMR 0 // NOLINT(cppcoreguidelines-macro-usage) +# if defined(__has_include) +# if __has_include() +# undef ANKERL_UNORDERED_DENSE_PMR +# define ANKERL_UNORDERED_DENSE_PMR 1 // NOLINT(cppcoreguidelines-macro-usage) +# define ANKERL_UNORDERED_DENSE_PMR_ALLOCATOR \ + std::pmr::polymorphic_allocator // NOLINT(cppcoreguidelines-macro-usage) +# include // for polymorphic_allocator +# elif __has_include() +# undef ANKERL_UNORDERED_DENSE_PMR +# define ANKERL_UNORDERED_DENSE_PMR 1 // NOLINT(cppcoreguidelines-macro-usage) +# define ANKERL_UNORDERED_DENSE_PMR_ALLOCATOR \ + std::experimental::pmr::polymorphic_allocator // NOLINT(cppcoreguidelines-macro-usage) +# include // for polymorphic_allocator +# endif +# endif + +# if defined(_MSC_VER) && defined(_M_X64) +# include +# pragma intrinsic(_umul128) +# endif + +# if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__) +# define ANKERL_UNORDERED_DENSE_LIKELY(x) __builtin_expect(x, 1) // NOLINT(cppcoreguidelines-macro-usage) +# define ANKERL_UNORDERED_DENSE_UNLIKELY(x) __builtin_expect(x, 0) // NOLINT(cppcoreguidelines-macro-usage) +# else +# define ANKERL_UNORDERED_DENSE_LIKELY(x) (x) // NOLINT(cppcoreguidelines-macro-usage) +# define ANKERL_UNORDERED_DENSE_UNLIKELY(x) (x) // NOLINT(cppcoreguidelines-macro-usage) +# endif + +namespace ankerl::unordered_dense { +inline namespace ANKERL_UNORDERED_DENSE_NAMESPACE { + +namespace detail { + +# if ANKERL_UNORDERED_DENSE_HAS_EXCEPTIONS() + +// make sure this is not inlined as it is slow and dramatically enlarges code, thus making other +// inlinings more difficult. Throws are also generally the slow path. +[[noreturn]] inline ANKERL_UNORDERED_DENSE_NOINLINE void on_error_key_not_found() { + throw std::out_of_range("ankerl::unordered_dense::map::at(): key not found"); +} +[[noreturn]] inline ANKERL_UNORDERED_DENSE_NOINLINE void on_error_bucket_overflow() { + throw std::overflow_error("ankerl::unordered_dense: reached max bucket size, cannot increase size"); +} +[[noreturn]] inline ANKERL_UNORDERED_DENSE_NOINLINE void on_error_too_many_elements() { + throw std::out_of_range("ankerl::unordered_dense::map::replace(): too many elements"); +} + +# else + +[[noreturn]] inline void on_error_key_not_found() { + abort(); +} +[[noreturn]] inline void on_error_bucket_overflow() { + abort(); +} +[[noreturn]] inline void on_error_too_many_elements() { + abort(); +} + +# endif + +} // namespace detail + +// hash /////////////////////////////////////////////////////////////////////// + +// This is a stripped-down implementation of wyhash: https://github.com/wangyi-fudan/wyhash +// No big-endian support (because different values on different machines don't matter), +// hardcodes seed and the secret, reformattes the code, and clang-tidy fixes. +namespace detail::wyhash { + +static inline void mum(uint64_t* a, uint64_t* b) { +# if defined(__SIZEOF_INT128__) + __uint128_t r = *a; + r *= *b; + *a = static_cast(r); + *b = static_cast(r >> 64U); +# elif defined(_MSC_VER) && defined(_M_X64) + *a = _umul128(*a, *b, b); +# else + uint64_t ha = *a >> 32U; + uint64_t hb = *b >> 32U; + uint64_t la = static_cast(*a); + uint64_t lb = static_cast(*b); + uint64_t hi{}; + uint64_t lo{}; + uint64_t rh = ha * hb; + uint64_t rm0 = ha * lb; + uint64_t rm1 = hb * la; + uint64_t rl = la * lb; + uint64_t t = rl + (rm0 << 32U); + auto c = static_cast(t < rl); + lo = t + (rm1 << 32U); + c += static_cast(lo < t); + hi = rh + (rm0 >> 32U) + (rm1 >> 32U) + c; + *a = lo; + *b = hi; +# endif +} + +// multiply and xor mix function, aka MUM +[[nodiscard]] static inline auto mix(uint64_t a, uint64_t b) -> uint64_t { + mum(&a, &b); + return a ^ b; +} + +// read functions. WARNING: we don't care about endianness, so results are different on big endian! +[[nodiscard]] static inline auto r8(const uint8_t* p) -> uint64_t { + uint64_t v{}; + std::memcpy(&v, p, 8U); + return v; +} + +[[nodiscard]] static inline auto r4(const uint8_t* p) -> uint64_t { + uint32_t v{}; + std::memcpy(&v, p, 4); + return v; +} + +// reads 1, 2, or 3 bytes +[[nodiscard]] static inline auto r3(const uint8_t* p, size_t k) -> uint64_t { + return (static_cast(p[0]) << 16U) | (static_cast(p[k >> 1U]) << 8U) | p[k - 1]; +} + +[[maybe_unused]] [[nodiscard]] static inline auto hash(void const* key, size_t len) -> uint64_t { + static constexpr auto secret = std::array{UINT64_C(0xa0761d6478bd642f), + UINT64_C(0xe7037ed1a0b428db), + UINT64_C(0x8ebc6af09c88c6e3), + UINT64_C(0x589965cc75374cc3)}; + + auto const* p = static_cast(key); + uint64_t seed = secret[0]; + uint64_t a{}; + uint64_t b{}; + if (ANKERL_UNORDERED_DENSE_LIKELY(len <= 16)) { + if (ANKERL_UNORDERED_DENSE_LIKELY(len >= 4)) { + a = (r4(p) << 32U) | r4(p + ((len >> 3U) << 2U)); + b = (r4(p + len - 4) << 32U) | r4(p + len - 4 - ((len >> 3U) << 2U)); + } else if (ANKERL_UNORDERED_DENSE_LIKELY(len > 0)) { + a = r3(p, len); + b = 0; + } else { + a = 0; + b = 0; + } + } else { + size_t i = len; + if (ANKERL_UNORDERED_DENSE_UNLIKELY(i > 48)) { + uint64_t see1 = seed; + uint64_t see2 = seed; + do { + seed = mix(r8(p) ^ secret[1], r8(p + 8) ^ seed); + see1 = mix(r8(p + 16) ^ secret[2], r8(p + 24) ^ see1); + see2 = mix(r8(p + 32) ^ secret[3], r8(p + 40) ^ see2); + p += 48; + i -= 48; + } while (ANKERL_UNORDERED_DENSE_LIKELY(i > 48)); + seed ^= see1 ^ see2; + } + while (ANKERL_UNORDERED_DENSE_UNLIKELY(i > 16)) { + seed = mix(r8(p) ^ secret[1], r8(p + 8) ^ seed); + i -= 16; + p += 16; + } + a = r8(p + i - 16); + b = r8(p + i - 8); + } + + return mix(secret[1] ^ len, mix(a ^ secret[1], b ^ seed)); +} + +[[nodiscard]] static inline auto hash(uint64_t x) -> uint64_t { + return detail::wyhash::mix(x, UINT64_C(0x9E3779B97F4A7C15)); +} + +} // namespace detail::wyhash + +template +struct hash { + auto operator()(T const& obj) const noexcept(noexcept(std::declval>().operator()(std::declval()))) + -> uint64_t { + return std::hash{}(obj); + } +}; + +template +struct hash> { + using is_avalanching = void; + auto operator()(std::basic_string const& str) const noexcept -> uint64_t { + return detail::wyhash::hash(str.data(), sizeof(CharT) * str.size()); + } +}; + +template +struct hash> { + using is_avalanching = void; + auto operator()(std::basic_string_view const& sv) const noexcept -> uint64_t { + return detail::wyhash::hash(sv.data(), sizeof(CharT) * sv.size()); + } +}; + +template +struct hash { + using is_avalanching = void; + auto operator()(T* ptr) const noexcept -> uint64_t { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + return detail::wyhash::hash(reinterpret_cast(ptr)); + } +}; + +template +struct hash> { + using is_avalanching = void; + auto operator()(std::unique_ptr const& ptr) const noexcept -> uint64_t { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + return detail::wyhash::hash(reinterpret_cast(ptr.get())); + } +}; + +template +struct hash> { + using is_avalanching = void; + auto operator()(std::shared_ptr const& ptr) const noexcept -> uint64_t { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + return detail::wyhash::hash(reinterpret_cast(ptr.get())); + } +}; + +template +struct hash::value>::type> { + using is_avalanching = void; + auto operator()(Enum e) const noexcept -> uint64_t { + using underlying = typename std::underlying_type_t; + return detail::wyhash::hash(static_cast(e)); + } +}; + +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +# define ANKERL_UNORDERED_DENSE_HASH_STATICCAST(T) \ + template <> \ + struct hash { \ + using is_avalanching = void; \ + auto operator()(T const& obj) const noexcept -> uint64_t { \ + return detail::wyhash::hash(static_cast(obj)); \ + } \ + } + +# if defined(__GNUC__) && !defined(__clang__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wuseless-cast" +# endif +// see https://en.cppreference.com/w/cpp/utility/hash +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(bool); +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(char); +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(signed char); +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned char); +# if ANKERL_UNORDERED_DENSE_CPP_VERSION >= 202002L +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(char8_t); +# endif +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(char16_t); +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(char32_t); +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(wchar_t); +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(short); +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned short); +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(int); +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned int); +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(long); +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(long long); +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned long); +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned long long); + +# if defined(__GNUC__) && !defined(__clang__) +# pragma GCC diagnostic pop +# endif + +// bucket_type ////////////////////////////////////////////////////////// + +namespace bucket_type { + +struct standard { + static constexpr uint32_t dist_inc = 1U << 8U; // skip 1 byte fingerprint + static constexpr uint32_t fingerprint_mask = dist_inc - 1; // mask for 1 byte of fingerprint + + uint32_t m_dist_and_fingerprint; // upper 3 byte: distance to original bucket. lower byte: fingerprint from hash + uint32_t m_value_idx; // index into the m_values vector. +}; + +ANKERL_UNORDERED_DENSE_PACK(struct big { + static constexpr uint32_t dist_inc = 1U << 8U; // skip 1 byte fingerprint + static constexpr uint32_t fingerprint_mask = dist_inc - 1; // mask for 1 byte of fingerprint + + uint32_t m_dist_and_fingerprint; // upper 3 byte: distance to original bucket. lower byte: fingerprint from hash + size_t m_value_idx; // index into the m_values vector. +}); + +} // namespace bucket_type + +namespace detail { + +struct nonesuch {}; + +template class Op, class... Args> +struct detector { + using value_t = std::false_type; + using type = Default; +}; + +template class Op, class... Args> +struct detector>, Op, Args...> { + using value_t = std::true_type; + using type = Op; +}; + +template