diff options
author | YuSanka <yusanka@gmail.com> | 2019-03-19 10:52:58 +0300 |
---|---|---|
committer | YuSanka <yusanka@gmail.com> | 2019-03-19 10:52:58 +0300 |
commit | 8be8b604f5f91f7cacc7b4dc9150a34a6f4ca10e (patch) | |
tree | 23a92a43a708ad1c93f9b4c498eb8f830003275c /src/slic3r | |
parent | b382ad1ffbc1bae64734fcc8e9b70befed5c03c2 (diff) | |
parent | 20be57dc582b53014cca5e51b73a585ebdcff8c1 (diff) |
Merge remote-tracking branch 'origin/vb_faster_tabs' into ys_comboboxes
Diffstat (limited to 'src/slic3r')
54 files changed, 4916 insertions, 4405 deletions
diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 157fc9011..219f17082 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -28,8 +28,20 @@ set(SLIC3R_GUI_SOURCES GUI/GLCanvas3D.cpp GUI/GLCanvas3DManager.hpp GUI/GLCanvas3DManager.cpp - GUI/GLGizmo.hpp - GUI/GLGizmo.cpp + GUI/Gizmos/GLGizmoBase.cpp + GUI/Gizmos/GLGizmoBase.hpp + GUI/Gizmos/GLGizmoMove.cpp + GUI/Gizmos/GLGizmoMove.hpp + GUI/Gizmos/GLGizmoRotate.cpp + GUI/Gizmos/GLGizmoRotate.hpp + GUI/Gizmos/GLGizmoScale.cpp + GUI/Gizmos/GLGizmoScale.hpp + GUI/Gizmos/GLGizmoSlaSupports.cpp + GUI/Gizmos/GLGizmoSlaSupports.hpp + GUI/Gizmos/GLGizmoFlatten.cpp + GUI/Gizmos/GLGizmoFlatten.hpp + GUI/Gizmos/GLGizmoCut.cpp + GUI/Gizmos/GLGizmoCut.hpp GUI/GLTexture.hpp GUI/GLTexture.cpp GUI/GLToolbar.hpp @@ -76,6 +88,8 @@ set(SLIC3R_GUI_SOURCES GUI/2DBed.hpp GUI/3DBed.cpp GUI/3DBed.hpp + GUI/Camera.cpp + GUI/Camera.hpp GUI/wxExtensions.cpp GUI/wxExtensions.hpp GUI/WipeTowerDialog.cpp diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 88815d9a6..deb2bb08d 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -834,6 +834,8 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M ModelInstance::EPrintVolumeState state = ModelInstance::PVS_Inside; bool all_contained = true; + bool contained_min_one = false; + for (GLVolume* volume : this->volumes) { if ((volume == nullptr) || volume->is_modifier || (volume->is_wipe_tower && !volume->shader_outside_printer_detection_enabled) || ((volume->composite_id.volume_id < 0) && !volume->shader_outside_printer_detection_enabled)) @@ -843,6 +845,9 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M bool contained = print_volume.contains(bb); all_contained &= contained; + if (contained) + contained_min_one = true; + volume->is_outside = !contained; if ((state == ModelInstance::PVS_Inside) && volume->is_outside) @@ -855,7 +860,7 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M if (out_state != nullptr) *out_state = state; - return all_contained; + return /*all_contained*/ contained_min_one; // #ys_FIXME_delete_after_testing } void GLVolumeCollection::reset_outside_state() @@ -2002,9 +2007,9 @@ std::string _3DScene::get_gl_info(bool format_as_html, bool extensions) return s_canvas_mgr.get_gl_info(format_as_html, extensions); } -bool _3DScene::add_canvas(wxGLCanvas* canvas) +bool _3DScene::add_canvas(wxGLCanvas* canvas, GUI::Bed3D& bed, GUI::Camera& camera, GUI::GLToolbar& view_toolbar) { - return s_canvas_mgr.add(canvas); + return s_canvas_mgr.add(canvas, bed, camera, view_toolbar); } bool _3DScene::remove_canvas(wxGLCanvas* canvas) diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 46cb5b870..15ac6a3a1 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -25,6 +25,11 @@ inline void glAssertRecentCall() { } #endif namespace Slic3r { +namespace GUI { +class Bed3D; +struct Camera; +class GLToolbar; +} // namespace GUI class Print; class PrintObject; @@ -359,7 +364,7 @@ public: void set_volume_rotation(const Vec3d& rotation) { m_volume_transformation.set_rotation(rotation); set_bounding_boxes_as_dirty(); } void set_volume_rotation(Axis axis, double rotation) { m_volume_transformation.set_rotation(axis, rotation); set_bounding_boxes_as_dirty(); } - Vec3d get_volume_scaling_factor() const { return m_volume_transformation.get_scaling_factor(); } + const Vec3d& get_volume_scaling_factor() const { return m_volume_transformation.get_scaling_factor(); } double get_volume_scaling_factor(Axis axis) const { return m_volume_transformation.get_scaling_factor(axis); } void set_volume_scaling_factor(const Vec3d& scaling_factor) { m_volume_transformation.set_scaling_factor(scaling_factor); set_bounding_boxes_as_dirty(); } @@ -563,7 +568,7 @@ class _3DScene public: static std::string get_gl_info(bool format_as_html, bool extensions); - static bool add_canvas(wxGLCanvas* canvas); + static bool add_canvas(wxGLCanvas* canvas, GUI::Bed3D& bed, GUI::Camera& camera, GUI::GLToolbar& view_toolbar); static bool remove_canvas(wxGLCanvas* canvas); static void remove_all_canvases(); diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp index a2299e7bf..9ea20163d 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp @@ -123,7 +123,7 @@ public: // This "finished" flag does not account for the final export of the output file (.gcode or zipped PNGs), // and it does not account for the OctoPrint scheduling. bool finished() const { return m_print->finished(); } - + private: void thread_proc(); void thread_proc_safe(); diff --git a/src/slic3r/GUI/BonjourDialog.cpp b/src/slic3r/GUI/BonjourDialog.cpp index 68e353ebe..ec6b2f0c9 100644 --- a/src/slic3r/GUI/BonjourDialog.cpp +++ b/src/slic3r/GUI/BonjourDialog.cpp @@ -10,8 +10,10 @@ #include <wx/listctrl.h> #include <wx/stattext.h> #include <wx/timer.h> +#include <wx/wupdlock.h> #include "slic3r/GUI/GUI.hpp" +#include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/I18N.hpp" #include "slic3r/Utils/Bonjour.hpp" @@ -49,31 +51,36 @@ struct LifetimeGuard LifetimeGuard(BonjourDialog *dialog) : dialog(dialog) {} }; - -BonjourDialog::BonjourDialog(wxWindow *parent) : - wxDialog(parent, wxID_ANY, _(L("Network lookup"))), - list(new wxListView(this, wxID_ANY, wxDefaultPosition, wxSize(800, 300))), - replies(new ReplySet), - label(new wxStaticText(this, wxID_ANY, "")), - timer(new wxTimer()), - timer_state(0) +BonjourDialog::BonjourDialog(wxWindow *parent, Slic3r::PrinterTechnology tech) + : wxDialog(parent, wxID_ANY, _(L("Network lookup")), wxDefaultPosition, wxDefaultSize, wxRESIZE_BORDER) + , list(new wxListView(this, wxID_ANY)) + , replies(new ReplySet) + , label(new wxStaticText(this, wxID_ANY, "")) + , timer(new wxTimer()) + , timer_state(0) + , tech(tech) { + const int em = GUI::wxGetApp().em_unit(); + list->SetMinSize(wxSize(80 * em, 30 * em)); + wxBoxSizer *vsizer = new wxBoxSizer(wxVERTICAL); - vsizer->Add(label, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 10); + vsizer->Add(label, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, em); list->SetSingleStyle(wxLC_SINGLE_SEL); list->SetSingleStyle(wxLC_SORT_DESCENDING); - list->AppendColumn(_(L("Address")), wxLIST_FORMAT_LEFT, 50); - list->AppendColumn(_(L("Hostname")), wxLIST_FORMAT_LEFT, 100); - list->AppendColumn(_(L("Service name")), wxLIST_FORMAT_LEFT, 200); - list->AppendColumn(_(L("OctoPrint version")), wxLIST_FORMAT_LEFT, 50); + list->AppendColumn(_(L("Address")), wxLIST_FORMAT_LEFT, 5 * em); + list->AppendColumn(_(L("Hostname")), wxLIST_FORMAT_LEFT, 10 * em); + list->AppendColumn(_(L("Service name")), wxLIST_FORMAT_LEFT, 20 * em); + if (tech == ptFFF) { + list->AppendColumn(_(L("OctoPrint version")), wxLIST_FORMAT_LEFT, 5 * em); + } - vsizer->Add(list, 1, wxEXPAND | wxALL, 10); + vsizer->Add(list, 1, wxEXPAND | wxALL, em); wxBoxSizer *button_sizer = new wxBoxSizer(wxHORIZONTAL); - button_sizer->Add(new wxButton(this, wxID_OK, "OK"), 0, wxALL, 10); - button_sizer->Add(new wxButton(this, wxID_CANCEL, "Cancel"), 0, wxALL, 10); + button_sizer->Add(new wxButton(this, wxID_OK, "OK"), 0, wxALL, em); + button_sizer->Add(new wxButton(this, wxID_CANCEL, "Cancel"), 0, wxALL, em); // ^ Note: The Ok/Cancel labels are translated by wxWidgets vsizer->Add(button_sizer, 0, wxALIGN_CENTER); @@ -110,7 +117,11 @@ bool BonjourDialog::show_and_lookup() // so that both threads can access it safely. auto dguard = std::make_shared<LifetimeGuard>(this); + // Note: More can be done here when we support discovery of hosts other than Octoprint and SL1 + Bonjour::TxtKeys txt_keys { "version", "model" }; + bonjour = std::move(Bonjour("octoprint") + .set_txt_keys(std::move(txt_keys)) .set_retries(3) .set_timeout(4) .on_reply([dguard](BonjourReply &&reply) { @@ -157,9 +168,20 @@ void BonjourDialog::on_reply(BonjourReplyEvent &e) return; } + // Filter replies based on selected technology + const auto model = e.reply.txt_data.find("model"); + const bool sl1 = model != e.reply.txt_data.end() && model->second == "SL1"; + if (tech == ptFFF && sl1 || tech == ptSLA && !sl1) { + return; + } + replies->insert(std::move(e.reply)); auto selected = get_selected(); + + wxWindowUpdateLocker freeze_guard(this); + (void)freeze_guard; + list->DeleteAllItems(); // The whole list is recreated so that we benefit from it already being sorted in the set. @@ -168,12 +190,20 @@ void BonjourDialog::on_reply(BonjourReplyEvent &e) auto item = list->InsertItem(0, reply.full_address); list->SetItem(item, 1, reply.hostname); list->SetItem(item, 2, reply.service_name); - list->SetItem(item, 3, reply.version); + + if (tech == ptFFF) { + const auto it = reply.txt_data.find("version"); + if (it != reply.txt_data.end()) { + list->SetItem(item, 3, GUI::from_u8(it->second)); + } + } } - for (int i = 0; i < 4; i++) { - this->list->SetColumnWidth(i, wxLIST_AUTOSIZE); - if (this->list->GetColumnWidth(i) < 100) { this->list->SetColumnWidth(i, 100); } + const int em = GUI::wxGetApp().em_unit(); + + for (int i = 0; i < list->GetColumnCount(); i++) { + list->SetColumnWidth(i, wxLIST_AUTOSIZE); + if (list->GetColumnWidth(i) < 10 * em) { list->SetColumnWidth(i, 10 * em); } } if (!selected.IsEmpty()) { diff --git a/src/slic3r/GUI/BonjourDialog.hpp b/src/slic3r/GUI/BonjourDialog.hpp index e3f53790b..a9a33d522 100644 --- a/src/slic3r/GUI/BonjourDialog.hpp +++ b/src/slic3r/GUI/BonjourDialog.hpp @@ -5,6 +5,8 @@ #include <wx/dialog.h> +#include "libslic3r/PrintConfig.hpp" + class wxListView; class wxStaticText; class wxTimer; @@ -21,7 +23,7 @@ class ReplySet; class BonjourDialog: public wxDialog { public: - BonjourDialog(wxWindow *parent); + BonjourDialog(wxWindow *parent, Slic3r::PrinterTechnology); BonjourDialog(BonjourDialog &&) = delete; BonjourDialog(const BonjourDialog &) = delete; BonjourDialog &operator=(BonjourDialog &&) = delete; @@ -37,6 +39,7 @@ private: std::shared_ptr<Bonjour> bonjour; std::unique_ptr<wxTimer> timer; unsigned timer_state; + Slic3r::PrinterTechnology tech; void on_reply(BonjourReplyEvent &); void on_timer(wxTimerEvent &); diff --git a/src/slic3r/GUI/Camera.cpp b/src/slic3r/GUI/Camera.cpp new file mode 100644 index 000000000..c748efa64 --- /dev/null +++ b/src/slic3r/GUI/Camera.cpp @@ -0,0 +1,62 @@ +#include "libslic3r/libslic3r.h" + +#include "Camera.hpp" + +static const float GIMBALL_LOCK_THETA_MAX = 180.0f; + +namespace Slic3r { +namespace GUI { + +Camera::Camera() + : type(Ortho) + , zoom(1.0f) + , phi(45.0f) +// , distance(0.0f) + , requires_zoom_to_bed(false) + , m_theta(45.0f) + , m_target(Vec3d::Zero()) +{ +} + +std::string Camera::get_type_as_string() const +{ + switch (type) + { + default: + case Unknown: + return "unknown"; +// case Perspective: +// return "perspective"; + case Ortho: + return "ortho"; + }; +} + +void Camera::set_target(const Vec3d& target) +{ + m_target = target; + m_target(0) = clamp(m_scene_box.min(0), m_scene_box.max(0), m_target(0)); + m_target(1) = clamp(m_scene_box.min(1), m_scene_box.max(1), m_target(1)); + m_target(2) = clamp(m_scene_box.min(2), m_scene_box.max(2), m_target(2)); +} + +void Camera::set_theta(float theta, bool apply_limit) +{ + if (apply_limit) + m_theta = clamp(0.0f, GIMBALL_LOCK_THETA_MAX, theta); + else + { + m_theta = fmod(theta, 360.0f); + if (m_theta < 0.0f) + m_theta += 360.0f; + } +} + +void Camera::set_scene_box(const BoundingBoxf3& box) +{ + m_scene_box = box; +} + +} // GUI +} // Slic3r + diff --git a/src/slic3r/GUI/Camera.hpp b/src/slic3r/GUI/Camera.hpp new file mode 100644 index 000000000..d50dc6e4d --- /dev/null +++ b/src/slic3r/GUI/Camera.hpp @@ -0,0 +1,50 @@ +#ifndef slic3r_Camera_hpp_ +#define slic3r_Camera_hpp_ + +#include "libslic3r/BoundingBox.hpp" + +namespace Slic3r { +namespace GUI { + +struct Camera +{ + enum EType : unsigned char + { + Unknown, +// Perspective, + Ortho, + Num_types + }; + + EType type; + float zoom; + float phi; +// float distance; + bool requires_zoom_to_bed; + +private: + Vec3d m_target; + float m_theta; + + BoundingBoxf3 m_scene_box; + +public: + Camera(); + + std::string get_type_as_string() const; + + const Vec3d& get_target() const { return m_target; } + void set_target(const Vec3d& target); + + float get_theta() const { return m_theta; } + void set_theta(float theta, bool apply_limit); + + const BoundingBoxf3& get_scene_box() const { return m_scene_box; } + void set_scene_box(const BoundingBoxf3& box); +}; + +} // GUI +} // Slic3r + +#endif // slic3r_Camera_hpp_ + diff --git a/src/slic3r/GUI/ConfigSnapshotDialog.cpp b/src/slic3r/GUI/ConfigSnapshotDialog.cpp index 422f683b3..205e84f57 100644 --- a/src/slic3r/GUI/ConfigSnapshotDialog.cpp +++ b/src/slic3r/GUI/ConfigSnapshotDialog.cpp @@ -38,7 +38,7 @@ static wxString generate_html_row(const Config::Snapshot &snapshot, bool row_eve text += wxString("<font size=\"5\"><b>") + (snapshot_active ? _(L("Active: ")) : "") + Utils::format_local_date_time(snapshot.time_captured) + ": " + format_reason(snapshot.reason); if (! snapshot.comment.empty()) - text += " (" + snapshot.comment + ")"; + text += " (" + wxString::FromUTF8(snapshot.comment.data()) + ")"; text += "</b></font><br>"; // End of row header. text += _(L("slic3r version")) + ": " + snapshot.slic3r_version_captured.to_string() + "<br>"; diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 7cc533772..217b9380a 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -37,7 +37,9 @@ void Field::PostInitialize() m_Undo_to_sys_btn = new MyButton(m_parent, wxID_ANY, "", wxDefaultPosition,wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); if (wxMSW) { m_Undo_btn->SetBackgroundColour(color); + m_Undo_btn->SetBackgroundStyle(wxBG_STYLE_PAINT); m_Undo_to_sys_btn->SetBackgroundColour(color); + m_Undo_to_sys_btn->SetBackgroundStyle(wxBG_STYLE_PAINT); } m_Undo_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent) { on_back_to_initial_value(); })); m_Undo_to_sys_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent) { on_back_to_sys_value(); })); @@ -258,6 +260,12 @@ void TextCtrl::BUILD() { const long style = m_opt.multiline ? wxTE_MULTILINE : wxTE_PROCESS_ENTER/*0*/; auto temp = new wxTextCtrl(m_parent, wxID_ANY, text_value, wxDefaultPosition, size, style); + temp->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + + if (! m_opt.multiline) + // Only disable background refresh for single line input fields, as they are completely painted over by the edit control. + // This does not apply to the multi-line edit field, where the last line and a narrow frame around the text is not cleared. + temp->SetBackgroundStyle(wxBG_STYLE_PAINT); #ifdef __WXOSX__ temp->OSXDisableAllSmartSubstitutions(); #endif // __WXOSX__ @@ -373,6 +381,8 @@ void CheckBox::BUILD() { false; auto temp = new wxCheckBox(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size); + temp->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + temp->SetBackgroundStyle(wxBG_STYLE_PAINT); temp->SetValue(check_value); if (m_opt.readonly) temp->Disable(); @@ -430,6 +440,8 @@ void SpinCtrl::BUILD() { auto temp = new wxSpinCtrl(m_parent, wxID_ANY, text_value, wxDefaultPosition, size, 0|wxTE_PROCESS_ENTER, min_val, max_val, default_value); + temp->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + temp->SetBackgroundStyle(wxBG_STYLE_PAINT); #ifndef __WXOSX__ // #ys_FIXME_KILL_FOCUS @@ -505,6 +517,8 @@ void Choice::BUILD() { } else temp = new wxBitmapComboBox(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size, 0, nullptr, wxCB_READONLY); + temp->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + temp->SetBackgroundStyle(wxBG_STYLE_PAINT); // recast as a wxWindow to fit the calling convention window = dynamic_cast<wxWindow*>(temp); @@ -784,6 +798,7 @@ void ColourPicker::BUILD() } auto temp = new wxColourPickerCtrl(m_parent, wxID_ANY, clr, wxDefaultPosition, size); + temp->SetBackgroundStyle(wxBG_STYLE_PAINT); // // recast as a wxWindow to fit the calling convention window = dynamic_cast<wxWindow*>(temp); @@ -818,10 +833,21 @@ void PointCtrl::BUILD() x_textctrl = new wxTextCtrl(m_parent, wxID_ANY, X, wxDefaultPosition, field_size, wxTE_PROCESS_ENTER); y_textctrl = new wxTextCtrl(m_parent, wxID_ANY, Y, wxDefaultPosition, field_size, wxTE_PROCESS_ENTER); - - temp->Add(new wxStaticText(m_parent, wxID_ANY, "x : "), 0, wxALIGN_CENTER_VERTICAL, 0); + x_textctrl->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + x_textctrl->SetBackgroundStyle(wxBG_STYLE_PAINT); + y_textctrl->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + y_textctrl->SetBackgroundStyle(wxBG_STYLE_PAINT); + + auto static_text_x = new wxStaticText(m_parent, wxID_ANY, "x : "); + auto static_text_y = new wxStaticText(m_parent, wxID_ANY, " y : "); + static_text_x->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + static_text_x->SetBackgroundStyle(wxBG_STYLE_PAINT); + static_text_y->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + static_text_y->SetBackgroundStyle(wxBG_STYLE_PAINT); + + temp->Add(static_text_x, 0, wxALIGN_CENTER_VERTICAL, 0); temp->Add(x_textctrl); - temp->Add(new wxStaticText(m_parent, wxID_ANY, " y : "), 0, wxALIGN_CENTER_VERTICAL, 0); + temp->Add(static_text_y, 0, wxALIGN_CENTER_VERTICAL, 0); temp->Add(y_textctrl); // x_textctrl->Bind(wxEVT_TEXT, ([this](wxCommandEvent e) { on_change_field(); }), x_textctrl->GetId()); @@ -890,6 +916,8 @@ void StaticText::BUILD() const wxString legend(static_cast<const ConfigOptionString*>(m_opt.default_value)->value); auto temp = new wxStaticText(m_parent, wxID_ANY, legend, wxDefaultPosition, size, wxST_ELLIPSIZE_MIDDLE); + temp->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + temp->SetBackgroundStyle(wxBG_STYLE_PAINT); temp->SetFont(wxGetApp().bold_font()); // // recast as a wxWindow to fit the calling convention @@ -913,10 +941,14 @@ void SliderCtrl::BUILD() m_slider = new wxSlider(m_parent, wxID_ANY, def_val * m_scale, min * m_scale, max * m_scale, wxDefaultPosition, size); + m_slider->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + m_slider->SetBackgroundStyle(wxBG_STYLE_PAINT); wxSize field_size(40, -1); m_textctrl = new wxTextCtrl(m_parent, wxID_ANY, wxString::Format("%d", m_slider->GetValue()/m_scale), wxDefaultPosition, field_size); + m_textctrl->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + m_textctrl->SetBackgroundStyle(wxBG_STYLE_PAINT); temp->Add(m_slider, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL, 0); temp->Add(m_textctrl, 0, wxALIGN_CENTER_VERTICAL, 0); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 8415e8c24..f38e257ac 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1,4 +1,4 @@ -#include "slic3r/GUI/GLGizmo.hpp" +#include "slic3r/GUI/Gizmos/GLGizmos.hpp" #include "GLCanvas3D.hpp" #include "admesh/stl.h" @@ -16,7 +16,6 @@ #include "slic3r/GUI/GLShader.hpp" #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/PresetBundle.hpp" -//#include "slic3r/GUI/GLGizmo.hpp" #include "GUI_App.hpp" #include "GUI_ObjectList.hpp" #include "GUI_ObjectManipulation.hpp" @@ -54,7 +53,6 @@ #include <cmath> static const float TRACKBALLSIZE = 0.8f; -static const float GIMBALL_LOCK_THETA_MAX = 180.0f; static const float GROUND_Z = -0.02f; // phi / theta angles to orient the camera. @@ -79,7 +77,7 @@ static const float DEFAULT_BG_LIGHT_COLOR[3] = { 0.753f, 0.753f, 0.753f }; static const float ERROR_BG_DARK_COLOR[3] = { 0.478f, 0.192f, 0.039f }; static const float ERROR_BG_LIGHT_COLOR[3] = { 0.753f, 0.192f, 0.039f }; static const float UNIFORM_SCALE_COLOR[3] = { 1.0f, 0.38f, 0.0f }; -static const float AXES_COLOR[3][3] = { { 1.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f } }; +//static const float AXES_COLOR[3][3] = { { 1.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f } }; namespace Slic3r { namespace GUI { @@ -183,61 +181,6 @@ void Rect::set_bottom(float bottom) m_bottom = bottom; } -GLCanvas3D::Camera::Camera() - : type(Ortho) - , zoom(1.0f) - , phi(45.0f) -// , distance(0.0f) - , m_theta(45.0f) - , m_target(Vec3d::Zero()) -{ -} - -std::string GLCanvas3D::Camera::get_type_as_string() const -{ - switch (type) - { - default: - case Unknown: - return "unknown"; -// case Perspective: -// return "perspective"; - case Ortho: - return "ortho"; - }; -} - -void GLCanvas3D::Camera::set_theta(float theta, bool apply_limit) -{ - if (apply_limit) - m_theta = clamp(0.0f, GIMBALL_LOCK_THETA_MAX, theta); - else - { - m_theta = fmod(theta, 360.0f); - if (m_theta < 0.0f) - m_theta += 360.0f; - } -} - -void GLCanvas3D::Camera::set_target(const Vec3d& target, GLCanvas3D& canvas) -{ - m_target = target; - m_target(0) = clamp(m_scene_box.min(0), m_scene_box.max(0), m_target(0)); - m_target(1) = clamp(m_scene_box.min(1), m_scene_box.max(1), m_target(1)); - m_target(2) = clamp(m_scene_box.min(2), m_scene_box.max(2), m_target(2)); - if (!m_target.isApprox(target)) - canvas.viewport_changed(); -} - -void GLCanvas3D::Camera::set_scene_box(const BoundingBoxf3& box, GLCanvas3D& canvas) -{ - if (m_scene_box != box) - { - m_scene_box = box; - canvas.viewport_changed(); - } -} - #if !ENABLE_TEXTURES_FROM_SVG GLCanvas3D::Shader::Shader() : m_shader(nullptr) @@ -1145,8 +1088,11 @@ bool GLCanvas3D::Selection::is_single_full_instance() const for (unsigned int i : m_list) { const GLVolume* v = (*m_volumes)[i]; + if ((object_idx != v->object_idx()) || (instance_idx != v->instance_idx())) + return false; + int volume_idx = v->volume_idx(); - if ((v->object_idx() == object_idx) && (v->instance_idx() == instance_idx) && (volume_idx >= 0)) + if (volume_idx >= 0) volumes_idxs.insert(volume_idx); } @@ -1275,69 +1221,89 @@ void GLCanvas3D::Selection::rotate(const Vec3d& rotation, GLCanvas3D::Transforma // Only relative rotation values are allowed in the world coordinate system. assert(! transformation_type.world() || transformation_type.relative()); - int rot_axis_max; - //FIXME this does not work for absolute rotations (transformation_type.absolute() is true) - rotation.cwiseAbs().maxCoeff(&rot_axis_max); - - // For generic rotation, we want to rotate the first volume in selection, and then to synchronize the other volumes with it. - std::vector<int> object_instance_first(m_model->objects.size(), -1); - auto rotate_instance = [this, &rotation, &object_instance_first, rot_axis_max, transformation_type](GLVolume &volume, int i) { - int first_volume_idx = object_instance_first[volume.object_idx()]; - if (rot_axis_max != 2 && first_volume_idx != -1) { - // Generic rotation, but no rotation around the Z axis. - // Always do a local rotation (do not consider the selection to be a rigid body). - assert(is_approx(rotation.z(), 0.0)); - const GLVolume &first_volume = *(*m_volumes)[first_volume_idx]; - const Vec3d &rotation = first_volume.get_instance_rotation(); - double z_diff = rotation_diff_z(m_cache.volumes_data[first_volume_idx].get_instance_rotation(), m_cache.volumes_data[i].get_instance_rotation()); - volume.set_instance_rotation(Vec3d(rotation(0), rotation(1), rotation(2) + z_diff)); - } else { - // extracts rotations from the composed transformation - Vec3d new_rotation = transformation_type.world() ? - Geometry::extract_euler_angles(Geometry::assemble_transform(Vec3d::Zero(), rotation) * m_cache.volumes_data[i].get_instance_rotation_matrix()) : - transformation_type.absolute() ? rotation : rotation + m_cache.volumes_data[i].get_instance_rotation(); - if (rot_axis_max == 2 && transformation_type.joint()) { - // Only allow rotation of multiple instances as a single rigid body when rotating around the Z axis. - double z_diff = rotation_diff_z(new_rotation, m_cache.volumes_data[i].get_instance_rotation()); - volume.set_instance_offset(m_cache.dragging_center + Eigen::AngleAxisd(z_diff, Vec3d::UnitZ()) * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); - } - volume.set_instance_rotation(new_rotation); - object_instance_first[volume.object_idx()] = i; - } - }; - - for (unsigned int i : m_list) + int rot_axis_max = 0; + if (rotation.isApprox(Vec3d::Zero())) { - GLVolume &volume = *(*m_volumes)[i]; - if (is_single_full_instance()) - rotate_instance(volume, i); - else if (is_single_volume() || is_single_modifier()) + for (unsigned int i : m_list) { - if (transformation_type.independent()) - volume.set_volume_rotation(volume.get_volume_rotation() + rotation); - else + GLVolume &volume = *(*m_volumes)[i]; + if (m_mode == Instance) + { + volume.set_instance_rotation(m_cache.volumes_data[i].get_instance_rotation()); + volume.set_instance_offset(m_cache.volumes_data[i].get_instance_position()); + } + else if (m_mode == Volume) { - Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); - Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); - volume.set_volume_rotation(new_rotation); + volume.set_volume_rotation(m_cache.volumes_data[i].get_volume_rotation()); + volume.set_volume_offset(m_cache.volumes_data[i].get_volume_position()); } } - else + } + else + { + //FIXME this does not work for absolute rotations (transformation_type.absolute() is true) + rotation.cwiseAbs().maxCoeff(&rot_axis_max); + + // For generic rotation, we want to rotate the first volume in selection, and then to synchronize the other volumes with it. + std::vector<int> object_instance_first(m_model->objects.size(), -1); + auto rotate_instance = [this, &rotation, &object_instance_first, rot_axis_max, transformation_type](GLVolume &volume, int i) { + int first_volume_idx = object_instance_first[volume.object_idx()]; + if (rot_axis_max != 2 && first_volume_idx != -1) { + // Generic rotation, but no rotation around the Z axis. + // Always do a local rotation (do not consider the selection to be a rigid body). + assert(is_approx(rotation.z(), 0.0)); + const GLVolume &first_volume = *(*m_volumes)[first_volume_idx]; + const Vec3d &rotation = first_volume.get_instance_rotation(); + double z_diff = rotation_diff_z(m_cache.volumes_data[first_volume_idx].get_instance_rotation(), m_cache.volumes_data[i].get_instance_rotation()); + volume.set_instance_rotation(Vec3d(rotation(0), rotation(1), rotation(2) + z_diff)); + } else { + // extracts rotations from the composed transformation + Vec3d new_rotation = transformation_type.world() ? + Geometry::extract_euler_angles(Geometry::assemble_transform(Vec3d::Zero(), rotation) * m_cache.volumes_data[i].get_instance_rotation_matrix()) : + transformation_type.absolute() ? rotation : rotation + m_cache.volumes_data[i].get_instance_rotation(); + if (rot_axis_max == 2 && transformation_type.joint()) { + // Only allow rotation of multiple instances as a single rigid body when rotating around the Z axis. + Vec3d offset = Geometry::assemble_transform(Vec3d::Zero(), Vec3d(0.0, 0.0, new_rotation(2) - m_cache.volumes_data[i].get_instance_rotation()(2))) * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center); + volume.set_instance_offset(m_cache.dragging_center + offset); + } + volume.set_instance_rotation(new_rotation); + object_instance_first[volume.object_idx()] = i; + } + }; + + for (unsigned int i : m_list) { - if (m_mode == Instance) + GLVolume &volume = *(*m_volumes)[i]; + if (is_single_full_instance()) rotate_instance(volume, i); - else if (m_mode == Volume) + else if (is_single_volume() || is_single_modifier()) { - // extracts rotations from the composed transformation - Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); - Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); - if (transformation_type.joint()) + if (transformation_type.independent()) + volume.set_volume_rotation(volume.get_volume_rotation() + rotation); + else { - Vec3d local_pivot = m_cache.volumes_data[i].get_instance_full_matrix().inverse() * m_cache.dragging_center; - Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() - local_pivot); - volume.set_volume_offset(local_pivot + offset); + Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); + Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); + volume.set_volume_rotation(new_rotation); + } + } + else + { + if (m_mode == Instance) + rotate_instance(volume, i); + else if (m_mode == Volume) + { + // extracts rotations from the composed transformation + Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); + Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); + if (transformation_type.joint()) + { + Vec3d local_pivot = m_cache.volumes_data[i].get_instance_full_matrix().inverse() * m_cache.dragging_center; + Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() - local_pivot); + volume.set_volume_offset(local_pivot + offset); + } + volume.set_volume_rotation(new_rotation); } - volume.set_volume_rotation(new_rotation); } } } @@ -3039,14 +3005,6 @@ void GLCanvas3D::Gizmos::render_overlay(const GLCanvas3D& canvas, const GLCanvas ::glPopMatrix(); } -#if !ENABLE_IMGUI -void GLCanvas3D::Gizmos::create_external_gizmo_widgets(wxWindow *parent) -{ - for (auto &entry : m_gizmos) { - entry.second->create_external_gizmo_widgets(parent); - } -} -#endif // not ENABLE_IMGUI void GLCanvas3D::Gizmos::reset() { @@ -3065,13 +3023,12 @@ void GLCanvas3D::Gizmos::do_render_overlay(const GLCanvas3D& canvas, const GLCan return; float cnv_w = (float)canvas.get_canvas_size().get_width(); -#if ENABLE_IMGUI float cnv_h = (float)canvas.get_canvas_size().get_height(); -#endif // ENABLE_IMGUI float zoom = canvas.get_camera_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; float height = get_total_overlay_height(); + float width = get_total_overlay_width(); #if ENABLE_SVG_ICONS float scaled_border = m_overlay_border * m_overlay_scale * inv_zoom; #else @@ -3083,7 +3040,7 @@ void GLCanvas3D::Gizmos::do_render_overlay(const GLCanvas3D& canvas, const GLCan float left = top_x; float top = top_y; - float right = left + get_total_overlay_width() * inv_zoom; + float right = left + width * inv_zoom; float bottom = top - height * inv_zoom; // renders background @@ -3191,29 +3148,27 @@ void GLCanvas3D::Gizmos::do_render_overlay(const GLCanvas3D& canvas, const GLCan #if ENABLE_SVG_ICONS float u_icon_size = m_overlay_icons_size * m_overlay_scale * inv_tex_width; float v_icon_size = m_overlay_icons_size * m_overlay_scale * inv_tex_height; - float top = sprite_id * v_icon_size; - float left = state * u_icon_size; - float bottom = top + v_icon_size; - float right = left + u_icon_size; + float v_top = sprite_id * v_icon_size; + float u_left = state * u_icon_size; + float v_bottom = v_top + v_icon_size; + float u_right = u_left + u_icon_size; #else float uv_icon_size = (float)m_icons_texture.metadata.icon_size * inv_texture_size; - float top = sprite_id * uv_icon_size; - float left = state * uv_icon_size; - float bottom = top + uv_icon_size; - float right = left + uv_icon_size; + float v_top = sprite_id * uv_icon_size; + float u_left = state * uv_icon_size; + float v_bottom = v_top + uv_icon_size; + float u_right = u_left + uv_icon_size; #endif // ENABLE_SVG_ICONS - GLTexture::render_sub_texture(icons_texture_id, top_x, top_x + scaled_icons_size, top_y - scaled_icons_size, top_y, { { left, bottom }, { right, bottom }, { right, top }, { left, top } }); -#if ENABLE_IMGUI + GLTexture::render_sub_texture(icons_texture_id, top_x, top_x + scaled_icons_size, top_y - scaled_icons_size, top_y, { { u_left, v_bottom }, { u_right, v_bottom }, { u_right, v_top }, { u_left, v_top } }); if (it->second->get_state() == GLGizmoBase::On) { - float toolbar_top = (float)cnv_h - canvas.m_view_toolbar->get_height(); + float toolbar_top = (float)cnv_h - canvas.m_view_toolbar.get_height(); #if ENABLE_SVG_ICONS - it->second->render_input_window(2.0f * m_overlay_border + m_overlay_icons_size, 0.5f * cnv_h - top_y * zoom, toolbar_top, selection); + it->second->render_input_window(width, 0.5f * cnv_h - top_y * zoom, toolbar_top, selection); #else it->second->render_input_window(2.0f * m_overlay_border + icon_size * zoom, 0.5f * cnv_h - top_y * zoom, toolbar_top, selection); #endif // ENABLE_SVG_ICONS } -#endif // ENABLE_IMGUI #if ENABLE_SVG_ICONS top_y -= scaled_stride_y; #else @@ -3305,7 +3260,7 @@ bool GLCanvas3D::Gizmos::generate_icons_texture() const } #endif // ENABLE_SVG_ICONS -const unsigned char GLCanvas3D::WarningTexture::Background_Color[3] = { 9, 91, 134 }; +const unsigned char GLCanvas3D::WarningTexture::Background_Color[3] = { 120, 120, 120 };//{ 9, 91, 134 }; const unsigned char GLCanvas3D::WarningTexture::Opacity = 255; GLCanvas3D::WarningTexture::WarningTexture() @@ -3339,16 +3294,23 @@ void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool // Look at the end of our vector and generate proper texture. std::string text; + bool red_colored = false; switch (m_warnings.back()) { case ObjectOutside : text = L("Detected object outside print volume"); break; case ToolpathOutside : text = L("Detected toolpath outside print volume"); break; case SomethingNotShown : text = L("Some objects are not visible when editing supports"); break; + case ObjectClashed: { + text = L("Detected object outside print volume\n" + "Resolve a clash to continue slicing/export process correctly"); + red_colored = true; + break; + } } - _generate(text, canvas); // GUI::GLTexture::reset() is called at the beginning of generate(...) + _generate(text, canvas, red_colored); // GUI::GLTexture::reset() is called at the beginning of generate(...) } -bool GLCanvas3D::WarningTexture::_generate(const std::string& msg, const GLCanvas3D& canvas) +bool GLCanvas3D::WarningTexture::_generate(const std::string& msg, const GLCanvas3D& canvas, const bool red_colored/* = false*/) { reset(); @@ -3365,7 +3327,8 @@ bool GLCanvas3D::WarningTexture::_generate(const std::string& msg, const GLCanva // calculates texture size wxCoord w, h; - memDC.GetTextExtent(msg, &w, &h); +// memDC.GetTextExtent(msg, &w, &h); + memDC.GetMultiLineTextExtent(msg, &w, &h); int pow_of_two_size = next_highest_power_of_2(std::max<unsigned int>(w, h)); @@ -3382,8 +3345,9 @@ bool GLCanvas3D::WarningTexture::_generate(const std::string& msg, const GLCanva memDC.Clear(); // draw message - memDC.SetTextForeground(*wxWHITE); - memDC.DrawText(msg, 0, 0); + memDC.SetTextForeground(red_colored ? wxColour(255,72,65/*204,204*/) : *wxWHITE); +// memDC.DrawText(msg, 0, 0); + memDC.DrawLabel(msg, wxRect(0,0, m_original_width, m_original_height), wxALIGN_CENTER); memDC.SelectObject(wxNullBitmap); @@ -3710,7 +3674,6 @@ void GLCanvas3D::LegendTexture::render(const GLCanvas3D& canvas) const wxDEFINE_EVENT(EVT_GLCANVAS_INIT, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_VIEWPORT_CHANGED, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, Vec2dEvent); wxDEFINE_EVENT(EVT_GLCANVAS_REMOVE_OBJECT, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_ARRANGE, SimpleEvent); @@ -3727,20 +3690,21 @@ wxDEFINE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_UPDATE_BED_SHAPE, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent); -GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas) +GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar) : m_canvas(canvas) , m_context(nullptr) #if ENABLE_RETINA_GL , m_retina_helper(nullptr) #endif , m_in_render(false) - , m_bed(nullptr) + , m_bed(bed) + , m_camera(camera) + , m_view_toolbar(view_toolbar) #if ENABLE_SVG_ICONS , m_toolbar(GLToolbar::Normal, "Top") #else , m_toolbar(GLToolbar::Normal) #endif // ENABLE_SVG_ICONS - , m_view_toolbar(nullptr) , m_use_clipping_planes(false) , m_sidebar_field("") , m_config(nullptr) @@ -3749,7 +3713,6 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas) , m_dirty(true) , m_initialized(false) , m_use_VBOs(false) - , m_requires_zoom_to_bed(false) , m_apply_zoom_to_volumes_filter(false) , m_hover_volume_id(-1) , m_toolbar_action_running(false) @@ -3764,9 +3727,6 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas) , m_color_by("volume") , m_reload_delayed(false) , m_render_sla_auxiliaries(true) -#if !ENABLE_IMGUI - , m_external_gizmo_widgets_parent(nullptr) -#endif // not ENABLE_IMGUI { if (m_canvas != nullptr) { m_timer.SetOwner(m_canvas); @@ -3789,11 +3749,6 @@ void GLCanvas3D::post_event(wxEvent &&event) wxPostEvent(m_canvas, event); } -void GLCanvas3D::viewport_changed() -{ - post_event(SimpleEvent(EVT_GLCANVAS_VIEWPORT_CHANGED)); -} - bool GLCanvas3D::init(bool useVBOs, bool use_legacy_opengl) { if (m_initialized) @@ -3864,13 +3819,6 @@ bool GLCanvas3D::init(bool useVBOs, bool use_legacy_opengl) std::cout << "Unable to initialize gizmos: please, check that all the required textures are available" << std::endl; return false; } - -#if !ENABLE_IMGUI - if (m_external_gizmo_widgets_parent != nullptr) { - m_gizmos.create_external_gizmo_widgets(m_external_gizmo_widgets_parent); - m_canvas->GetParent()->Layout(); - } -#endif // not ENABLE_IMGUI } if (!_init_toolbar()) @@ -3968,9 +3916,8 @@ void GLCanvas3D::set_model(Model* model) void GLCanvas3D::bed_shape_changed() { - m_camera.set_scene_box(scene_bounding_box(), *this); - m_requires_zoom_to_bed = true; - + m_camera.set_scene_box(scene_bounding_box()); + m_camera.requires_zoom_to_bed = true; m_dirty = true; } @@ -3998,8 +3945,7 @@ BoundingBoxf3 GLCanvas3D::volumes_bounding_box() const BoundingBoxf3 GLCanvas3D::scene_bounding_box() const { BoundingBoxf3 bb = volumes_bounding_box(); - if (m_bed != nullptr) - bb.merge(m_bed->get_bounding_box()); + bb.merge(m_bed.get_bounding_box()); if (m_config != nullptr) { @@ -4088,8 +4034,7 @@ bool GLCanvas3D::is_toolbar_item_pressed(const std::string& name) const void GLCanvas3D::zoom_to_bed() { - if (m_bed != nullptr) - _zoom_to_bounding_box(m_bed->get_bounding_box()); + _zoom_to_bounding_box(m_bed.get_bounding_box()); } void GLCanvas3D::zoom_to_volumes() @@ -4128,24 +4073,11 @@ void GLCanvas3D::select_view(const std::string& direction) { m_camera.phi = dir_vec[0]; m_camera.set_theta(dir_vec[1], false); - - viewport_changed(); - if (m_canvas != nullptr) m_canvas->Refresh(); } } -void GLCanvas3D::set_viewport_from_scene(const GLCanvas3D& other) -{ - m_camera.phi = other.m_camera.phi; - m_camera.set_theta(other.m_camera.get_theta(), false); - m_camera.set_scene_box(other.m_camera.get_scene_box(), *this); - m_camera.set_target(other.m_camera.get_target(), *this); - m_camera.zoom = other.m_camera.zoom; - m_dirty = true; -} - void GLCanvas3D::update_volumes_colors_by_extruder() { if (m_config != nullptr) @@ -4161,27 +4093,6 @@ void GLCanvas3D::update_toolbar_items_visibility() m_dirty = true; } -// Returns a Rect object denoting size and position of the Reset button used by a gizmo. -// Returns in either screen or viewport coords. -#if !ENABLE_IMGUI -Rect GLCanvas3D::get_gizmo_reset_rect(const GLCanvas3D& canvas, bool viewport) const -{ - const Size& cnv_size = canvas.get_canvas_size(); - float w = (viewport ? -0.5f : 0.f) * (float)cnv_size.get_width(); - float h = (viewport ? 0.5f : 1.f) * (float)cnv_size.get_height(); - float zoom = canvas.get_camera_zoom(); - float inv_zoom = viewport ? ((zoom != 0.0f) ? 1.0f / zoom : 0.0f) : 1.f; - const float gap = 30.f; - return Rect((w + gap + 80.f) * inv_zoom, (viewport ? -1.f : 1.f) * (h - GIZMO_RESET_BUTTON_HEIGHT) * inv_zoom, - (w + gap + 80.f + GIZMO_RESET_BUTTON_WIDTH) * inv_zoom, (viewport ? -1.f : 1.f) * (h * inv_zoom)); -} - -bool GLCanvas3D::gizmo_reset_rect_contains(const GLCanvas3D& canvas, float x, float y) const -{ - const Rect& rect = get_gizmo_reset_rect(canvas, false); - return (rect.get_left() <= x) && (x <= rect.get_right()) && (rect.get_top() <= y) && (y <= rect.get_bottom()); -} -#endif // not ENABLE_IMGUI void GLCanvas3D::render() { @@ -4193,25 +4104,29 @@ void GLCanvas3D::render() if (m_canvas == nullptr) return; +#ifndef __WXMAC__ + // on Mac this check causes flickering when changing view if (!_is_shown_on_screen()) return; +#endif // __WXMAC__ // ensures this canvas is current and initialized if (!_set_current() || !_3DScene::init(m_canvas)) return; - if ((m_bed != nullptr) && m_bed->get_shape().empty()) + if (m_bed.get_shape().empty()) { // this happens at startup when no data is still saved under <>\AppData\Roaming\Slic3rPE post_event(SimpleEvent(EVT_GLCANVAS_UPDATE_BED_SHAPE)); + return; } - if (m_requires_zoom_to_bed) + if (m_camera.requires_zoom_to_bed) { zoom_to_bed(); const Size& cnv_size = get_canvas_size(); _resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height()); - m_requires_zoom_to_bed = false; + m_camera.requires_zoom_to_bed = false; } _camera_tranform(); @@ -4226,11 +4141,7 @@ void GLCanvas3D::render() // absolute value of the rotation theta = 360.f - theta; - bool is_custom_bed = (m_bed == nullptr) || m_bed->is_custom(); - -#if ENABLE_IMGUI wxGetApp().imgui()->new_frame(); -#endif // ENABLE_IMGUI // picking pass _picking_pass(); @@ -4240,8 +4151,8 @@ void GLCanvas3D::render() _render_background(); // textured bed needs to be rendered after objects if the texture is transparent - bool early_bed_render = is_custom_bed || (theta <= 90.0f); - if (early_bed_render) + bool early_bed_render = m_bed.is_custom() || (theta <= 90.0f); + if (early_bed_render) _render_bed(theta); _render_objects(); @@ -4281,9 +4192,7 @@ void GLCanvas3D::render() if (m_layers_editing.last_object_id >= 0) m_layers_editing.render_overlay(*this); -#if ENABLE_IMGUI wxGetApp().imgui()->render(); -#endif // ENABLE_IMGUI m_canvas->SwapBuffers(); } @@ -4611,11 +4520,18 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re const Print *print = m_process->fff_print(); float depth = print->get_wipe_tower_depth(); + + // Calculate wipe tower brim spacing. + const DynamicPrintConfig &print_config = wxGetApp().preset_bundle->prints.get_edited_preset().config; + double layer_height = print_config.opt_float("layer_height"); + double first_layer_height = print_config.get_abs_value("first_layer_height", layer_height); + float brim_spacing = print->config().nozzle_diameter.values[0] * 1.25f - first_layer_height * (1. - M_PI_4); + if (!print->is_step_done(psWipeTower)) depth = (900.f/w) * (float)(extruders_count - 1) ; int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( 1000, x, y, w, depth, (float)height, a, m_use_VBOs && m_initialized, !print->is_step_done(psWipeTower), - print->config().nozzle_diameter.values[0] * 1.25f * 4.5f); + brim_spacing * 4.5f); if (volume_idx_wipe_tower_old != -1) map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new; } @@ -4635,31 +4551,40 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re if (!m_volumes.empty()) { ModelInstance::EPrintVolumeState state; - bool contained = m_volumes.check_outside_state(m_config, &state); - if (!contained) - { - _set_warning_texture(WarningTexture::ObjectOutside, true); - post_event(Event<bool>(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, state == ModelInstance::PVS_Fully_Outside)); - } - else - { - m_volumes.reset_outside_state(); - _set_warning_texture(WarningTexture::ObjectOutside, false); - post_event(Event<bool>(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, !m_model->objects.empty())); - } + const bool contained_min_one = m_volumes.check_outside_state(m_config, &state); + + _set_warning_texture(WarningTexture::ObjectClashed, state == ModelInstance::PVS_Partly_Outside); + _set_warning_texture(WarningTexture::ObjectOutside, state == ModelInstance::PVS_Fully_Outside); + + post_event(Event<bool>(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, + contained_min_one && !m_model->objects.empty() && state != ModelInstance::PVS_Partly_Outside)); + +// #ys_FIXME_delete_after_testing +// bool contained = m_volumes.check_outside_state(m_config, &state); +// if (!contained) +// { +// _set_warning_texture(WarningTexture::ObjectOutside, true); +// post_event(Event<bool>(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, state == ModelInstance::PVS_Fully_Outside)); +// } +// else +// { +// m_volumes.reset_outside_state(); +// _set_warning_texture(WarningTexture::ObjectOutside, false); +// post_event(Event<bool>(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, !m_model->objects.empty())); +// } } else { _set_warning_texture(WarningTexture::ObjectOutside, false); + _set_warning_texture(WarningTexture::ObjectClashed, false); post_event(Event<bool>(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, false)); } // restore to default value m_regenerate_volumes = true; - m_camera.set_scene_box(scene_bounding_box(), *this); - m_camera.set_target(m_camera.get_target(), *this); + m_camera.set_scene_box(scene_bounding_box()); if (m_selection.is_empty()) { @@ -4833,13 +4758,11 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) int keyCode = evt.GetKeyCode(); int ctrlMask = wxMOD_CONTROL; -#if ENABLE_IMGUI auto imgui = wxGetApp().imgui(); if (imgui->update_key_data(evt)) { render(); return; } -#endif // ENABLE_IMGUI //#ifdef __APPLE__ // ctrlMask |= wxMOD_RAW_CONTROL; @@ -4947,12 +4870,10 @@ void GLCanvas3D::on_key(wxKeyEvent& evt) { const int keyCode = evt.GetKeyCode(); -#if ENABLE_IMGUI auto imgui = wxGetApp().imgui(); if (imgui->update_key_data(evt)) { render(); } else -#endif // ENABLE_IMGUI if (evt.GetEventType() == wxEVT_KEY_UP) { if (m_tab_down && keyCode == WXK_TAB && !evt.HasAnyModifiers()) { // Enable switching between 3D and Preview with Tab @@ -5075,7 +4996,6 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) Point pos(evt.GetX(), evt.GetY()); -#if ENABLE_IMGUI ImGuiWrapper *imgui = wxGetApp().imgui(); if (imgui->update_mouse_data(evt)) { m_mouse.position = evt.Leaving() ? Vec2d(-1.0, -1.0) : pos.cast<double>(); @@ -5085,7 +5005,6 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) #endif /* SLIC3R_DEBUG_MOUSE_EVENTS */ return; } -#endif // ENABLE_IMGUI #ifdef __WXMSW__ bool on_enter_workaround = false; @@ -5113,7 +5032,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_layers_editing.select_object(*m_model, layer_editing_object_idx); bool gizmos_overlay_contains_mouse = m_gizmos.overlay_contains_mouse(*this, m_mouse.position); int toolbar_contains_mouse = m_toolbar.contains_mouse(m_mouse.position, *this); - int view_toolbar_contains_mouse = (m_view_toolbar != nullptr) ? m_view_toolbar->contains_mouse(m_mouse.position, *this) : -1; + int view_toolbar_contains_mouse = m_view_toolbar.contains_mouse(m_mouse.position, *this); #if ENABLE_MOVE_MIN_THRESHOLD if (m_mouse.drag.move_requires_threshold && m_mouse.is_move_start_threshold_position_2D_defined() && m_mouse.is_move_threshold_met(pos)) @@ -5225,10 +5144,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) // event was taken care of by the SlaSupports gizmo } else if (evt.LeftDown() && (view_toolbar_contains_mouse != -1)) - { - if (m_view_toolbar != nullptr) - m_view_toolbar->do_action((unsigned int)view_toolbar_contains_mouse, *this); - } + m_view_toolbar.do_action((unsigned int)view_toolbar_contains_mouse, *this); else if (evt.LeftDown() && (toolbar_contains_mouse != -1)) { m_toolbar_action_running = true; @@ -5294,42 +5210,6 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_moving = true; } } - else if (evt.RightDown()) - { - m_mouse.position = pos.cast<double>(); - // forces a frame render to ensure that m_hover_volume_id is updated even when the user right clicks while - // the context menu is already shown - render(); - if (m_hover_volume_id != -1) - { - // if right clicking on volume, propagate event through callback (shows context menu) - if (m_volumes.volumes[m_hover_volume_id]->hover - && !m_volumes.volumes[m_hover_volume_id]->is_wipe_tower // no context menu for the wipe tower - && m_gizmos.get_current_type() != Gizmos::SlaSupports) // disable context menu when the gizmo is open - { - // forces the selection of the volume - /** #ys_FIXME_to_delete after testing: - * Next condition allows a multiple instance selection for the context menu, - * which has no reason. So it's commented till next testing - */ -// if (!m_selection.is_multiple_full_instance()) // #ys_FIXME_to_delete - m_selection.add(m_hover_volume_id); - m_gizmos.update_on_off_state(m_selection); - post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); - _update_gizmos_data(); - wxGetApp().obj_manipul()->update_settings_value(m_selection); - // forces a frame render to update the view before the context menu is shown - render(); - - Vec2d logical_pos = pos.cast<double>(); -#if ENABLE_RETINA_GL - const float factor = m_retina_helper->get_scale_factor(); - logical_pos = logical_pos.cwiseQuotient(Vec2d(factor, factor)); -#endif // ENABLE_RETINA_GL - post_event(Vec2dEvent(EVT_GLCANVAS_RIGHT_CLICK, logical_pos)); - } - } - } } } } @@ -5459,9 +5339,6 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) const Vec3d& orig = m_mouse.drag.start_position_3D; m_camera.phi += (((float)pos(0) - (float)orig(0)) * TRACKBALLSIZE); m_camera.set_theta(m_camera.get_theta() - ((float)pos(1) - (float)orig(1)) * TRACKBALLSIZE, wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA); - - viewport_changed(); - m_dirty = true; } m_mouse.drag.start_position_3D = Vec3d((double)pos(0), (double)pos(1), 0.0); @@ -5475,10 +5352,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) float z = 0.0f; const Vec3d& cur_pos = _mouse_to_3d(pos, &z); Vec3d orig = _mouse_to_3d(m_mouse.drag.start_position_2D, &z); - m_camera.set_target(m_camera.get_target() + orig - cur_pos, *this); - - viewport_changed(); - + m_camera.set_target(m_camera.get_target() + orig - cur_pos); m_dirty = true; } @@ -5498,7 +5372,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) { // the gizmo got the event and took some action, no need to do anything more } - else if ((m_mouse.drag.move_volume_idx != -1) && m_mouse.dragging) + else if ((m_mouse.drag.move_volume_idx != -1) && m_mouse.dragging && m_gizmos.get_current_type() != Gizmos::SlaSupports) { m_regenerate_volumes = false; do_move(); @@ -5555,8 +5429,38 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) // Let the platter know that the dragging finished, so a delayed refresh // of the scene with the background processing data should be performed. post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); - m_camera.set_scene_box(scene_bounding_box(), *this); - set_camera_zoom(0.0f); + m_camera.set_scene_box(scene_bounding_box()); + } + else if (evt.RightUp()) + { + m_mouse.position = pos.cast<double>(); + // forces a frame render to ensure that m_hover_volume_id is updated even when the user right clicks while + // the context menu is already shown + render(); + if (m_hover_volume_id != -1) + { + // if right clicking on volume, propagate event through callback (shows context menu) + if (m_volumes.volumes[m_hover_volume_id]->hover + && !m_volumes.volumes[m_hover_volume_id]->is_wipe_tower // no context menu for the wipe tower + && m_gizmos.get_current_type() != Gizmos::SlaSupports) // disable context menu when the gizmo is open + { + // forces the selection of the volume + m_selection.add(m_hover_volume_id); + m_gizmos.update_on_off_state(m_selection); + post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); + _update_gizmos_data(); + wxGetApp().obj_manipul()->update_settings_value(m_selection); + // forces a frame render to update the view before the context menu is shown + render(); + + Vec2d logical_pos = pos.cast<double>(); +#if ENABLE_RETINA_GL + const float factor = m_retina_helper->get_scale_factor(); + logical_pos = logical_pos.cwiseQuotient(Vec2d(factor, factor)); +#endif // ENABLE_RETINA_GL + post_event(Vec2dEvent(EVT_GLCANVAS_RIGHT_CLICK, logical_pos)); + } + } } m_moving = false; @@ -5586,9 +5490,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) tooltip = m_toolbar.update_hover_state(m_mouse.position, *this); // updates view toolbar overlay - if (tooltip.empty() && (m_view_toolbar != nullptr)) + if (tooltip.empty()) { - tooltip = m_view_toolbar->update_hover_state(m_mouse.position, *this); + tooltip = m_view_toolbar.update_hover_state(m_mouse.position, *this); if (!tooltip.empty()) m_dirty = true; } @@ -5671,12 +5575,6 @@ void GLCanvas3D::set_tooltip(const std::string& tooltip) const } } -#if !ENABLE_IMGUI -void GLCanvas3D::set_external_gizmo_widgets_parent(wxWindow *parent) -{ - m_external_gizmo_widgets_parent = parent; -} -#endif // not ENABLE_IMGUI void GLCanvas3D::do_move() { @@ -5902,7 +5800,6 @@ void GLCanvas3D::set_camera_zoom(float zoom) zoom = std::min(zoom, 100.0f); m_camera.zoom = zoom; - viewport_changed(); _refresh_if_shown_on_screen(); } @@ -5978,6 +5875,9 @@ bool GLCanvas3D::_init_toolbar() return true; } +#if ENABLE_SVG_ICONS + m_toolbar.set_icons_size(40); +#endif // ENABLE_SVG_ICONS // m_toolbar.set_layout_type(GLToolbar::Layout::Vertical); m_toolbar.set_layout_type(GLToolbar::Layout::Horizontal); m_toolbar.set_layout_orientation(GLToolbar::Layout::Top); @@ -6107,14 +6007,12 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h) if ((m_canvas == nullptr) && (m_context == nullptr)) return; -#if ENABLE_IMGUI wxGetApp().imgui()->set_display_size((float)w, (float)h); #if ENABLE_RETINA_GL wxGetApp().imgui()->set_style_scaling(m_retina_helper->get_scale_factor()); #else wxGetApp().imgui()->set_style_scaling(m_canvas->GetContentScaleFactor()); #endif -#endif // ENABLE_IMGUI // ensures that this canvas is current _set_current(); @@ -6182,8 +6080,7 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h) BoundingBoxf3 GLCanvas3D::_max_bounding_box() const { BoundingBoxf3 bb = volumes_bounding_box(); - if (m_bed != nullptr) - bb.merge(m_bed->get_bounding_box()); + bb.merge(m_bed.get_bounding_box()); return bb; } @@ -6195,10 +6092,7 @@ void GLCanvas3D::_zoom_to_bounding_box(const BoundingBoxf3& bbox) { m_camera.zoom = zoom; // center view around bounding box center - m_camera.set_target(bbox.center(), *this); - - viewport_changed(); - + m_camera.set_target(bbox.center()); m_dirty = true; } } @@ -6290,7 +6184,6 @@ void GLCanvas3D::_camera_tranform() const ::glRotatef(-m_camera.get_theta(), 1.0f, 0.0f, 0.0f); // pitch ::glRotatef(m_camera.phi, 0.0f, 0.0f, 1.0f); // yaw - Vec3d target = -m_camera.get_target(); ::glTranslated(target(0), target(1), target(2)); } @@ -6329,7 +6222,6 @@ void GLCanvas3D::_picking_pass() const ::glReadPixels(pos(0), cnv_size.get_height() - pos(1) - 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, (void*)color); volume_id = color[0] + color[1] * 256 + color[2] * 256 * 256; } - if ((0 <= volume_id) && (volume_id < (int)m_volumes.volumes.size())) { m_hover_volume_id = volume_id; @@ -6338,7 +6230,7 @@ void GLCanvas3D::_picking_pass() const else { m_hover_volume_id = -1; - m_gizmos.set_hover_id(inside ? (254 - (int)color[2]) : -1); + m_gizmos.set_hover_id(inside && volume_id <= GLGizmoBase::BASE_ID ? (GLGizmoBase::BASE_ID - volume_id) : -1); } _update_volumes_hover_state(); @@ -6386,16 +6278,13 @@ void GLCanvas3D::_render_bed(float theta) const float scale_factor = 1.0; #if ENABLE_RETINA_GL scale_factor = m_retina_helper->get_scale_factor(); -#endif - - if (m_bed != nullptr) - m_bed->render(theta, m_use_VBOs, scale_factor); +#endif // ENABLE_RETINA_GL + m_bed.render(theta, m_use_VBOs, scale_factor); } void GLCanvas3D::_render_axes() const { - if (m_bed != nullptr) - m_bed->render_axes(); + m_bed.render_axes(); } void GLCanvas3D::_render_objects() const @@ -6413,9 +6302,9 @@ void GLCanvas3D::_render_objects() const // Update the layer editing selection to the first object selected, update the current object maximum Z. const_cast<LayersEditing&>(m_layers_editing).select_object(*m_model, this->is_layers_editing_enabled() ? m_selection.get_object_idx() : -1); - if ((m_config != nullptr) && (m_bed != nullptr)) + if (m_config != nullptr) { - const BoundingBoxf3& bed_bb = m_bed->get_bounding_box(); + const BoundingBoxf3& bed_bb = m_bed.get_bounding_box(); m_volumes.set_print_box((float)bed_bb.min(0), (float)bed_bb.min(1), 0.0f, (float)bed_bb.max(0), (float)bed_bb.max(1), (float)m_config->opt_float("max_print_height")); m_volumes.check_outside_state(m_config, nullptr); } @@ -6597,7 +6486,7 @@ void GLCanvas3D::_render_toolbar() const } else { - top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar->get_height()) * inv_zoom; + top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar.get_height()) * inv_zoom; left = -0.5f * m_toolbar.get_width() * inv_zoom; } break; @@ -6632,31 +6521,29 @@ void GLCanvas3D::_render_toolbar() const void GLCanvas3D::_render_view_toolbar() const { - if (m_view_toolbar != nullptr) { #if ENABLE_SVG_ICONS #if ENABLE_RETINA_GL - m_view_toolbar->set_scale(m_retina_helper->get_scale_factor()); + m_view_toolbar.set_scale(m_retina_helper->get_scale_factor()); #else - m_view_toolbar->set_scale(m_canvas->GetContentScaleFactor()); + m_view_toolbar.set_scale(m_canvas->GetContentScaleFactor()); #endif // ENABLE_RETINA_GL - Size cnv_size = get_canvas_size(); - float zoom = get_camera_zoom(); - float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + Size cnv_size = get_canvas_size(); + float zoom = get_camera_zoom(); + float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; - // places the toolbar on the bottom-left corner of the 3d scene - float top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar->get_height()) * inv_zoom; - float left = -0.5f * (float)cnv_size.get_width() * inv_zoom; - m_view_toolbar->set_position(top, left); + // places the toolbar on the bottom-left corner of the 3d scene + float top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar.get_height()) * inv_zoom; + float left = -0.5f * (float)cnv_size.get_width() * inv_zoom; + m_view_toolbar.set_position(top, left); #else #if ENABLE_RETINA_GL - m_view_toolbar->set_icons_scale(m_retina_helper->get_scale_factor()); + m_view_toolbar.set_icons_scale(m_retina_helper->get_scale_factor()); #else - m_view_toolbar->set_icons_scale(m_canvas->GetContentScaleFactor()); + m_view_toolbar.set_icons_scale(m_canvas->GetContentScaleFactor()); #endif /* __WXMSW__ */ #endif // ENABLE_SVG_ICONS - m_view_toolbar->render(*this); - } + m_view_toolbar.render(*this); } #if ENABLE_SHOW_CAMERA_TARGET @@ -7908,10 +7795,17 @@ void GLCanvas3D::_load_shells_fff() unsigned int extruders_count = config.nozzle_diameter.size(); if ((extruders_count > 1) && config.single_extruder_multi_material && config.wipe_tower && !config.complete_objects) { float depth = print->get_wipe_tower_depth(); + + // Calculate wipe tower brim spacing. + const DynamicPrintConfig &print_config = wxGetApp().preset_bundle->prints.get_edited_preset().config; + double layer_height = print_config.opt_float("layer_height"); + double first_layer_height = print_config.get_abs_value("first_layer_height", layer_height); + float brim_spacing = print->config().nozzle_diameter.values[0] * 1.25f - first_layer_height * (1. - M_PI_4); + if (!print->is_step_done(psWipeTower)) depth = (900.f/config.wipe_tower_width) * (float)(extruders_count - 1) ; m_volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle, - m_use_VBOs && m_initialized, !print->is_step_done(psWipeTower), print->config().nozzle_diameter.values[0] * 1.25f * 4.5f); + m_use_VBOs && m_initialized, !print->is_step_done(psWipeTower), brim_spacing * 4.5f); } } } @@ -8172,7 +8066,7 @@ void GLCanvas3D::_resize_toolbars() const } else { - top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar->get_height()) * inv_zoom; + top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar.get_height()) * inv_zoom; left = -0.5f * m_toolbar.get_width() * inv_zoom; } m_toolbar.set_position(top, left); @@ -8200,15 +8094,15 @@ void GLCanvas3D::_resize_toolbars() const if (m_view_toolbar != nullptr) { #if ENABLE_RETINA_GL - m_view_toolbar->set_icons_scale(m_retina_helper->get_scale_factor()); + m_view_toolbar.set_icons_scale(m_retina_helper->get_scale_factor()); #else - m_view_toolbar->set_icons_scale(m_canvas->GetContentScaleFactor()); + m_view_toolbar.set_icons_scale(m_canvas->GetContentScaleFactor()); #endif /* __WXMSW__ */ // places the toolbar on the bottom-left corner of the 3d scene - float top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar->get_height()) * inv_zoom; + float top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar.get_height()) * inv_zoom; float left = -0.5f * (float)cnv_size.get_width() * inv_zoom; - m_view_toolbar->set_position(top, left); + m_view_toolbar.set_position(top, left); } } #endif // !ENABLE_SVG_ICONS diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index ae6155876..4ec0f076b 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -9,6 +9,7 @@ #include "GLToolbar.hpp" #include "Event.hpp" #include "3DBed.hpp" +#include "Camera.hpp" #include <float.h> @@ -100,7 +101,6 @@ template <size_t N> using Vec3dsEvent = ArrayEvent<Vec3d, N>; wxDECLARE_EVENT(EVT_GLCANVAS_INIT, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent); -wxDECLARE_EVENT(EVT_GLCANVAS_VIEWPORT_CHANGED, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, Vec2dEvent); wxDECLARE_EVENT(EVT_GLCANVAS_REMOVE_OBJECT, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_ARRANGE, SimpleEvent); @@ -162,41 +162,6 @@ class GLCanvas3D void reset() { first_volumes.clear(); } }; - struct Camera - { - enum EType : unsigned char - { - Unknown, -// Perspective, - Ortho, - Num_types - }; - - EType type; - float zoom; - float phi; -// float distance; - - private: - Vec3d m_target; - BoundingBoxf3 m_scene_box; - float m_theta; - - public: - Camera(); - - std::string get_type_as_string() const; - - float get_theta() const { return m_theta; } - void set_theta(float theta, bool apply_limit); - - const Vec3d& get_target() const { return m_target; } - void set_target(const Vec3d& target, GLCanvas3D& canvas); - - const BoundingBoxf3& get_scene_box() const { return m_scene_box; } - void set_scene_box(const BoundingBoxf3& box, GLCanvas3D& canvas); - }; - #if !ENABLE_TEXTURES_FROM_SVG class Shader { @@ -785,10 +750,6 @@ private: void render_overlay(const GLCanvas3D& canvas, const Selection& selection) const; -#if !ENABLE_IMGUI - void create_external_gizmo_widgets(wxWindow *parent); -#endif // not ENABLE_IMGUI - private: void reset(); @@ -829,7 +790,8 @@ private: enum Warning { ObjectOutside, ToolpathOutside, - SomethingNotShown + SomethingNotShown, + ObjectClashed }; // Sets a warning of the given type to be active/inactive. If several warnings are active simultaneously, @@ -848,7 +810,7 @@ private: std::vector<Warning> m_warnings; // Generates the texture with given text. - bool _generate(const std::string& msg, const GLCanvas3D& canvas); + bool _generate(const std::string& msg, const GLCanvas3D& canvas, const bool red_colored = false); }; class LegendTexture : public GUI::GLTexture @@ -885,14 +847,14 @@ private: LegendTexture m_legend_texture; WarningTexture m_warning_texture; wxTimer m_timer; - Camera m_camera; - Bed3D* m_bed; + Bed3D& m_bed; + Camera& m_camera; + GLToolbar& m_view_toolbar; LayersEditing m_layers_editing; Shader m_shader; Mouse m_mouse; mutable Gizmos m_gizmos; mutable GLToolbar m_toolbar; - GLToolbar* m_view_toolbar; ClippingPlane m_clipping_planes[2]; bool m_use_clipping_planes; mutable SlaCap m_sla_caps[2]; @@ -908,7 +870,6 @@ private: bool m_dirty; bool m_initialized; bool m_use_VBOs; - bool m_requires_zoom_to_bed; bool m_apply_zoom_to_volumes_filter; mutable int m_hover_volume_id; bool m_toolbar_action_running; @@ -929,12 +890,8 @@ private: GCodePreviewVolumeIndex m_gcode_preview_volume_index; -#if !ENABLE_IMGUI - wxWindow *m_external_gizmo_widgets_parent; -#endif // not ENABLE_IMGUI - public: - GLCanvas3D(wxGLCanvas* canvas); + GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar); ~GLCanvas3D(); void set_context(wxGLContext* context) { m_context = context; } @@ -942,10 +899,6 @@ public: wxGLCanvas* get_wxglcanvas() { return m_canvas; } const wxGLCanvas* get_wxglcanvas() const { return m_canvas; } - void set_bed(Bed3D* bed) { m_bed = bed; } - - void set_view_toolbar(GLToolbar* toolbar) { m_view_toolbar = toolbar; } - bool init(bool useVBOs, bool use_legacy_opengl); void post_event(wxEvent &&event); @@ -1005,17 +958,11 @@ public: void zoom_to_volumes(); void zoom_to_selection(); void select_view(const std::string& direction); - void set_viewport_from_scene(const GLCanvas3D& other); void update_volumes_colors_by_extruder(); void update_toolbar_items_visibility(); -#if !ENABLE_IMGUI - Rect get_gizmo_reset_rect(const GLCanvas3D& canvas, bool viewport) const; - bool gizmo_reset_rect_contains(const GLCanvas3D& canvas, float x, float y) const; -#endif // not ENABLE_IMGUI - bool is_dragging() const { return m_gizmos.is_dragging() || m_moving; } void render(); @@ -1056,10 +1003,6 @@ public: void set_tooltip(const std::string& tooltip) const; -#if !ENABLE_IMGUI - void set_external_gizmo_widgets_parent(wxWindow *parent); -#endif // not ENABLE_IMGUI - void do_move(); void do_rotate(); void do_scale(); @@ -1070,8 +1013,6 @@ public: void update_gizmos_on_off_state(); - void viewport_changed(); - void handle_sidebar_focus_event(const std::string& opt_key, bool focus_on); void update_ui_from_settings(); diff --git a/src/slic3r/GUI/GLCanvas3DManager.cpp b/src/slic3r/GUI/GLCanvas3DManager.cpp index 71299f777..e409bed0d 100644 --- a/src/slic3r/GUI/GLCanvas3DManager.cpp +++ b/src/slic3r/GUI/GLCanvas3DManager.cpp @@ -129,7 +129,7 @@ GLCanvas3DManager::~GLCanvas3DManager() } } -bool GLCanvas3DManager::add(wxGLCanvas* canvas) +bool GLCanvas3DManager::add(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar) { if (canvas == nullptr) return false; @@ -137,7 +137,7 @@ bool GLCanvas3DManager::add(wxGLCanvas* canvas) if (_get_canvas(canvas) != m_canvases.end()) return false; - GLCanvas3D* canvas3D = new GLCanvas3D(canvas); + GLCanvas3D* canvas3D = new GLCanvas3D(canvas, bed, camera, view_toolbar); if (canvas3D == nullptr) return false; diff --git a/src/slic3r/GUI/GLCanvas3DManager.hpp b/src/slic3r/GUI/GLCanvas3DManager.hpp index 1f7c49f72..75647e6b2 100644 --- a/src/slic3r/GUI/GLCanvas3DManager.hpp +++ b/src/slic3r/GUI/GLCanvas3DManager.hpp @@ -23,6 +23,9 @@ class PrintObject; namespace GUI { class GLCanvas3D; +class Bed3D; +class GLToolbar; +struct Camera; class GLCanvas3DManager { @@ -62,7 +65,7 @@ public: GLCanvas3DManager(); ~GLCanvas3DManager(); - bool add(wxGLCanvas* canvas); + bool add(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar); bool remove(wxGLCanvas* canvas); void remove_all(); diff --git a/src/slic3r/GUI/GLGizmo.cpp b/src/slic3r/GUI/GLGizmo.cpp deleted file mode 100644 index 22023825f..000000000 --- a/src/slic3r/GUI/GLGizmo.cpp +++ /dev/null @@ -1,2862 +0,0 @@ -// Include GLGizmo.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. -#include "GLGizmo.hpp" - -#include <GL/glew.h> -#include <Eigen/Dense> - -#include "libslic3r/libslic3r.h" -#include "libslic3r/Geometry.hpp" -#include "libslic3r/Utils.hpp" -#include "libslic3r/SLA/SLACommon.hpp" -#include "libslic3r/SLAPrint.hpp" - -#include <cstdio> -#include <numeric> -#include <algorithm> - -#include <wx/sizer.h> -#include <wx/panel.h> -#include <wx/button.h> -#include <wx/checkbox.h> -#include <wx/stattext.h> -#include <wx/debug.h> - -#include "Tab.hpp" -#include "GUI.hpp" -#include "GUI_Utils.hpp" -#include "GUI_App.hpp" -#include "GUI_ObjectSettings.hpp" -#include "GUI_ObjectList.hpp" -#include "I18N.hpp" -#include "PresetBundle.hpp" - -#include <wx/defs.h> - -// TODO: Display tooltips quicker on Linux - -static const float DEFAULT_BASE_COLOR[3] = { 0.625f, 0.625f, 0.625f }; -static const float DEFAULT_DRAG_COLOR[3] = { 1.0f, 1.0f, 1.0f }; -static const float DEFAULT_HIGHLIGHT_COLOR[3] = { 1.0f, 0.38f, 0.0f }; - -static const float AXES_COLOR[3][3] = { { 1.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f } }; - -namespace Slic3r { -namespace GUI { - -const float GLGizmoBase::Grabber::SizeFactor = 0.025f; -const float GLGizmoBase::Grabber::MinHalfSize = 1.5f; -const float GLGizmoBase::Grabber::DraggingScaleFactor = 1.25f; - -GLGizmoBase::Grabber::Grabber() - : center(Vec3d::Zero()) - , angles(Vec3d::Zero()) - , dragging(false) - , enabled(true) -{ - color[0] = 1.0f; - color[1] = 1.0f; - color[2] = 1.0f; -} - -void GLGizmoBase::Grabber::render(bool hover, float size) const -{ - float render_color[3]; - if (hover) - { - render_color[0] = 1.0f - color[0]; - render_color[1] = 1.0f - color[1]; - render_color[2] = 1.0f - color[2]; - } - else - ::memcpy((void*)render_color, (const void*)color, 3 * sizeof(float)); - - render(size, render_color, true); -} - -float GLGizmoBase::Grabber::get_half_size(float size) const -{ - return std::max(size * SizeFactor, MinHalfSize); -} - -float GLGizmoBase::Grabber::get_dragging_half_size(float size) const -{ - return std::max(size * SizeFactor * DraggingScaleFactor, MinHalfSize); -} - -void GLGizmoBase::Grabber::render(float size, const float* render_color, bool use_lighting) const -{ - float half_size = dragging ? get_dragging_half_size(size) : get_half_size(size); - - if (use_lighting) - ::glEnable(GL_LIGHTING); - - ::glColor3fv(render_color); - - ::glPushMatrix(); - ::glTranslated(center(0), center(1), center(2)); - - ::glRotated(Geometry::rad2deg(angles(2)), 0.0, 0.0, 1.0); - ::glRotated(Geometry::rad2deg(angles(1)), 0.0, 1.0, 0.0); - ::glRotated(Geometry::rad2deg(angles(0)), 1.0, 0.0, 0.0); - - // face min x - ::glPushMatrix(); - ::glTranslatef(-(GLfloat)half_size, 0.0f, 0.0f); - ::glRotatef(-90.0f, 0.0f, 1.0f, 0.0f); - render_face(half_size); - ::glPopMatrix(); - - // face max x - ::glPushMatrix(); - ::glTranslatef((GLfloat)half_size, 0.0f, 0.0f); - ::glRotatef(90.0f, 0.0f, 1.0f, 0.0f); - render_face(half_size); - ::glPopMatrix(); - - // face min y - ::glPushMatrix(); - ::glTranslatef(0.0f, -(GLfloat)half_size, 0.0f); - ::glRotatef(90.0f, 1.0f, 0.0f, 0.0f); - render_face(half_size); - ::glPopMatrix(); - - // face max y - ::glPushMatrix(); - ::glTranslatef(0.0f, (GLfloat)half_size, 0.0f); - ::glRotatef(-90.0f, 1.0f, 0.0f, 0.0f); - render_face(half_size); - ::glPopMatrix(); - - // face min z - ::glPushMatrix(); - ::glTranslatef(0.0f, 0.0f, -(GLfloat)half_size); - ::glRotatef(180.0f, 1.0f, 0.0f, 0.0f); - render_face(half_size); - ::glPopMatrix(); - - // face max z - ::glPushMatrix(); - ::glTranslatef(0.0f, 0.0f, (GLfloat)half_size); - render_face(half_size); - ::glPopMatrix(); - - ::glPopMatrix(); - - if (use_lighting) - ::glDisable(GL_LIGHTING); -} - -void GLGizmoBase::Grabber::render_face(float half_size) const -{ - ::glBegin(GL_TRIANGLES); - ::glNormal3f(0.0f, 0.0f, 1.0f); - ::glVertex3f(-(GLfloat)half_size, -(GLfloat)half_size, 0.0f); - ::glVertex3f((GLfloat)half_size, -(GLfloat)half_size, 0.0f); - ::glVertex3f((GLfloat)half_size, (GLfloat)half_size, 0.0f); - ::glVertex3f((GLfloat)half_size, (GLfloat)half_size, 0.0f); - ::glVertex3f(-(GLfloat)half_size, (GLfloat)half_size, 0.0f); - ::glVertex3f(-(GLfloat)half_size, -(GLfloat)half_size, 0.0f); - ::glEnd(); -} - -#if ENABLE_SVG_ICONS -GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) -#else -GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, unsigned int sprite_id) -#endif // ENABLE_SVG_ICONS - : m_parent(parent) - , m_group_id(-1) - , m_state(Off) - , m_shortcut_key(0) -#if ENABLE_SVG_ICONS - , m_icon_filename(icon_filename) -#endif // ENABLE_SVG_ICONS - , m_sprite_id(sprite_id) - , m_hover_id(-1) - , m_dragging(false) -#if ENABLE_IMGUI - , m_imgui(wxGetApp().imgui()) -#endif // ENABLE_IMGUI -{ - ::memcpy((void*)m_base_color, (const void*)DEFAULT_BASE_COLOR, 3 * sizeof(float)); - ::memcpy((void*)m_drag_color, (const void*)DEFAULT_DRAG_COLOR, 3 * sizeof(float)); - ::memcpy((void*)m_highlight_color, (const void*)DEFAULT_HIGHLIGHT_COLOR, 3 * sizeof(float)); -} - -void GLGizmoBase::set_hover_id(int id) -{ - if (m_grabbers.empty() || (id < (int)m_grabbers.size())) - { - m_hover_id = id; - on_set_hover_id(); - } -} - -void GLGizmoBase::set_highlight_color(const float* color) -{ - if (color != nullptr) - ::memcpy((void*)m_highlight_color, (const void*)color, 3 * sizeof(float)); -} - -void GLGizmoBase::enable_grabber(unsigned int id) -{ - if ((0 <= id) && (id < (unsigned int)m_grabbers.size())) - m_grabbers[id].enabled = true; - - on_enable_grabber(id); -} - -void GLGizmoBase::disable_grabber(unsigned int id) -{ - if ((0 <= id) && (id < (unsigned int)m_grabbers.size())) - m_grabbers[id].enabled = false; - - on_disable_grabber(id); -} - -void GLGizmoBase::start_dragging(const GLCanvas3D::Selection& selection) -{ - m_dragging = true; - - for (int i = 0; i < (int)m_grabbers.size(); ++i) - { - m_grabbers[i].dragging = (m_hover_id == i); - } - - on_start_dragging(selection); -} - -void GLGizmoBase::stop_dragging() -{ - m_dragging = false; - - for (int i = 0; i < (int)m_grabbers.size(); ++i) - { - m_grabbers[i].dragging = false; - } - - on_stop_dragging(); -} - -void GLGizmoBase::update(const UpdateData& data, const GLCanvas3D::Selection& selection) -{ - if (m_hover_id != -1) - on_update(data, selection); -} - -float GLGizmoBase::picking_color_component(unsigned int id) const -{ - int color = 254 - (int)id; - if (m_group_id > -1) - color -= m_group_id; - - return (float)color / 255.0f; -} - -void GLGizmoBase::render_grabbers(const BoundingBoxf3& box) const -{ - float size = (float)box.max_size(); - - for (int i = 0; i < (int)m_grabbers.size(); ++i) - { - if (m_grabbers[i].enabled) - m_grabbers[i].render((m_hover_id == i), size); - } -} - -void GLGizmoBase::render_grabbers(float size) const -{ - for (int i = 0; i < (int)m_grabbers.size(); ++i) - { - if (m_grabbers[i].enabled) - m_grabbers[i].render((m_hover_id == i), size); - } -} - -void GLGizmoBase::render_grabbers_for_picking(const BoundingBoxf3& box) const -{ - float size = (float)box.max_size(); - - for (unsigned int i = 0; i < (unsigned int)m_grabbers.size(); ++i) - { - if (m_grabbers[i].enabled) - { - m_grabbers[i].color[0] = 1.0f; - m_grabbers[i].color[1] = 1.0f; - m_grabbers[i].color[2] = picking_color_component(i); - m_grabbers[i].render_for_picking(size); - } - } -} - -#if !ENABLE_IMGUI -void GLGizmoBase::create_external_gizmo_widgets(wxWindow *parent) {} -#endif // not ENABLE_IMGUI - -void GLGizmoBase::set_tooltip(const std::string& tooltip) const -{ - m_parent.set_tooltip(tooltip); -} - -std::string GLGizmoBase::format(float value, unsigned int decimals) const -{ - return Slic3r::string_printf("%.*f", decimals, value); -} - -const float GLGizmoRotate::Offset = 5.0f; -const unsigned int GLGizmoRotate::CircleResolution = 64; -const unsigned int GLGizmoRotate::AngleResolution = 64; -const unsigned int GLGizmoRotate::ScaleStepsCount = 72; -const float GLGizmoRotate::ScaleStepRad = 2.0f * (float)PI / GLGizmoRotate::ScaleStepsCount; -const unsigned int GLGizmoRotate::ScaleLongEvery = 2; -const float GLGizmoRotate::ScaleLongTooth = 0.1f; // in percent of radius -const unsigned int GLGizmoRotate::SnapRegionsCount = 8; -const float GLGizmoRotate::GrabberOffset = 0.15f; // in percent of radius - -GLGizmoRotate::GLGizmoRotate(GLCanvas3D& parent, GLGizmoRotate::Axis axis) -#if ENABLE_SVG_ICONS - : GLGizmoBase(parent, "", -1) -#else - : GLGizmoBase(parent, -1) -#endif // ENABLE_SVG_ICONS - , m_axis(axis) - , m_angle(0.0) - , m_quadric(nullptr) - , m_center(0.0, 0.0, 0.0) - , m_radius(0.0f) - , m_snap_coarse_in_radius(0.0f) - , m_snap_coarse_out_radius(0.0f) - , m_snap_fine_in_radius(0.0f) - , m_snap_fine_out_radius(0.0f) -{ - m_quadric = ::gluNewQuadric(); - if (m_quadric != nullptr) - ::gluQuadricDrawStyle(m_quadric, GLU_FILL); -} - -GLGizmoRotate::GLGizmoRotate(const GLGizmoRotate& other) -#if ENABLE_SVG_ICONS - : GLGizmoBase(other.m_parent, other.m_icon_filename, other.m_sprite_id) -#else - : GLGizmoBase(other.m_parent, other.m_sprite_id) -#endif // ENABLE_SVG_ICONS - , m_axis(other.m_axis) - , m_angle(other.m_angle) - , m_quadric(nullptr) - , m_center(other.m_center) - , m_radius(other.m_radius) - , m_snap_coarse_in_radius(other.m_snap_coarse_in_radius) - , m_snap_coarse_out_radius(other.m_snap_coarse_out_radius) - , m_snap_fine_in_radius(other.m_snap_fine_in_radius) - , m_snap_fine_out_radius(other.m_snap_fine_out_radius) -{ - m_quadric = ::gluNewQuadric(); - if (m_quadric != nullptr) - ::gluQuadricDrawStyle(m_quadric, GLU_FILL); -} - -GLGizmoRotate::~GLGizmoRotate() -{ - if (m_quadric != nullptr) - ::gluDeleteQuadric(m_quadric); -} - -void GLGizmoRotate::set_angle(double angle) -{ - if (std::abs(angle - 2.0 * (double)PI) < EPSILON) - angle = 0.0; - - m_angle = angle; -} - -bool GLGizmoRotate::on_init() -{ - m_grabbers.push_back(Grabber()); - return true; -} - -void GLGizmoRotate::on_start_dragging(const GLCanvas3D::Selection& selection) -{ - const BoundingBoxf3& box = selection.get_bounding_box(); - m_center = box.center(); - m_radius = Offset + box.radius(); - m_snap_coarse_in_radius = m_radius / 3.0f; - m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius; - m_snap_fine_in_radius = m_radius; - m_snap_fine_out_radius = m_snap_fine_in_radius + m_radius * ScaleLongTooth; -} - -void GLGizmoRotate::on_update(const UpdateData& data, const GLCanvas3D::Selection& selection) -{ - Vec2d mouse_pos = to_2d(mouse_position_in_local_plane(data.mouse_ray, selection)); - - Vec2d orig_dir = Vec2d::UnitX(); - Vec2d new_dir = mouse_pos.normalized(); - - double theta = ::acos(clamp(-1.0, 1.0, new_dir.dot(orig_dir))); - if (cross2(orig_dir, new_dir) < 0.0) - theta = 2.0 * (double)PI - theta; - - double len = mouse_pos.norm(); - - // snap to coarse snap region - if ((m_snap_coarse_in_radius <= len) && (len <= m_snap_coarse_out_radius)) - { - double step = 2.0 * (double)PI / (double)SnapRegionsCount; - theta = step * (double)std::round(theta / step); - } - else - { - // snap to fine snap region (scale) - if ((m_snap_fine_in_radius <= len) && (len <= m_snap_fine_out_radius)) - { - double step = 2.0 * (double)PI / (double)ScaleStepsCount; - theta = step * (double)std::round(theta / step); - } - } - - if (theta == 2.0 * (double)PI) - theta = 0.0; - - m_angle = theta; -} - -void GLGizmoRotate::on_render(const GLCanvas3D::Selection& selection) const -{ - if (!m_grabbers[0].enabled) - return; - - const BoundingBoxf3& box = selection.get_bounding_box(); - - std::string axis; - switch (m_axis) - { - case X: { axis = "X"; break; } - case Y: { axis = "Y"; break; } - case Z: { axis = "Z"; break; } - } - - if (!m_dragging && (m_hover_id == 0)) - set_tooltip(axis); - else if (m_dragging) - set_tooltip(axis + ": " + format((float)Geometry::rad2deg(m_angle), 4) + "\u00B0"); - else - { - m_center = box.center(); - m_radius = Offset + box.radius(); - m_snap_coarse_in_radius = m_radius / 3.0f; - m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius; - m_snap_fine_in_radius = m_radius; - m_snap_fine_out_radius = m_radius * (1.0f + ScaleLongTooth); - } - - ::glEnable(GL_DEPTH_TEST); - - ::glPushMatrix(); - transform_to_local(selection); - - ::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f); - ::glColor3fv((m_hover_id != -1) ? m_drag_color : m_highlight_color); - - render_circle(); - - if (m_hover_id != -1) - { - render_scale(); - render_snap_radii(); - render_reference_radius(); - } - - ::glColor3fv(m_highlight_color); - - if (m_hover_id != -1) - render_angle(); - - render_grabber(box); - render_grabber_extension(box, false); - - ::glPopMatrix(); -} - -void GLGizmoRotate::on_render_for_picking(const GLCanvas3D::Selection& selection) const -{ - ::glDisable(GL_DEPTH_TEST); - - ::glPushMatrix(); - - transform_to_local(selection); - - const BoundingBoxf3& box = selection.get_bounding_box(); - render_grabbers_for_picking(box); - render_grabber_extension(box, true); - - ::glPopMatrix(); -} - -void GLGizmoRotate::render_circle() const -{ - ::glBegin(GL_LINE_LOOP); - for (unsigned int i = 0; i < ScaleStepsCount; ++i) - { - float angle = (float)i * ScaleStepRad; - float x = ::cos(angle) * m_radius; - float y = ::sin(angle) * m_radius; - float z = 0.0f; - ::glVertex3f((GLfloat)x, (GLfloat)y, (GLfloat)z); - } - ::glEnd(); -} - -void GLGizmoRotate::render_scale() const -{ - float out_radius_long = m_snap_fine_out_radius; - float out_radius_short = m_radius * (1.0f + 0.5f * ScaleLongTooth); - - ::glBegin(GL_LINES); - for (unsigned int i = 0; i < ScaleStepsCount; ++i) - { - float angle = (float)i * ScaleStepRad; - float cosa = ::cos(angle); - float sina = ::sin(angle); - float in_x = cosa * m_radius; - float in_y = sina * m_radius; - float in_z = 0.0f; - float out_x = (i % ScaleLongEvery == 0) ? cosa * out_radius_long : cosa * out_radius_short; - float out_y = (i % ScaleLongEvery == 0) ? sina * out_radius_long : sina * out_radius_short; - float out_z = 0.0f; - ::glVertex3f((GLfloat)in_x, (GLfloat)in_y, (GLfloat)in_z); - ::glVertex3f((GLfloat)out_x, (GLfloat)out_y, (GLfloat)out_z); - } - ::glEnd(); -} - -void GLGizmoRotate::render_snap_radii() const -{ - float step = 2.0f * (float)PI / (float)SnapRegionsCount; - - float in_radius = m_radius / 3.0f; - float out_radius = 2.0f * in_radius; - - ::glBegin(GL_LINES); - for (unsigned int i = 0; i < SnapRegionsCount; ++i) - { - float angle = (float)i * step; - float cosa = ::cos(angle); - float sina = ::sin(angle); - float in_x = cosa * in_radius; - float in_y = sina * in_radius; - float in_z = 0.0f; - float out_x = cosa * out_radius; - float out_y = sina * out_radius; - float out_z = 0.0f; - ::glVertex3f((GLfloat)in_x, (GLfloat)in_y, (GLfloat)in_z); - ::glVertex3f((GLfloat)out_x, (GLfloat)out_y, (GLfloat)out_z); - } - ::glEnd(); -} - -void GLGizmoRotate::render_reference_radius() const -{ - ::glBegin(GL_LINES); - ::glVertex3f(0.0f, 0.0f, 0.0f); - ::glVertex3f((GLfloat)(m_radius * (1.0f + GrabberOffset)), 0.0f, 0.0f); - ::glEnd(); -} - -void GLGizmoRotate::render_angle() const -{ - float step_angle = (float)m_angle / AngleResolution; - float ex_radius = m_radius * (1.0f + GrabberOffset); - - ::glBegin(GL_LINE_STRIP); - for (unsigned int i = 0; i <= AngleResolution; ++i) - { - float angle = (float)i * step_angle; - float x = ::cos(angle) * ex_radius; - float y = ::sin(angle) * ex_radius; - float z = 0.0f; - ::glVertex3f((GLfloat)x, (GLfloat)y, (GLfloat)z); - } - ::glEnd(); -} - -void GLGizmoRotate::render_grabber(const BoundingBoxf3& box) const -{ - double grabber_radius = (double)m_radius * (1.0 + (double)GrabberOffset); - m_grabbers[0].center = Vec3d(::cos(m_angle) * grabber_radius, ::sin(m_angle) * grabber_radius, 0.0); - m_grabbers[0].angles(2) = m_angle; - - ::glColor3fv((m_hover_id != -1) ? m_drag_color : m_highlight_color); - - ::glBegin(GL_LINES); - ::glVertex3f(0.0f, 0.0f, 0.0f); - ::glVertex3dv(m_grabbers[0].center.data()); - ::glEnd(); - - ::memcpy((void*)m_grabbers[0].color, (const void*)m_highlight_color, 3 * sizeof(float)); - render_grabbers(box); -} - -void GLGizmoRotate::render_grabber_extension(const BoundingBoxf3& box, bool picking) const -{ - if (m_quadric == nullptr) - return; - - double size = m_dragging ? (double)m_grabbers[0].get_dragging_half_size((float)box.max_size()) : (double)m_grabbers[0].get_half_size((float)box.max_size()); - - float color[3]; - ::memcpy((void*)color, (const void*)m_grabbers[0].color, 3 * sizeof(float)); - if (!picking && (m_hover_id != -1)) - { - color[0] = 1.0f - color[0]; - color[1] = 1.0f - color[1]; - color[2] = 1.0f - color[2]; - } - - if (!picking) - ::glEnable(GL_LIGHTING); - - ::glColor3fv(color); - ::glPushMatrix(); - ::glTranslated(m_grabbers[0].center(0), m_grabbers[0].center(1), m_grabbers[0].center(2)); - ::glRotated(Geometry::rad2deg(m_angle), 0.0, 0.0, 1.0); - ::glRotated(90.0, 1.0, 0.0, 0.0); - ::glTranslated(0.0, 0.0, 2.0 * size); - ::gluQuadricOrientation(m_quadric, GLU_OUTSIDE); - ::gluCylinder(m_quadric, 0.75 * size, 0.0, 3.0 * size, 36, 1); - ::gluQuadricOrientation(m_quadric, GLU_INSIDE); - ::gluDisk(m_quadric, 0.0, 0.75 * size, 36, 1); - ::glPopMatrix(); - ::glPushMatrix(); - ::glTranslated(m_grabbers[0].center(0), m_grabbers[0].center(1), m_grabbers[0].center(2)); - ::glRotated(Geometry::rad2deg(m_angle), 0.0, 0.0, 1.0); - ::glRotated(-90.0, 1.0, 0.0, 0.0); - ::glTranslated(0.0, 0.0, 2.0 * size); - ::gluQuadricOrientation(m_quadric, GLU_OUTSIDE); - ::gluCylinder(m_quadric, 0.75 * size, 0.0, 3.0 * size, 36, 1); - ::gluQuadricOrientation(m_quadric, GLU_INSIDE); - ::gluDisk(m_quadric, 0.0, 0.75 * size, 36, 1); - ::glPopMatrix(); - - if (!picking) - ::glDisable(GL_LIGHTING); -} - -void GLGizmoRotate::transform_to_local(const GLCanvas3D::Selection& selection) const -{ - ::glTranslated(m_center(0), m_center(1), m_center(2)); - - if (selection.is_single_volume() || selection.is_single_modifier() || selection.requires_local_axes()) - { - Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); - ::glMultMatrixd(orient_matrix.data()); - } - - switch (m_axis) - { - case X: - { - ::glRotatef(90.0f, 0.0f, 1.0f, 0.0f); - ::glRotatef(-90.0f, 0.0f, 0.0f, 1.0f); - break; - } - case Y: - { - ::glRotatef(-90.0f, 0.0f, 0.0f, 1.0f); - ::glRotatef(-90.0f, 0.0f, 1.0f, 0.0f); - break; - } - default: - case Z: - { - // no rotation - break; - } - } -} - -Vec3d GLGizmoRotate::mouse_position_in_local_plane(const Linef3& mouse_ray, const GLCanvas3D::Selection& selection) const -{ - double half_pi = 0.5 * (double)PI; - - Transform3d m = Transform3d::Identity(); - - switch (m_axis) - { - case X: - { - m.rotate(Eigen::AngleAxisd(half_pi, Vec3d::UnitZ())); - m.rotate(Eigen::AngleAxisd(-half_pi, Vec3d::UnitY())); - break; - } - case Y: - { - m.rotate(Eigen::AngleAxisd(half_pi, Vec3d::UnitY())); - m.rotate(Eigen::AngleAxisd(half_pi, Vec3d::UnitZ())); - break; - } - default: - case Z: - { - // no rotation applied - break; - } - } - - if (selection.is_single_volume() || selection.is_single_modifier() || selection.requires_local_axes()) - m = m * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true).inverse(); - - m.translate(-m_center); - - return transform(mouse_ray, m).intersect_plane(0.0); -} - -#if ENABLE_SVG_ICONS -GLGizmoRotate3D::GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) - : GLGizmoBase(parent, icon_filename, sprite_id) -#else -GLGizmoRotate3D::GLGizmoRotate3D(GLCanvas3D& parent, unsigned int sprite_id) - : GLGizmoBase(parent, sprite_id) -#endif // ENABLE_SVG_ICONS -{ - m_gizmos.emplace_back(parent, GLGizmoRotate::X); - m_gizmos.emplace_back(parent, GLGizmoRotate::Y); - m_gizmos.emplace_back(parent, GLGizmoRotate::Z); - - for (unsigned int i = 0; i < 3; ++i) - { - m_gizmos[i].set_group_id(i); - } -} - -bool GLGizmoRotate3D::on_init() -{ - for (GLGizmoRotate& g : m_gizmos) - { - if (!g.init()) - return false; - } - - for (unsigned int i = 0; i < 3; ++i) - { - m_gizmos[i].set_highlight_color(AXES_COLOR[i]); - } - - m_shortcut_key = WXK_CONTROL_R; - - return true; -} - -std::string GLGizmoRotate3D::on_get_name() const -{ - return L("Rotate [R]"); -} - -void GLGizmoRotate3D::on_start_dragging(const GLCanvas3D::Selection& selection) -{ - if ((0 <= m_hover_id) && (m_hover_id < 3)) - m_gizmos[m_hover_id].start_dragging(selection); -} - -void GLGizmoRotate3D::on_stop_dragging() -{ - if ((0 <= m_hover_id) && (m_hover_id < 3)) - m_gizmos[m_hover_id].stop_dragging(); -} - -void GLGizmoRotate3D::on_render(const GLCanvas3D::Selection& selection) const -{ - ::glClear(GL_DEPTH_BUFFER_BIT); - - if ((m_hover_id == -1) || (m_hover_id == 0)) - m_gizmos[X].render(selection); - - if ((m_hover_id == -1) || (m_hover_id == 1)) - m_gizmos[Y].render(selection); - - if ((m_hover_id == -1) || (m_hover_id == 2)) - m_gizmos[Z].render(selection); -} - -#if ENABLE_IMGUI -void GLGizmoRotate3D::on_render_input_window(float x, float y, float bottom_limit, const GLCanvas3D::Selection& selection) -{ -#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI - Vec3d rotation(Geometry::rad2deg(m_gizmos[0].get_angle()), Geometry::rad2deg(m_gizmos[1].get_angle()), Geometry::rad2deg(m_gizmos[2].get_angle())); - wxString label = _(L("Rotation (deg)")); - - m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); - m_imgui->set_next_window_bg_alpha(0.5f); - m_imgui->begin(label, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - m_imgui->input_vec3("", rotation, 100.0f, "%.2f"); - m_imgui->end(); -#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI -} -#endif // ENABLE_IMGUI - -const float GLGizmoScale3D::Offset = 5.0f; - -#if ENABLE_SVG_ICONS -GLGizmoScale3D::GLGizmoScale3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) - : GLGizmoBase(parent, icon_filename, sprite_id) -#else -GLGizmoScale3D::GLGizmoScale3D(GLCanvas3D& parent, unsigned int sprite_id) - : GLGizmoBase(parent, sprite_id) -#endif // ENABLE_SVG_ICONS - , m_scale(Vec3d::Ones()) - , m_snap_step(0.05) - , m_starting_scale(Vec3d::Ones()) -{ -} - -bool GLGizmoScale3D::on_init() -{ - for (int i = 0; i < 10; ++i) - { - m_grabbers.push_back(Grabber()); - } - - double half_pi = 0.5 * (double)PI; - - // x axis - m_grabbers[0].angles(1) = half_pi; - m_grabbers[1].angles(1) = half_pi; - - // y axis - m_grabbers[2].angles(0) = half_pi; - m_grabbers[3].angles(0) = half_pi; - - m_shortcut_key = WXK_CONTROL_S; - - return true; -} - -std::string GLGizmoScale3D::on_get_name() const -{ - return L("Scale [S]"); -} - -void GLGizmoScale3D::on_start_dragging(const GLCanvas3D::Selection& selection) -{ - if (m_hover_id != -1) - { - m_starting_drag_position = m_grabbers[m_hover_id].center; - m_starting_box = selection.get_bounding_box(); - } -} - -void GLGizmoScale3D::on_update(const UpdateData& data, const GLCanvas3D::Selection& selection) -{ - if ((m_hover_id == 0) || (m_hover_id == 1)) - do_scale_x(data); - else if ((m_hover_id == 2) || (m_hover_id == 3)) - do_scale_y(data); - else if ((m_hover_id == 4) || (m_hover_id == 5)) - do_scale_z(data); - else if (m_hover_id >= 6) - do_scale_uniform(data); -} - -void GLGizmoScale3D::on_render(const GLCanvas3D::Selection& selection) const -{ - bool single_instance = selection.is_single_full_instance(); - bool single_volume = selection.is_single_modifier() || selection.is_single_volume(); - bool single_selection = single_instance || single_volume; - - Vec3f scale = 100.0f * Vec3f::Ones(); - if (single_instance) - scale = 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor().cast<float>(); - else if (single_volume) - scale = 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_scaling_factor().cast<float>(); - - if ((single_selection && ((m_hover_id == 0) || (m_hover_id == 1))) || m_grabbers[0].dragging || m_grabbers[1].dragging) - set_tooltip("X: " + format(scale(0), 4) + "%"); - else if (!m_grabbers[0].dragging && !m_grabbers[1].dragging && ((m_hover_id == 0) || (m_hover_id == 1))) - set_tooltip("X"); - else if ((single_selection && ((m_hover_id == 2) || (m_hover_id == 3))) || m_grabbers[2].dragging || m_grabbers[3].dragging) - set_tooltip("Y: " + format(scale(1), 4) + "%"); - else if (!m_grabbers[2].dragging && !m_grabbers[3].dragging && ((m_hover_id == 2) || (m_hover_id == 3))) - set_tooltip("Y"); - else if ((single_selection && ((m_hover_id == 4) || (m_hover_id == 5))) || m_grabbers[4].dragging || m_grabbers[5].dragging) - set_tooltip("Z: " + format(scale(2), 4) + "%"); - else if (!m_grabbers[4].dragging && !m_grabbers[5].dragging && ((m_hover_id == 4) || (m_hover_id == 5))) - set_tooltip("Z"); - else if ((single_selection && ((m_hover_id == 6) || (m_hover_id == 7) || (m_hover_id == 8) || (m_hover_id == 9))) - || m_grabbers[6].dragging || m_grabbers[7].dragging || m_grabbers[8].dragging || m_grabbers[9].dragging) - { - std::string tooltip = "X: " + format(scale(0), 4) + "%\n"; - tooltip += "Y: " + format(scale(1), 4) + "%\n"; - tooltip += "Z: " + format(scale(2), 4) + "%"; - set_tooltip(tooltip); - } - else if (!m_grabbers[6].dragging && !m_grabbers[7].dragging && !m_grabbers[8].dragging && !m_grabbers[9].dragging && - ((m_hover_id == 6) || (m_hover_id == 7) || (m_hover_id == 8) || (m_hover_id == 9))) - set_tooltip("X/Y/Z"); - - ::glClear(GL_DEPTH_BUFFER_BIT); - ::glEnable(GL_DEPTH_TEST); - - BoundingBoxf3 box; - Transform3d transform = Transform3d::Identity(); - Vec3d angles = Vec3d::Zero(); - Transform3d offsets_transform = Transform3d::Identity(); - - Vec3d grabber_size = Vec3d::Zero(); - - if (single_instance) - { - // calculate bounding box in instance local reference system - const GLCanvas3D::Selection::IndicesList& idxs = selection.get_volume_idxs(); - for (unsigned int idx : idxs) - { - const GLVolume* vol = selection.get_volume(idx); - box.merge(vol->bounding_box.transformed(vol->get_volume_transformation().get_matrix())); - } - - // gets transform from first selected volume - const GLVolume* v = selection.get_volume(*idxs.begin()); - transform = v->get_instance_transformation().get_matrix(); - // gets angles from first selected volume - angles = v->get_instance_rotation(); - // consider rotation+mirror only components of the transform for offsets - offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_instance_mirror()); - grabber_size = v->get_instance_transformation().get_matrix(true, true, false, true) * box.size(); - } - else if (single_volume) - { - const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); - box = v->bounding_box; - transform = v->world_matrix(); - angles = Geometry::extract_euler_angles(transform); - // consider rotation+mirror only components of the transform for offsets - offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_instance_mirror()); - grabber_size = v->get_volume_transformation().get_matrix(true, true, false, true) * box.size(); - } - else - { - box = selection.get_bounding_box(); - grabber_size = box.size(); - } - - m_box = box; - - const Vec3d& center = m_box.center(); - Vec3d offset_x = offsets_transform * Vec3d((double)Offset, 0.0, 0.0); - Vec3d offset_y = offsets_transform * Vec3d(0.0, (double)Offset, 0.0); - Vec3d offset_z = offsets_transform * Vec3d(0.0, 0.0, (double)Offset); - - // x axis - m_grabbers[0].center = transform * Vec3d(m_box.min(0), center(1), center(2)) - offset_x; - m_grabbers[1].center = transform * Vec3d(m_box.max(0), center(1), center(2)) + offset_x; - ::memcpy((void*)m_grabbers[0].color, (const void*)&AXES_COLOR[0], 3 * sizeof(float)); - ::memcpy((void*)m_grabbers[1].color, (const void*)&AXES_COLOR[0], 3 * sizeof(float)); - - // y axis - m_grabbers[2].center = transform * Vec3d(center(0), m_box.min(1), center(2)) - offset_y; - m_grabbers[3].center = transform * Vec3d(center(0), m_box.max(1), center(2)) + offset_y; - ::memcpy((void*)m_grabbers[2].color, (const void*)&AXES_COLOR[1], 3 * sizeof(float)); - ::memcpy((void*)m_grabbers[3].color, (const void*)&AXES_COLOR[1], 3 * sizeof(float)); - - // z axis - m_grabbers[4].center = transform * Vec3d(center(0), center(1), m_box.min(2)) - offset_z; - m_grabbers[5].center = transform * Vec3d(center(0), center(1), m_box.max(2)) + offset_z; - ::memcpy((void*)m_grabbers[4].color, (const void*)&AXES_COLOR[2], 3 * sizeof(float)); - ::memcpy((void*)m_grabbers[5].color, (const void*)&AXES_COLOR[2], 3 * sizeof(float)); - - // uniform - m_grabbers[6].center = transform * Vec3d(m_box.min(0), m_box.min(1), center(2)) - offset_x - offset_y; - m_grabbers[7].center = transform * Vec3d(m_box.max(0), m_box.min(1), center(2)) + offset_x - offset_y; - m_grabbers[8].center = transform * Vec3d(m_box.max(0), m_box.max(1), center(2)) + offset_x + offset_y; - m_grabbers[9].center = transform * Vec3d(m_box.min(0), m_box.max(1), center(2)) - offset_x + offset_y; - for (int i = 6; i < 10; ++i) - { - ::memcpy((void*)m_grabbers[i].color, (const void*)m_highlight_color, 3 * sizeof(float)); - } - - // sets grabbers orientation - for (int i = 0; i < 10; ++i) - { - m_grabbers[i].angles = angles; - } - - ::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f); - - float grabber_mean_size = (float)(grabber_size(0) + grabber_size(1) + grabber_size(2)) / 3.0f; - - if (m_hover_id == -1) - { - // draw connections - if (m_grabbers[0].enabled && m_grabbers[1].enabled) - { - ::glColor3fv(m_grabbers[0].color); - render_grabbers_connection(0, 1); - } - if (m_grabbers[2].enabled && m_grabbers[3].enabled) - { - ::glColor3fv(m_grabbers[2].color); - render_grabbers_connection(2, 3); - } - if (m_grabbers[4].enabled && m_grabbers[5].enabled) - { - ::glColor3fv(m_grabbers[4].color); - render_grabbers_connection(4, 5); - } - ::glColor3fv(m_base_color); - render_grabbers_connection(6, 7); - render_grabbers_connection(7, 8); - render_grabbers_connection(8, 9); - render_grabbers_connection(9, 6); - // draw grabbers - render_grabbers(grabber_mean_size); - } - else if ((m_hover_id == 0) || (m_hover_id == 1)) - { - // draw connection - ::glColor3fv(m_grabbers[0].color); - render_grabbers_connection(0, 1); - // draw grabbers - m_grabbers[0].render(true, grabber_mean_size); - m_grabbers[1].render(true, grabber_mean_size); - } - else if ((m_hover_id == 2) || (m_hover_id == 3)) - { - // draw connection - ::glColor3fv(m_grabbers[2].color); - render_grabbers_connection(2, 3); - // draw grabbers - m_grabbers[2].render(true, grabber_mean_size); - m_grabbers[3].render(true, grabber_mean_size); - } - else if ((m_hover_id == 4) || (m_hover_id == 5)) - { - // draw connection - ::glColor3fv(m_grabbers[4].color); - render_grabbers_connection(4, 5); - // draw grabbers - m_grabbers[4].render(true, grabber_mean_size); - m_grabbers[5].render(true, grabber_mean_size); - } - else if (m_hover_id >= 6) - { - // draw connection - ::glColor3fv(m_drag_color); - render_grabbers_connection(6, 7); - render_grabbers_connection(7, 8); - render_grabbers_connection(8, 9); - render_grabbers_connection(9, 6); - // draw grabbers - for (int i = 6; i < 10; ++i) - { - m_grabbers[i].render(true, grabber_mean_size); - } - } -} - -void GLGizmoScale3D::on_render_for_picking(const GLCanvas3D::Selection& selection) const -{ - ::glDisable(GL_DEPTH_TEST); - - render_grabbers_for_picking(selection.get_bounding_box()); -} - -#if ENABLE_IMGUI -void GLGizmoScale3D::on_render_input_window(float x, float y, float bottom_limit, const GLCanvas3D::Selection& selection) -{ -#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI - bool single_instance = selection.is_single_full_instance(); - wxString label = _(L("Scale (%)")); - - m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); - m_imgui->set_next_window_bg_alpha(0.5f); - m_imgui->begin(label, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - m_imgui->input_vec3("", m_scale * 100.f, 100.0f, "%.2f"); - m_imgui->end(); -#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI -} -#endif // ENABLE_IMGUI - -void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int id_2) const -{ - unsigned int grabbers_count = (unsigned int)m_grabbers.size(); - if ((id_1 < grabbers_count) && (id_2 < grabbers_count)) - { - ::glBegin(GL_LINES); - ::glVertex3dv(m_grabbers[id_1].center.data()); - ::glVertex3dv(m_grabbers[id_2].center.data()); - ::glEnd(); - } -} - -void GLGizmoScale3D::do_scale_x(const UpdateData& data) -{ - double ratio = calc_ratio(data); - if (ratio > 0.0) - m_scale(0) = m_starting_scale(0) * ratio; -} - -void GLGizmoScale3D::do_scale_y(const UpdateData& data) -{ - double ratio = calc_ratio(data); - if (ratio > 0.0) - m_scale(1) = m_starting_scale(1) * ratio; -} - -void GLGizmoScale3D::do_scale_z(const UpdateData& data) -{ - double ratio = calc_ratio(data); - if (ratio > 0.0) - m_scale(2) = m_starting_scale(2) * ratio; -} - -void GLGizmoScale3D::do_scale_uniform(const UpdateData& data) -{ - double ratio = calc_ratio(data); - if (ratio > 0.0) - m_scale = m_starting_scale * ratio; -} - -double GLGizmoScale3D::calc_ratio(const UpdateData& data) const -{ - double ratio = 0.0; - - // vector from the center to the starting position - Vec3d starting_vec = m_starting_drag_position - m_starting_box.center(); - double len_starting_vec = starting_vec.norm(); - if (len_starting_vec != 0.0) - { - Vec3d mouse_dir = data.mouse_ray.unit_vector(); - // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position - // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form - // in our case plane normal and ray direction are the same (orthogonal view) - // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal - Vec3d inters = data.mouse_ray.a + (m_starting_drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; - // vector from the starting position to the found intersection - Vec3d inters_vec = inters - m_starting_drag_position; - - // finds projection of the vector along the staring direction - double proj = inters_vec.dot(starting_vec.normalized()); - - ratio = (len_starting_vec + proj) / len_starting_vec; - } - - if (data.shift_down) - ratio = m_snap_step * (double)std::round(ratio / m_snap_step); - - return ratio; -} - -const double GLGizmoMove3D::Offset = 10.0; - -#if ENABLE_SVG_ICONS -GLGizmoMove3D::GLGizmoMove3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) - : GLGizmoBase(parent, icon_filename, sprite_id) -#else -GLGizmoMove3D::GLGizmoMove3D(GLCanvas3D& parent, unsigned int sprite_id) - : GLGizmoBase(parent, sprite_id) -#endif // ENABLE_SVG_ICONS - , m_displacement(Vec3d::Zero()) - , m_snap_step(1.0) - , m_starting_drag_position(Vec3d::Zero()) - , m_starting_box_center(Vec3d::Zero()) - , m_starting_box_bottom_center(Vec3d::Zero()) - , m_quadric(nullptr) -{ - m_quadric = ::gluNewQuadric(); - if (m_quadric != nullptr) - ::gluQuadricDrawStyle(m_quadric, GLU_FILL); -} - -GLGizmoMove3D::~GLGizmoMove3D() -{ - if (m_quadric != nullptr) - ::gluDeleteQuadric(m_quadric); -} - -bool GLGizmoMove3D::on_init() -{ - for (int i = 0; i < 3; ++i) - { - m_grabbers.push_back(Grabber()); - } - - m_shortcut_key = WXK_CONTROL_M; - - return true; -} - -std::string GLGizmoMove3D::on_get_name() const -{ - return L("Move [M]"); -} - -void GLGizmoMove3D::on_start_dragging(const GLCanvas3D::Selection& selection) -{ - if (m_hover_id != -1) - { - m_displacement = Vec3d::Zero(); - const BoundingBoxf3& box = selection.get_bounding_box(); - m_starting_drag_position = m_grabbers[m_hover_id].center; - m_starting_box_center = box.center(); - m_starting_box_bottom_center = box.center(); - m_starting_box_bottom_center(2) = box.min(2); - } -} - -void GLGizmoMove3D::on_stop_dragging() -{ - m_displacement = Vec3d::Zero(); -} - -void GLGizmoMove3D::on_update(const UpdateData& data, const GLCanvas3D::Selection& selection) -{ - if (m_hover_id == 0) - m_displacement(0) = calc_projection(data); - else if (m_hover_id == 1) - m_displacement(1) = calc_projection(data); - else if (m_hover_id == 2) - m_displacement(2) = calc_projection(data); -} - -void GLGizmoMove3D::on_render(const GLCanvas3D::Selection& selection) const -{ - bool show_position = selection.is_single_full_instance(); - const Vec3d& position = selection.get_bounding_box().center(); - - if ((show_position && (m_hover_id == 0)) || m_grabbers[0].dragging) - set_tooltip("X: " + format(show_position ? position(0) : m_displacement(0), 2)); - else if (!m_grabbers[0].dragging && (m_hover_id == 0)) - set_tooltip("X"); - else if ((show_position && (m_hover_id == 1)) || m_grabbers[1].dragging) - set_tooltip("Y: " + format(show_position ? position(1) : m_displacement(1), 2)); - else if (!m_grabbers[1].dragging && (m_hover_id == 1)) - set_tooltip("Y"); - else if ((show_position && (m_hover_id == 2)) || m_grabbers[2].dragging) - set_tooltip("Z: " + format(show_position ? position(2) : m_displacement(2), 2)); - else if (!m_grabbers[2].dragging && (m_hover_id == 2)) - set_tooltip("Z"); - - ::glClear(GL_DEPTH_BUFFER_BIT); - ::glEnable(GL_DEPTH_TEST); - - const BoundingBoxf3& box = selection.get_bounding_box(); - const Vec3d& center = box.center(); - - // x axis - m_grabbers[0].center = Vec3d(box.max(0) + Offset, center(1), center(2)); - ::memcpy((void*)m_grabbers[0].color, (const void*)&AXES_COLOR[0], 3 * sizeof(float)); - - // y axis - m_grabbers[1].center = Vec3d(center(0), box.max(1) + Offset, center(2)); - ::memcpy((void*)m_grabbers[1].color, (const void*)&AXES_COLOR[1], 3 * sizeof(float)); - - // z axis - m_grabbers[2].center = Vec3d(center(0), center(1), box.max(2) + Offset); - ::memcpy((void*)m_grabbers[2].color, (const void*)&AXES_COLOR[2], 3 * sizeof(float)); - - ::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f); - - if (m_hover_id == -1) - { - // draw axes - for (unsigned int i = 0; i < 3; ++i) - { - if (m_grabbers[i].enabled) - { - ::glColor3fv(AXES_COLOR[i]); - ::glBegin(GL_LINES); - ::glVertex3dv(center.data()); - ::glVertex3dv(m_grabbers[i].center.data()); - ::glEnd(); - } - } - - // draw grabbers - render_grabbers(box); - for (unsigned int i = 0; i < 3; ++i) - { - if (m_grabbers[i].enabled) - render_grabber_extension((Axis)i, box, false); - } - } - else - { - // draw axis - ::glColor3fv(AXES_COLOR[m_hover_id]); - ::glBegin(GL_LINES); - ::glVertex3dv(center.data()); - ::glVertex3dv(m_grabbers[m_hover_id].center.data()); - ::glEnd(); - - // draw grabber - m_grabbers[m_hover_id].render(true, box.max_size()); - render_grabber_extension((Axis)m_hover_id, box, false); - } -} - -void GLGizmoMove3D::on_render_for_picking(const GLCanvas3D::Selection& selection) const -{ - ::glDisable(GL_DEPTH_TEST); - - const BoundingBoxf3& box = selection.get_bounding_box(); - render_grabbers_for_picking(box); - render_grabber_extension(X, box, true); - render_grabber_extension(Y, box, true); - render_grabber_extension(Z, box, true); -} - -#if ENABLE_IMGUI -void GLGizmoMove3D::on_render_input_window(float x, float y, float bottom_limit, const GLCanvas3D::Selection& selection) -{ -#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI - bool show_position = selection.is_single_full_instance(); - const Vec3d& position = selection.get_bounding_box().center(); - - Vec3d displacement = show_position ? position : m_displacement; - wxString label = show_position ? _(L("Position (mm)")) : _(L("Displacement (mm)")); - - m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); - m_imgui->set_next_window_bg_alpha(0.5f); - m_imgui->begin(label, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - m_imgui->input_vec3("", displacement, 100.0f, "%.2f"); - - m_imgui->end(); -#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI -} -#endif // ENABLE_IMGUI - -double GLGizmoMove3D::calc_projection(const UpdateData& data) const -{ - double projection = 0.0; - - Vec3d starting_vec = m_starting_drag_position - m_starting_box_center; - double len_starting_vec = starting_vec.norm(); - if (len_starting_vec != 0.0) - { - Vec3d mouse_dir = data.mouse_ray.unit_vector(); - // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position - // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form - // in our case plane normal and ray direction are the same (orthogonal view) - // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal - Vec3d inters = data.mouse_ray.a + (m_starting_drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; - // vector from the starting position to the found intersection - Vec3d inters_vec = inters - m_starting_drag_position; - - // finds projection of the vector along the staring direction - projection = inters_vec.dot(starting_vec.normalized()); - } - - if (data.shift_down) - projection = m_snap_step * (double)std::round(projection / m_snap_step); - - return projection; -} - -void GLGizmoMove3D::render_grabber_extension(Axis axis, const BoundingBoxf3& box, bool picking) const -{ - if (m_quadric == nullptr) - return; - - double size = m_dragging ? (double)m_grabbers[axis].get_dragging_half_size((float)box.max_size()) : (double)m_grabbers[axis].get_half_size((float)box.max_size()); - - float color[3]; - ::memcpy((void*)color, (const void*)m_grabbers[axis].color, 3 * sizeof(float)); - if (!picking && (m_hover_id != -1)) - { - color[0] = 1.0f - color[0]; - color[1] = 1.0f - color[1]; - color[2] = 1.0f - color[2]; - } - - if (!picking) - ::glEnable(GL_LIGHTING); - - ::glColor3fv(color); - ::glPushMatrix(); - ::glTranslated(m_grabbers[axis].center(0), m_grabbers[axis].center(1), m_grabbers[axis].center(2)); - if (axis == X) - ::glRotated(90.0, 0.0, 1.0, 0.0); - else if (axis == Y) - ::glRotated(-90.0, 1.0, 0.0, 0.0); - - ::glTranslated(0.0, 0.0, 2.0 * size); - ::gluQuadricOrientation(m_quadric, GLU_OUTSIDE); - ::gluCylinder(m_quadric, 0.75 * size, 0.0, 3.0 * size, 36, 1); - ::gluQuadricOrientation(m_quadric, GLU_INSIDE); - ::gluDisk(m_quadric, 0.0, 0.75 * size, 36, 1); - ::glPopMatrix(); - - if (!picking) - ::glDisable(GL_LIGHTING); -} - -#if ENABLE_SVG_ICONS -GLGizmoFlatten::GLGizmoFlatten(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) - : GLGizmoBase(parent, icon_filename, sprite_id) -#else -GLGizmoFlatten::GLGizmoFlatten(GLCanvas3D& parent, unsigned int sprite_id) - : GLGizmoBase(parent, sprite_id) -#endif // ENABLE_SVG_ICONS - , m_normal(Vec3d::Zero()) - , m_starting_center(Vec3d::Zero()) -{ -} - -bool GLGizmoFlatten::on_init() -{ - m_shortcut_key = WXK_CONTROL_F; - return true; -} - -std::string GLGizmoFlatten::on_get_name() const -{ - return L("Place on face [F]"); -} - -bool GLGizmoFlatten::on_is_activable(const GLCanvas3D::Selection& selection) const -{ - return selection.is_single_full_instance(); -} - -void GLGizmoFlatten::on_start_dragging(const GLCanvas3D::Selection& selection) -{ - if (m_hover_id != -1) - { - assert(m_planes_valid); - m_normal = m_planes[m_hover_id].normal; - m_starting_center = selection.get_bounding_box().center(); - } -} - -void GLGizmoFlatten::on_render(const GLCanvas3D::Selection& selection) const -{ - ::glClear(GL_DEPTH_BUFFER_BIT); - - ::glEnable(GL_DEPTH_TEST); - ::glEnable(GL_BLEND); - - if (selection.is_single_full_instance()) - { - const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(); - ::glPushMatrix(); - ::glTranslatef(0.f, 0.f, selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z()); - ::glMultMatrixd(m.data()); - if (this->is_plane_update_necessary()) - const_cast<GLGizmoFlatten*>(this)->update_planes(); - for (int i = 0; i < (int)m_planes.size(); ++i) - { - if (i == m_hover_id) - ::glColor4f(0.9f, 0.9f, 0.9f, 0.75f); - else - ::glColor4f(0.9f, 0.9f, 0.9f, 0.5f); - - ::glBegin(GL_POLYGON); - for (const Vec3d& vertex : m_planes[i].vertices) - { - ::glVertex3dv(vertex.data()); - } - ::glEnd(); - } - ::glPopMatrix(); - } - - ::glEnable(GL_CULL_FACE); - ::glDisable(GL_BLEND); -} - -void GLGizmoFlatten::on_render_for_picking(const GLCanvas3D::Selection& selection) const -{ - ::glDisable(GL_DEPTH_TEST); - ::glDisable(GL_BLEND); - - if (selection.is_single_full_instance()) - { - const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(); - ::glPushMatrix(); - ::glTranslatef(0.f, 0.f, selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z()); - ::glMultMatrixd(m.data()); - if (this->is_plane_update_necessary()) - const_cast<GLGizmoFlatten*>(this)->update_planes(); - for (int i = 0; i < (int)m_planes.size(); ++i) - { - ::glColor3f(1.0f, 1.0f, picking_color_component(i)); - ::glBegin(GL_POLYGON); - for (const Vec3d& vertex : m_planes[i].vertices) - { - ::glVertex3dv(vertex.data()); - } - ::glEnd(); - } - ::glPopMatrix(); - } - - ::glEnable(GL_CULL_FACE); -} - -void GLGizmoFlatten::set_flattening_data(const ModelObject* model_object) -{ - m_starting_center = Vec3d::Zero(); - if (m_model_object != model_object) { - m_planes.clear(); - m_planes_valid = false; - } - m_model_object = model_object; -} - -void GLGizmoFlatten::update_planes() -{ - TriangleMesh ch; - for (const ModelVolume* vol : m_model_object->volumes) - { - if (vol->type() != ModelVolumeType::MODEL_PART) - continue; - TriangleMesh vol_ch = vol->get_convex_hull(); - vol_ch.transform(vol->get_matrix()); - ch.merge(vol_ch); - } - ch = ch.convex_hull_3d(); - m_planes.clear(); - const Transform3d& inst_matrix = m_model_object->instances.front()->get_matrix(true); - - // Following constants are used for discarding too small polygons. - const float minimal_area = 5.f; // in square mm (world coordinates) - const float minimal_side = 1.f; // mm - - // Now we'll go through all the facets and append Points of facets sharing the same normal. - // This part is still performed in mesh coordinate system. - const int num_of_facets = ch.stl.stats.number_of_facets; - std::vector<int> facet_queue(num_of_facets, 0); - std::vector<bool> facet_visited(num_of_facets, false); - int facet_queue_cnt = 0; - const stl_normal* normal_ptr = nullptr; - while (1) { - // Find next unvisited triangle: - int facet_idx = 0; - for (; facet_idx < num_of_facets; ++ facet_idx) - if (!facet_visited[facet_idx]) { - facet_queue[facet_queue_cnt ++] = facet_idx; - facet_visited[facet_idx] = true; - normal_ptr = &ch.stl.facet_start[facet_idx].normal; - m_planes.emplace_back(); - break; - } - if (facet_idx == num_of_facets) - break; // Everything was visited already - - while (facet_queue_cnt > 0) { - int facet_idx = facet_queue[-- facet_queue_cnt]; - const stl_normal& this_normal = ch.stl.facet_start[facet_idx].normal; - if (std::abs(this_normal(0) - (*normal_ptr)(0)) < 0.001 && std::abs(this_normal(1) - (*normal_ptr)(1)) < 0.001 && std::abs(this_normal(2) - (*normal_ptr)(2)) < 0.001) { - stl_vertex* first_vertex = ch.stl.facet_start[facet_idx].vertex; - for (int j=0; j<3; ++j) - m_planes.back().vertices.emplace_back((double)first_vertex[j](0), (double)first_vertex[j](1), (double)first_vertex[j](2)); - - facet_visited[facet_idx] = true; - for (int j = 0; j < 3; ++ j) { - int neighbor_idx = ch.stl.neighbors_start[facet_idx].neighbor[j]; - if (! facet_visited[neighbor_idx]) - facet_queue[facet_queue_cnt ++] = neighbor_idx; - } - } - } - m_planes.back().normal = normal_ptr->cast<double>(); - - // Now we'll transform all the points into world coordinates, so that the areas, angles and distances - // make real sense. - m_planes.back().vertices = transform(m_planes.back().vertices, inst_matrix); - - // if this is a just a very small triangle, remove it to speed up further calculations (it would be rejected later anyway): - if (m_planes.back().vertices.size() == 3 && - ((m_planes.back().vertices[0] - m_planes.back().vertices[1]).norm() < minimal_side - || (m_planes.back().vertices[0] - m_planes.back().vertices[2]).norm() < minimal_side - || (m_planes.back().vertices[1] - m_planes.back().vertices[2]).norm() < minimal_side)) - m_planes.pop_back(); - } - - // Let's prepare transformation of the normal vector from mesh to instance coordinates. - Geometry::Transformation t(inst_matrix); - Vec3d scaling = t.get_scaling_factor(); - t.set_scaling_factor(Vec3d(1./scaling(0), 1./scaling(1), 1./scaling(2))); - - // Now we'll go through all the polygons, transform the points into xy plane to process them: - for (unsigned int polygon_id=0; polygon_id < m_planes.size(); ++polygon_id) { - Pointf3s& polygon = m_planes[polygon_id].vertices; - const Vec3d& normal = m_planes[polygon_id].normal; - - // transform the normal according to the instance matrix: - Vec3d normal_transformed = t.get_matrix() * normal; - - // We are going to rotate about z and y to flatten the plane - Eigen::Quaterniond q; - Transform3d m = Transform3d::Identity(); - m.matrix().block(0, 0, 3, 3) = q.setFromTwoVectors(normal_transformed, Vec3d::UnitZ()).toRotationMatrix(); - polygon = transform(polygon, m); - - // Now to remove the inner points. We'll misuse Geometry::convex_hull for that, but since - // it works in fixed point representation, we will rescale the polygon to avoid overflows. - // And yes, it is a nasty thing to do. Whoever has time is free to refactor. - Vec3d bb_size = BoundingBoxf3(polygon).size(); - float sf = std::min(1./bb_size(0), 1./bb_size(1)); - Transform3d tr = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d(sf, sf, 1.f)); - polygon = transform(polygon, tr); - polygon = Slic3r::Geometry::convex_hull(polygon); - polygon = transform(polygon, tr.inverse()); - - // Calculate area of the polygons and discard ones that are too small - float& area = m_planes[polygon_id].area; - area = 0.f; - for (unsigned int i = 0; i < polygon.size(); i++) // Shoelace formula - area += polygon[i](0)*polygon[i + 1 < polygon.size() ? i + 1 : 0](1) - polygon[i + 1 < polygon.size() ? i + 1 : 0](0)*polygon[i](1); - area = 0.5f * std::abs(area); - - bool discard = false; - if (area < minimal_area) - discard = true; - else { - // We also check the inner angles and discard polygons with angles smaller than the following threshold - const double angle_threshold = ::cos(10.0 * (double)PI / 180.0); - - for (unsigned int i = 0; i < polygon.size(); ++i) { - const Vec3d& prec = polygon[(i == 0) ? polygon.size() - 1 : i - 1]; - const Vec3d& curr = polygon[i]; - const Vec3d& next = polygon[(i == polygon.size() - 1) ? 0 : i + 1]; - - if ((prec - curr).normalized().dot((next - curr).normalized()) > angle_threshold) { - discard = true; - break; - } - } - } - - if (discard) { - m_planes.erase(m_planes.begin() + (polygon_id--)); - continue; - } - - // We will shrink the polygon a little bit so it does not touch the object edges: - Vec3d centroid = std::accumulate(polygon.begin(), polygon.end(), Vec3d(0.0, 0.0, 0.0)); - centroid /= (double)polygon.size(); - for (auto& vertex : polygon) - vertex = 0.9f*vertex + 0.1f*centroid; - - // Polygon is now simple and convex, we'll round the corners to make them look nicer. - // The algorithm takes a vertex, calculates middles of respective sides and moves the vertex - // towards their average (controlled by 'aggressivity'). This is repeated k times. - // In next iterations, the neighbours are not always taken at the middle (to increase the - // rounding effect at the corners, where we need it most). - const unsigned int k = 10; // number of iterations - const float aggressivity = 0.2f; // agressivity - const unsigned int N = polygon.size(); - std::vector<std::pair<unsigned int, unsigned int>> neighbours; - if (k != 0) { - Pointf3s points_out(2*k*N); // vector long enough to store the future vertices - for (unsigned int j=0; j<N; ++j) { - points_out[j*2*k] = polygon[j]; - neighbours.push_back(std::make_pair((int)(j*2*k-k) < 0 ? (N-1)*2*k+k : j*2*k-k, j*2*k+k)); - } - - for (unsigned int i=0; i<k; ++i) { - // Calculate middle of each edge so that neighbours points to something useful: - for (unsigned int j=0; j<N; ++j) - if (i==0) - points_out[j*2*k+k] = 0.5f * (points_out[j*2*k] + points_out[j==N-1 ? 0 : (j+1)*2*k]); - else { - float r = 0.2+0.3/(k-1)*i; // the neighbours are not always taken in the middle - points_out[neighbours[j].first] = r*points_out[j*2*k] + (1-r) * points_out[neighbours[j].first-1]; - points_out[neighbours[j].second] = r*points_out[j*2*k] + (1-r) * points_out[neighbours[j].second+1]; - } - // Now we have a triangle and valid neighbours, we can do an iteration: - for (unsigned int j=0; j<N; ++j) - points_out[2*k*j] = (1-aggressivity) * points_out[2*k*j] + - aggressivity*0.5f*(points_out[neighbours[j].first] + points_out[neighbours[j].second]); - - for (auto& n : neighbours) { - ++n.first; - --n.second; - } - } - polygon = points_out; // replace the coarse polygon with the smooth one that we just created - } - - - // Raise a bit above the object surface to avoid flickering: - for (auto& b : polygon) - b(2) += 0.1f; - - // Transform back to 3D (and also back to mesh coordinates) - polygon = transform(polygon, inst_matrix.inverse() * m.inverse()); - } - - // We'll sort the planes by area and only keep the 254 largest ones (because of the picking pass limitations): - std::sort(m_planes.rbegin(), m_planes.rend(), [](const PlaneData& a, const PlaneData& b) { return a.area < b.area; }); - m_planes.resize(std::min((int)m_planes.size(), 254)); - - // Planes are finished - let's save what we calculated it from: - m_volumes_matrices.clear(); - m_volumes_types.clear(); - for (const ModelVolume* vol : m_model_object->volumes) { - m_volumes_matrices.push_back(vol->get_matrix()); - m_volumes_types.push_back(vol->type()); - } - m_first_instance_scale = m_model_object->instances.front()->get_scaling_factor(); - m_first_instance_mirror = m_model_object->instances.front()->get_mirror(); - - m_planes_valid = true; -} - - -bool GLGizmoFlatten::is_plane_update_necessary() const -{ - if (m_state != On || !m_model_object || m_model_object->instances.empty()) - return false; - - if (! m_planes_valid || m_model_object->volumes.size() != m_volumes_matrices.size()) - return true; - - // We want to recalculate when the scale changes - some planes could (dis)appear. - if (! m_model_object->instances.front()->get_scaling_factor().isApprox(m_first_instance_scale) - || ! m_model_object->instances.front()->get_mirror().isApprox(m_first_instance_mirror)) - return true; - - for (unsigned int i=0; i < m_model_object->volumes.size(); ++i) - if (! m_model_object->volumes[i]->get_matrix().isApprox(m_volumes_matrices[i]) - || m_model_object->volumes[i]->type() != m_volumes_types[i]) - return true; - - return false; -} - -Vec3d GLGizmoFlatten::get_flattening_normal() const -{ - Vec3d out = m_normal; - m_normal = Vec3d::Zero(); - m_starting_center = Vec3d::Zero(); - return out; -} - -#if ENABLE_SVG_ICONS -GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) - : GLGizmoBase(parent, icon_filename, sprite_id) -#else -GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, unsigned int sprite_id) - : GLGizmoBase(parent, sprite_id) -#endif // ENABLE_SVG_ICONS - , m_starting_center(Vec3d::Zero()), m_quadric(nullptr) -{ - m_quadric = ::gluNewQuadric(); - if (m_quadric != nullptr) - // using GLU_FILL does not work when the instance's transformation - // contains mirroring (normals are reverted) - ::gluQuadricDrawStyle(m_quadric, GLU_FILL); -} - -GLGizmoSlaSupports::~GLGizmoSlaSupports() -{ - if (m_quadric != nullptr) - ::gluDeleteQuadric(m_quadric); -} - -bool GLGizmoSlaSupports::on_init() -{ - m_shortcut_key = WXK_CONTROL_L; - return true; -} - -void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const GLCanvas3D::Selection& selection) -{ - m_starting_center = Vec3d::Zero(); - m_old_model_object = m_model_object; - m_model_object = model_object; - if (selection.is_empty()) - m_old_instance_id = -1; - - m_active_instance = selection.get_instance_idx(); - - if (model_object && selection.is_from_single_instance()) - { - if (is_mesh_update_necessary()) - update_mesh(); - - if (m_model_object != m_old_model_object) - m_editing_mode = false; - - if (m_editing_mode_cache.empty() && m_model_object->sla_points_status != sla::PointsStatus::UserModified) - get_data_from_backend(); - - if (m_state == On) { - m_parent.toggle_model_objects_visibility(false); - m_parent.toggle_model_objects_visibility(true, m_model_object, m_active_instance); - } - } -} - -void GLGizmoSlaSupports::on_render(const GLCanvas3D::Selection& selection) const -{ - ::glEnable(GL_BLEND); - ::glEnable(GL_DEPTH_TEST); - - render_points(selection, false); - render_selection_rectangle(); - -#if !ENABLE_IMGUI - render_tooltip_texture(); -#endif // not ENABLE_IMGUI - - ::glDisable(GL_BLEND); -} - -void GLGizmoSlaSupports::render_selection_rectangle() const -{ - if (!m_selection_rectangle_active) - return; - - ::glLineWidth(1.5f); - float render_color[3] = {1.f, 0.f, 0.f}; - ::glColor3fv(render_color); - - ::glPushAttrib(GL_TRANSFORM_BIT); // remember current MatrixMode - - ::glMatrixMode(GL_MODELVIEW); // cache modelview matrix and set to identity - ::glPushMatrix(); - ::glLoadIdentity(); - - ::glMatrixMode(GL_PROJECTION); // cache projection matrix and set to identity - ::glPushMatrix(); - ::glLoadIdentity(); - - ::glOrtho(0.f, m_canvas_width, m_canvas_height, 0.f, -1.f, 1.f); // set projection matrix so that world coords = window coords - - // render the selection rectangle (window coordinates): - ::glPushAttrib(GL_ENABLE_BIT); - ::glLineStipple(4, 0xAAAA); - ::glEnable(GL_LINE_STIPPLE); - - ::glBegin(GL_LINE_LOOP); - ::glVertex3f((GLfloat)m_selection_rectangle_start_corner(0), (GLfloat)m_selection_rectangle_start_corner(1), (GLfloat)0.5f); - ::glVertex3f((GLfloat)m_selection_rectangle_end_corner(0), (GLfloat)m_selection_rectangle_start_corner(1), (GLfloat)0.5f); - ::glVertex3f((GLfloat)m_selection_rectangle_end_corner(0), (GLfloat)m_selection_rectangle_end_corner(1), (GLfloat)0.5f); - ::glVertex3f((GLfloat)m_selection_rectangle_start_corner(0), (GLfloat)m_selection_rectangle_end_corner(1), (GLfloat)0.5f); - ::glEnd(); - ::glPopAttrib(); - - ::glPopMatrix(); // restore former projection matrix - ::glMatrixMode(GL_MODELVIEW); - ::glPopMatrix(); // restore former modelview matrix - ::glPopAttrib(); // restore former MatrixMode -} - -void GLGizmoSlaSupports::on_render_for_picking(const GLCanvas3D::Selection& selection) const -{ - ::glEnable(GL_DEPTH_TEST); - - render_points(selection, true); -} - -void GLGizmoSlaSupports::render_points(const GLCanvas3D::Selection& selection, bool picking) const -{ - if (m_quadric == nullptr || !selection.is_from_single_instance()) - return; - - if (!picking) - ::glEnable(GL_LIGHTING); - - const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin()); - double z_shift = vol->get_sla_shift_z(); - const Transform3d& instance_scaling_matrix_inverse = vol->get_instance_transformation().get_matrix(true, true, false, true).inverse(); - const Transform3d& instance_matrix = vol->get_instance_transformation().get_matrix(); - - ::glPushMatrix(); - ::glTranslated(0.0, 0.0, z_shift); - ::glMultMatrixd(instance_matrix.data()); - - float render_color[3]; - for (int i = 0; i < (int)m_editing_mode_cache.size(); ++i) - { - const sla::SupportPoint& support_point = m_editing_mode_cache[i].first; - const bool& point_selected = m_editing_mode_cache[i].second; - - // First decide about the color of the point. - if (picking) { - render_color[0] = 1.0f; - render_color[1] = 1.0f; - render_color[2] = picking_color_component(i); - } - else { - if ((m_hover_id == i && m_editing_mode)) { // ignore hover state unless editing mode is active - render_color[0] = 0.f; - render_color[1] = 1.0f; - render_color[2] = 1.0f; - } - else { // neigher hover nor picking - bool supports_new_island = m_lock_unique_islands && m_editing_mode_cache[i].first.is_new_island; - if (m_editing_mode) { - render_color[0] = point_selected ? 1.0f : (supports_new_island ? 0.3f : 0.7f); - render_color[1] = point_selected ? 0.3f : (supports_new_island ? 0.3f : 0.7f); - render_color[2] = point_selected ? 0.3f : (supports_new_island ? 1.0f : 0.7f); - } - else - for (unsigned char i=0; i<3; ++i) render_color[i] = 0.5f; - } - } - ::glColor3fv(render_color); - float render_color_emissive[4] = { 0.5f * render_color[0], 0.5f * render_color[1], 0.5f * render_color[2], 1.f}; - ::glMaterialfv(GL_FRONT, GL_EMISSION, render_color_emissive); - - // Now render the sphere. Inverse matrix of the instance scaling is applied so that the - // sphere does not scale with the object. - ::glPushMatrix(); - ::glTranslated(support_point.pos(0), support_point.pos(1), support_point.pos(2)); - ::glMultMatrixd(instance_scaling_matrix_inverse.data()); - ::gluSphere(m_quadric, m_editing_mode_cache[i].first.head_front_radius * RenderPointScale, 64, 36); - ::glPopMatrix(); - } - - { - // Reset emissive component to zero (the default value) - float render_color_emissive[4] = { 0.f, 0.f, 0.f, 1.f }; - ::glMaterialfv(GL_FRONT, GL_EMISSION, render_color_emissive); - } - - if (!picking) - ::glDisable(GL_LIGHTING); - - ::glPopMatrix(); -} - -bool GLGizmoSlaSupports::is_mesh_update_necessary() const -{ - return (m_state == On) && (m_model_object != m_old_model_object) && (m_model_object != nullptr) && !m_model_object->instances.empty(); - - //if (m_state != On || !m_model_object || m_model_object->instances.empty() || ! m_instance_matrix.isApprox(m_source_data.matrix)) - // return false; -} - -void GLGizmoSlaSupports::update_mesh() -{ - Eigen::MatrixXf& V = m_V; - Eigen::MatrixXi& F = m_F; - // Composite mesh of all instances in the world coordinate system. - // This mesh does not account for the possible Z up SLA offset. - TriangleMesh mesh = m_model_object->raw_mesh(); - const stl_file& stl = mesh.stl; - V.resize(3 * stl.stats.number_of_facets, 3); - F.resize(stl.stats.number_of_facets, 3); - for (unsigned int i=0; i<stl.stats.number_of_facets; ++i) { - const stl_facet* facet = stl.facet_start+i; - V(3*i+0, 0) = facet->vertex[0](0); V(3*i+0, 1) = facet->vertex[0](1); V(3*i+0, 2) = facet->vertex[0](2); - V(3*i+1, 0) = facet->vertex[1](0); V(3*i+1, 1) = facet->vertex[1](1); V(3*i+1, 2) = facet->vertex[1](2); - V(3*i+2, 0) = facet->vertex[2](0); V(3*i+2, 1) = facet->vertex[2](1); V(3*i+2, 2) = facet->vertex[2](2); - F(i, 0) = 3*i+0; - F(i, 1) = 3*i+1; - F(i, 2) = 3*i+2; - } - - m_AABB = igl::AABB<Eigen::MatrixXf,3>(); - m_AABB.init(m_V, m_F); - - // we'll now reload support points (selection might have changed): - editing_mode_reload_cache(); -} - -Vec3f GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos) -{ - // if the gizmo doesn't have the V, F structures for igl, calculate them first: - if (m_V.size() == 0) - update_mesh(); - - Eigen::Matrix<GLint, 4, 1, Eigen::DontAlign> viewport; - ::glGetIntegerv(GL_VIEWPORT, viewport.data()); - Eigen::Matrix<GLdouble, 4, 4, Eigen::DontAlign> modelview_matrix; - ::glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix.data()); - Eigen::Matrix<GLdouble, 4, 4, Eigen::DontAlign> projection_matrix; - ::glGetDoublev(GL_PROJECTION_MATRIX, projection_matrix.data()); - - Vec3d point1; - Vec3d point2; - ::gluUnProject(mouse_pos(0), viewport(3)-mouse_pos(1), 0.f, modelview_matrix.data(), projection_matrix.data(), viewport.data(), &point1(0), &point1(1), &point1(2)); - ::gluUnProject(mouse_pos(0), viewport(3)-mouse_pos(1), 1.f, modelview_matrix.data(), projection_matrix.data(), viewport.data(), &point2(0), &point2(1), &point2(2)); - - igl::Hit hit; - - const GLCanvas3D::Selection& selection = m_parent.get_selection(); - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); - double z_offset = volume->get_sla_shift_z(); - - point1(2) -= z_offset; - point2(2) -= z_offset; - - Transform3d inv = volume->get_instance_transformation().get_matrix().inverse(); - - point1 = inv * point1; - point2 = inv * point2; - - if (!m_AABB.intersect_ray(m_V, m_F, point1.cast<float>(), (point2-point1).cast<float>(), hit)) - throw std::invalid_argument("unproject_on_mesh(): No intersection found."); - - int fid = hit.id; - Vec3f bc(1-hit.u-hit.v, hit.u, hit.v); - return bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2)); -} - -// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event. -// The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is -// aware that the event was reacted to and stops trying to make different sense of it. If the gizmo -// concludes that the event was not intended for it, it should return false. -bool GLGizmoSlaSupports::mouse_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down) -{ - if (m_editing_mode) { - - // left down - show the selection rectangle: - if (action == SLAGizmoEventType::LeftDown && shift_down) { - if (m_hover_id == -1) { - m_selection_rectangle_active = true; - m_selection_rectangle_start_corner = mouse_position; - m_selection_rectangle_end_corner = mouse_position; - m_canvas_width = m_parent.get_canvas_size().get_width(); - m_canvas_height = m_parent.get_canvas_size().get_height(); - } - else - select_point(m_hover_id); - - return true; - } - - // dragging the selection rectangle: - if (action == SLAGizmoEventType::Dragging && m_selection_rectangle_active) { - m_selection_rectangle_end_corner = mouse_position; - return true; - } - - // mouse up without selection rectangle - place point on the mesh: - if (action == SLAGizmoEventType::LeftUp && !m_selection_rectangle_active && !shift_down) { - if (m_ignore_up_event) { - m_ignore_up_event = false; - return false; - } - - int instance_id = m_parent.get_selection().get_instance_idx(); - if (m_old_instance_id != instance_id) - { - bool something_selected = (m_old_instance_id != -1); - m_old_instance_id = instance_id; - if (something_selected) - return false; - } - if (instance_id == -1) - return false; - - // If there is some selection, don't add new point and deselect everything instead. - if (m_selection_empty) { - Vec3f new_pos; - try { - new_pos = unproject_on_mesh(mouse_position); // this can throw - we don't want to create a new point in that case - m_editing_mode_cache.emplace_back(std::make_pair(sla::SupportPoint(new_pos, m_new_point_head_diameter/2.f, false), false)); - m_unsaved_changes = true; - } - catch (...) { // not clicked on object - return true; // prevents deselection of the gizmo by GLCanvas3D - } - } - else - select_point(NoPoints); - - return true; - } - - // left up with selection rectangle - select points inside the rectangle: - if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::ShiftUp) - && m_selection_rectangle_active) { - if (action == SLAGizmoEventType::ShiftUp) - m_ignore_up_event = true; - const Transform3d& instance_matrix = m_model_object->instances[m_active_instance]->get_transformation().get_matrix(); - GLint viewport[4]; - ::glGetIntegerv(GL_VIEWPORT, viewport); - GLdouble modelview_matrix[16]; - ::glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix); - GLdouble projection_matrix[16]; - ::glGetDoublev(GL_PROJECTION_MATRIX, projection_matrix); - - const GLCanvas3D::Selection& selection = m_parent.get_selection(); - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); - double z_offset = volume->get_sla_shift_z(); - - // bounding box created from the rectangle corners - will take care of order of the corners - BoundingBox rectangle(Points{Point(m_selection_rectangle_start_corner.cast<int>()), Point(m_selection_rectangle_end_corner.cast<int>())}); - - const Transform3d& instance_matrix_no_translation = volume->get_instance_transformation().get_matrix(true); - // we'll recover current look direction from the modelview matrix (in world coords)... - Vec3f direction_to_camera(modelview_matrix[2], modelview_matrix[6], modelview_matrix[10]); - // ...and transform it to model coords. - direction_to_camera = (instance_matrix_no_translation.inverse().cast<float>() * direction_to_camera).normalized().eval(); - - // Iterate over all points, check if they're in the rectangle and if so, check that they are not obscured by the mesh: - for (unsigned int i=0; i<m_editing_mode_cache.size(); ++i) { - const sla::SupportPoint &support_point = m_editing_mode_cache[i].first; - Vec3f pos = instance_matrix.cast<float>() * support_point.pos; - pos(2) += z_offset; - GLdouble out_x, out_y, out_z; - ::gluProject((GLdouble)pos(0), (GLdouble)pos(1), (GLdouble)pos(2), modelview_matrix, projection_matrix, viewport, &out_x, &out_y, &out_z); - out_y = m_canvas_height - out_y; - - if (rectangle.contains(Point(out_x, out_y))) { - bool is_obscured = false; - // Cast a ray in the direction of the camera and look for intersection with the mesh: - std::vector<igl::Hit> hits; - // Offset the start of the ray to the front of the ball + EPSILON to account for numerical inaccuracies. - if (m_AABB.intersect_ray(m_V, m_F, support_point.pos + direction_to_camera * (support_point.head_front_radius + EPSILON), direction_to_camera, hits)) - // FIXME: the intersection could in theory be behind the camera, but as of now we only have camera direction. - // Also, the threshold is in mesh coordinates, not in actual dimensions. - if (hits.size() > 1 || hits.front().t > 0.001f) - is_obscured = true; - - if (!is_obscured) - select_point(i); - } - } - m_selection_rectangle_active = false; - return true; - } - - if (action == SLAGizmoEventType::Delete) { - // delete key pressed - delete_selected_points(); - return true; - } - - if (action == SLAGizmoEventType::ApplyChanges) { - editing_mode_apply_changes(); - return true; - } - - if (action == SLAGizmoEventType::DiscardChanges) { - editing_mode_discard_changes(); - return true; - } - - if (action == SLAGizmoEventType::RightDown) { - if (m_hover_id != -1) { - select_point(NoPoints); - select_point(m_hover_id); - delete_selected_points(); - return true; - } - return false; - } - - if (action == SLAGizmoEventType::SelectAll) { - select_point(AllPoints); - return true; - } - } - - if (!m_editing_mode) { - if (action == SLAGizmoEventType::AutomaticGeneration) { - auto_generate(); - return true; - } - - if (action == SLAGizmoEventType::ManualEditing) { - switch_to_editing_mode(); - return true; - } - } - - return false; -} - -void GLGizmoSlaSupports::delete_selected_points(bool force) -{ - for (unsigned int idx=0; idx<m_editing_mode_cache.size(); ++idx) { - if (m_editing_mode_cache[idx].second && (!m_editing_mode_cache[idx].first.is_new_island || !m_lock_unique_islands || force)) { - m_editing_mode_cache.erase(m_editing_mode_cache.begin() + (idx--)); - m_unsaved_changes = true; - } - // This should trigger the support generation - // wxGetApp().plater()->reslice_SLA_supports(*m_model_object); - } - - select_point(NoPoints); - - //m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); -} - -void GLGizmoSlaSupports::on_update(const UpdateData& data, const GLCanvas3D::Selection& selection) -{ - if (m_editing_mode && m_hover_id != -1 && data.mouse_pos && (!m_editing_mode_cache[m_hover_id].first.is_new_island || !m_lock_unique_islands)) { - Vec3f new_pos; - try { - new_pos = unproject_on_mesh(Vec2d((*data.mouse_pos)(0), (*data.mouse_pos)(1))); - } - catch (...) { return; } - m_editing_mode_cache[m_hover_id].first.pos = new_pos; - m_editing_mode_cache[m_hover_id].first.is_new_island = false; - m_unsaved_changes = true; - // Do not update immediately, wait until the mouse is released. - // m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); - } -} - -#if !ENABLE_IMGUI -void GLGizmoSlaSupports::render_tooltip_texture() const { - if (m_tooltip_texture.get_id() == 0) - if (!m_tooltip_texture.load_from_file(resources_dir() + "/icons/sla_support_points_tooltip.png", false)) - return; - if (m_reset_texture.get_id() == 0) - if (!m_reset_texture.load_from_file(resources_dir() + "/icons/sla_support_points_reset.png", false)) - return; - - float zoom = m_parent.get_camera_zoom(); - float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; - float gap = 30.0f * inv_zoom; - - const Size& cnv_size = m_parent.get_canvas_size(); - float l = gap - cnv_size.get_width()/2.f * inv_zoom; - float r = l + (float)m_tooltip_texture.get_width() * inv_zoom; - float b = gap - cnv_size.get_height()/2.f * inv_zoom; - float t = b + (float)m_tooltip_texture.get_height() * inv_zoom; - - Rect reset_rect = m_parent.get_gizmo_reset_rect(m_parent, true); - - ::glDisable(GL_DEPTH_TEST); - ::glPushMatrix(); - ::glLoadIdentity(); - GLTexture::render_texture(m_tooltip_texture.get_id(), l, r, b, t); - GLTexture::render_texture(m_reset_texture.get_id(), reset_rect.get_left(), reset_rect.get_right(), reset_rect.get_bottom(), reset_rect.get_top()); - ::glPopMatrix(); - ::glEnable(GL_DEPTH_TEST); -} -#endif // not ENABLE_IMGUI - - -std::vector<ConfigOption*> GLGizmoSlaSupports::get_config_options(const std::vector<std::string>& keys) const -{ - std::vector<ConfigOption*> out; - - if (!m_model_object) - return out; - - DynamicPrintConfig& object_cfg = m_model_object->config; - DynamicPrintConfig& print_cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; - std::unique_ptr<DynamicPrintConfig> default_cfg = nullptr; - - for (const std::string& key : keys) { - if (object_cfg.has(key)) - out.push_back(object_cfg.option(key)); - else - if (print_cfg.has(key)) - out.push_back(print_cfg.option(key)); - else { // we must get it from defaults - if (default_cfg == nullptr) - default_cfg.reset(DynamicPrintConfig::new_from_defaults_keys(keys)); - out.push_back(default_cfg->option(key)); - } - } - - return out; -} - - - -#if ENABLE_IMGUI -void GLGizmoSlaSupports::on_render_input_window(float x, float y, float bottom_limit, const GLCanvas3D::Selection& selection) -{ - if (!m_model_object) - return; - - bool first_run = true; // This is a hack to redraw the button when all points are removed, - // so it is not delayed until the background process finishes. -RENDER_AGAIN: - m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); - - const float scaling = m_imgui->get_style_scaling(); - const ImVec2 window_size(285.f * scaling, 260.f * scaling); - ImGui::SetNextWindowPos(ImVec2(x, y - std::max(0.f, y+window_size.y-bottom_limit) )); - ImGui::SetNextWindowSize(ImVec2(window_size)); - - m_imgui->set_next_window_bg_alpha(0.5f); - m_imgui->begin(on_get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - - ImGui::PushItemWidth(100.0f); - - bool force_refresh = false; - bool remove_selected = false; - bool remove_all = false; - - if (m_editing_mode) { - m_imgui->text(_(L("Left mouse click - add point"))); - m_imgui->text(_(L("Right mouse click - remove point"))); - m_imgui->text(_(L("Shift + Left (+ drag) - select point(s)"))); - m_imgui->text(" "); // vertical gap - - static const std::vector<float> options = {0.2f, 0.4f, 0.6f, 0.8f, 1.0f}; - static const std::vector<std::string> options_str = {"0.2", "0.4", "0.6", "0.8", "1.0"}; - int selection = -1; - for (size_t i = 0; i < options.size(); i++) { - if (options[i] == m_new_point_head_diameter) { selection = (int)i; } - } - - bool old_combo_state = m_combo_box_open; - // The combo is commented out for now, until the feature is supported by backend. - // m_combo_box_open = m_imgui->combo(_(L("Head diameter")), options_str, selection); - force_refresh |= (old_combo_state != m_combo_box_open); - - // float current_number = atof(str); - const float current_number = selection < options.size() ? options[selection] : m_new_point_head_diameter; - if (old_combo_state && !m_combo_box_open) // closing the combo must always change the sizes (even if the selection did not change) - for (auto& point_and_selection : m_editing_mode_cache) - if (point_and_selection.second) { - point_and_selection.first.head_front_radius = current_number / 2.f; - m_unsaved_changes = true; - } - - if (std::abs(current_number - m_new_point_head_diameter) > 0.001) { - force_refresh = true; - m_new_point_head_diameter = current_number; - } - - bool changed = m_lock_unique_islands; - m_imgui->checkbox(_(L("Lock supports under new islands")), m_lock_unique_islands); - force_refresh |= changed != m_lock_unique_islands; - - m_imgui->disabled_begin(m_selection_empty); - remove_selected = m_imgui->button(_(L("Remove selected points"))); - m_imgui->disabled_end(); - - m_imgui->disabled_begin(m_editing_mode_cache.empty()); - remove_all = m_imgui->button(_(L("Remove all points"))); - m_imgui->disabled_end(); - - m_imgui->text(" "); // vertical gap - - if (m_imgui->button(_(L("Apply changes")))) { - editing_mode_apply_changes(); - force_refresh = true; - } - ImGui::SameLine(); - bool discard_changes = m_imgui->button(_(L("Discard changes"))); - if (discard_changes) { - editing_mode_discard_changes(); - force_refresh = true; - } - } - else { // not in editing mode: - ImGui::PushItemWidth(100.0f); - m_imgui->text(_(L("Minimal points distance: "))); - ImGui::SameLine(); - - std::vector<ConfigOption*> opts = get_config_options({"support_points_density_relative", "support_points_minimal_distance"}); - float density = static_cast<ConfigOptionInt*>(opts[0])->value; - float minimal_point_distance = static_cast<ConfigOptionFloat*>(opts[1])->value; - - bool value_changed = ImGui::SliderFloat("", &minimal_point_distance, 0.f, 20.f, "%.f mm"); - if (value_changed) - m_model_object->config.opt<ConfigOptionFloat>("support_points_minimal_distance", true)->value = minimal_point_distance; - - m_imgui->text(_(L("Support points density: "))); - ImGui::SameLine(); - if (ImGui::SliderFloat(" ", &density, 0.f, 200.f, "%.f %%")) { - value_changed = true; - m_model_object->config.opt<ConfigOptionInt>("support_points_density_relative", true)->value = (int)density; - } - - if (value_changed) { // Update side panel - wxTheApp->CallAfter([]() { - wxGetApp().obj_settings()->UpdateAndShow(true); - wxGetApp().obj_list()->update_settings_items(); - }); - } - - bool generate = m_imgui->button(_(L("Auto-generate points [A]"))); - - if (generate) - auto_generate(); - - m_imgui->text(""); - if (m_imgui->button(_(L("Manual editing [M]")))) - switch_to_editing_mode(); - - m_imgui->disabled_begin(m_editing_mode_cache.empty()); - remove_all = m_imgui->button(_(L("Remove all points"))); - m_imgui->disabled_end(); - - m_imgui->text(""); - - m_imgui->text(m_model_object->sla_points_status == sla::PointsStatus::None ? "No points (will be autogenerated)" : - (m_model_object->sla_points_status == sla::PointsStatus::AutoGenerated ? "Autogenerated points (no modifications)" : - (m_model_object->sla_points_status == sla::PointsStatus::UserModified ? "User-modified points" : - (m_model_object->sla_points_status == sla::PointsStatus::Generating ? "Generation in progress..." : "UNKNOWN STATUS")))); - } - - m_imgui->end(); - - if (m_editing_mode != m_old_editing_state) { // user toggled between editing/non-editing mode - m_parent.toggle_sla_auxiliaries_visibility(!m_editing_mode); - force_refresh = true; - } - m_old_editing_state = m_editing_mode; - - if (remove_selected || remove_all) { - force_refresh = false; - m_parent.set_as_dirty(); - if (remove_all) - select_point(AllPoints); - delete_selected_points(remove_all); - if (remove_all && !m_editing_mode) - editing_mode_apply_changes(); - if (first_run) { - first_run = false; - goto RENDER_AGAIN; - } - } - - if (force_refresh) - m_parent.set_as_dirty(); -} -#endif // ENABLE_IMGUI - -bool GLGizmoSlaSupports::on_is_activable(const GLCanvas3D::Selection& selection) const -{ - if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA - || !selection.is_from_single_instance()) - return false; - - // Check that none of the selected volumes is outside. - const GLCanvas3D::Selection::IndicesList& list = selection.get_volume_idxs(); - for (const auto& idx : list) - if (selection.get_volume(idx)->is_outside) - return false; - - return true; -} - -bool GLGizmoSlaSupports::on_is_selectable() const -{ - return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA); -} - -std::string GLGizmoSlaSupports::on_get_name() const -{ - return L("SLA Support Points [L]"); -} - -void GLGizmoSlaSupports::on_set_state() -{ - if (m_state == On) { - if (is_mesh_update_necessary()) - update_mesh(); - - m_parent.toggle_model_objects_visibility(false); - if (m_model_object) - m_parent.toggle_model_objects_visibility(true, m_model_object, m_active_instance); - } - if (m_state == Off) { - if (m_old_state != Off) { // the gizmo was just turned Off - - if (m_model_object) { - if (m_unsaved_changes) { - wxMessageDialog dlg(GUI::wxGetApp().plater(), _(L("Do you want to save your manually edited support points ?\n")), - _(L("Save changes?")), wxICON_QUESTION | wxYES | wxNO); - if (dlg.ShowModal() == wxID_YES) - editing_mode_apply_changes(); - else - editing_mode_discard_changes(); - } - } - - m_parent.toggle_model_objects_visibility(true); - m_editing_mode = false; // so it is not active next time the gizmo opens - m_editing_mode_cache.clear(); - } - } - m_old_state = m_state; -} - - - -void GLGizmoSlaSupports::on_start_dragging(const GLCanvas3D::Selection& selection) -{ - if (m_hover_id != -1) { - select_point(NoPoints); - select_point(m_hover_id); - } -} - - - -void GLGizmoSlaSupports::select_point(int i) -{ - if (i == AllPoints || i == NoPoints) { - for (auto& point_and_selection : m_editing_mode_cache) - point_and_selection.second = ( i == AllPoints ); - m_selection_empty = (i == NoPoints); - } - else { - m_editing_mode_cache[i].second = true; - m_selection_empty = false; - } -} - - - -void GLGizmoSlaSupports::editing_mode_discard_changes() -{ - m_editing_mode_cache.clear(); - for (const sla::SupportPoint& point : m_model_object->sla_support_points) - m_editing_mode_cache.push_back(std::make_pair(point, false)); - m_editing_mode = false; - m_unsaved_changes = false; -} - - - -void GLGizmoSlaSupports::editing_mode_apply_changes() -{ - // If there are no changes, don't touch the front-end. The data in the cache could have been - // taken from the backend and copying them to ModelObject would needlessly invalidate them. - if (m_unsaved_changes) { - m_model_object->sla_points_status = sla::PointsStatus::UserModified; - m_model_object->sla_support_points.clear(); - for (const std::pair<sla::SupportPoint, bool>& point_and_selection : m_editing_mode_cache) - m_model_object->sla_support_points.push_back(point_and_selection.first); - - // Recalculate support structures once the editing mode is left. - // m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); - // m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); - wxGetApp().plater()->reslice_SLA_supports(*m_model_object); - } - m_editing_mode = false; - m_unsaved_changes = false; -} - - - -void GLGizmoSlaSupports::editing_mode_reload_cache() -{ - m_editing_mode_cache.clear(); - for (const sla::SupportPoint& point : m_model_object->sla_support_points) - m_editing_mode_cache.push_back(std::make_pair(point, false)); - - m_unsaved_changes = false; -} - - - -void GLGizmoSlaSupports::get_data_from_backend() -{ - for (const SLAPrintObject* po : m_parent.sla_print()->objects()) { - if (po->model_object()->id() == m_model_object->id() && po->is_step_done(slaposSupportPoints)) { - m_editing_mode_cache.clear(); - const std::vector<sla::SupportPoint>& points = po->get_support_points(); - auto mat = po->trafo().inverse().cast<float>(); - for (unsigned int i=0; i<points.size();++i) - m_editing_mode_cache.emplace_back(sla::SupportPoint(mat * points[i].pos, points[i].head_front_radius, points[i].is_new_island), false); - - if (m_model_object->sla_points_status != sla::PointsStatus::UserModified) - m_model_object->sla_points_status = sla::PointsStatus::AutoGenerated; - - break; - } - } - m_unsaved_changes = false; - - // We don't copy the data into ModelObject, as this would stop the background processing. -} - - - -void GLGizmoSlaSupports::auto_generate() -{ - wxMessageDialog dlg(GUI::wxGetApp().plater(), _(L( - "Autogeneration will erase all manually edited points.\n\n" - "Are you sure you want to do it?\n" - )), _(L("Warning")), wxICON_WARNING | wxYES | wxNO); - - if (m_model_object->sla_points_status != sla::PointsStatus::UserModified || m_editing_mode_cache.empty() || dlg.ShowModal() == wxID_YES) { - m_model_object->sla_support_points.clear(); - m_model_object->sla_points_status = sla::PointsStatus::Generating; - m_editing_mode_cache.clear(); - wxGetApp().plater()->reslice_SLA_supports(*m_model_object); - } -} - - - -void GLGizmoSlaSupports::switch_to_editing_mode() -{ - if (m_model_object->sla_points_status != sla::PointsStatus::AutoGenerated) - editing_mode_reload_cache(); - m_unsaved_changes = false; - m_editing_mode = true; -} - - - - - - -// GLGizmoCut - -class GLGizmoCutPanel : public wxPanel -{ -public: - GLGizmoCutPanel(wxWindow *parent); - - void display(bool display); -private: - bool m_active; - wxCheckBox *m_cb_rotate; - wxButton *m_btn_cut; - wxButton *m_btn_cancel; -}; - -GLGizmoCutPanel::GLGizmoCutPanel(wxWindow *parent) - : wxPanel(parent) - , m_active(false) - , m_cb_rotate(new wxCheckBox(this, wxID_ANY, _(L("Rotate lower part upwards")))) - , m_btn_cut(new wxButton(this, wxID_OK, _(L("Perform cut")))) - , m_btn_cancel(new wxButton(this, wxID_CANCEL, _(L("Cancel")))) -{ - enum { MARGIN = 5 }; - - auto *sizer = new wxBoxSizer(wxHORIZONTAL); - - auto *label = new wxStaticText(this, wxID_ANY, _(L("Cut object:"))); - sizer->Add(label, 0, wxALL | wxALIGN_CENTER, MARGIN); - sizer->Add(m_cb_rotate, 0, wxALL | wxALIGN_CENTER, MARGIN); - sizer->AddStretchSpacer(); - sizer->Add(m_btn_cut, 0, wxALL | wxALIGN_CENTER, MARGIN); - sizer->Add(m_btn_cancel, 0, wxALL | wxALIGN_CENTER, MARGIN); - - SetSizer(sizer); -} - -void GLGizmoCutPanel::display(bool display) -{ - Show(display); - GetParent()->Layout(); -} - - -const double GLGizmoCut::Offset = 10.0; -const double GLGizmoCut::Margin = 20.0; -const std::array<float, 3> GLGizmoCut::GrabberColor = { 1.0, 0.5, 0.0 }; - -#if ENABLE_SVG_ICONS -GLGizmoCut::GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) - : GLGizmoBase(parent, icon_filename, sprite_id) -#else -GLGizmoCut::GLGizmoCut(GLCanvas3D& parent, unsigned int sprite_id) - : GLGizmoBase(parent, sprite_id) -#endif // ENABLE_SVG_ICONS - , m_cut_z(0.0) - , m_max_z(0.0) -#if !ENABLE_IMGUI - , m_panel(nullptr) -#endif // not ENABLE_IMGUI - , m_keep_upper(true) - , m_keep_lower(true) - , m_rotate_lower(false) -{} - -#if !ENABLE_IMGUI -void GLGizmoCut::create_external_gizmo_widgets(wxWindow *parent) -{ - wxASSERT(m_panel == nullptr); - - m_panel = new GLGizmoCutPanel(parent); - parent->GetSizer()->Add(m_panel, 0, wxEXPAND); - - parent->Layout(); - parent->Fit(); - auto prev_heigh = parent->GetMinSize().GetHeight(); - parent->SetMinSize(wxSize(-1, std::max(prev_heigh, m_panel->GetSize().GetHeight()))); - - m_panel->Hide(); - m_panel->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { - perform_cut(m_parent.get_selection()); - }, wxID_OK); -} -#endif // not ENABLE_IMGUI - -bool GLGizmoCut::on_init() -{ - m_grabbers.emplace_back(); - m_shortcut_key = WXK_CONTROL_C; - return true; -} - -std::string GLGizmoCut::on_get_name() const -{ - return L("Cut [C]"); -} - -void GLGizmoCut::on_set_state() -{ - // Reset m_cut_z on gizmo activation - if (get_state() == On) { - m_cut_z = m_parent.get_selection().get_bounding_box().size()(2) / 2.0; - } - -#if !ENABLE_IMGUI - // Display or hide the extra panel - if (m_panel != nullptr) { - m_panel->display(get_state() == On); - } -#endif // not ENABLE_IMGUI -} - -bool GLGizmoCut::on_is_activable(const GLCanvas3D::Selection& selection) const -{ - return selection.is_single_full_instance() && !selection.is_wipe_tower(); -} - -void GLGizmoCut::on_start_dragging(const GLCanvas3D::Selection& selection) -{ - if (m_hover_id == -1) { return; } - - const BoundingBoxf3& box = selection.get_bounding_box(); - m_start_z = m_cut_z; - update_max_z(selection); - m_drag_pos = m_grabbers[m_hover_id].center; - m_drag_center = box.center(); - m_drag_center(2) = m_cut_z; -} - -void GLGizmoCut::on_update(const UpdateData& data, const GLCanvas3D::Selection& selection) -{ - if (m_hover_id != -1) { - set_cut_z(m_start_z + calc_projection(data.mouse_ray)); - } -} - -void GLGizmoCut::on_render(const GLCanvas3D::Selection& selection) const -{ - if (m_grabbers[0].dragging) { - set_tooltip("Z: " + format(m_cut_z, 2)); - } - - update_max_z(selection); - - const BoundingBoxf3& box = selection.get_bounding_box(); - Vec3d plane_center = box.center(); - plane_center(2) = m_cut_z; - - const float min_x = box.min(0) - Margin; - const float max_x = box.max(0) + Margin; - const float min_y = box.min(1) - Margin; - const float max_y = box.max(1) + Margin; - ::glEnable(GL_DEPTH_TEST); - ::glDisable(GL_CULL_FACE); - ::glEnable(GL_BLEND); - ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - // Draw the cutting plane - ::glBegin(GL_QUADS); - ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f); - ::glVertex3f(min_x, min_y, plane_center(2)); - ::glVertex3f(max_x, min_y, plane_center(2)); - ::glVertex3f(max_x, max_y, plane_center(2)); - ::glVertex3f(min_x, max_y, plane_center(2)); - ::glEnd(); - - ::glEnable(GL_CULL_FACE); - ::glDisable(GL_BLEND); - - // TODO: draw cut part contour? - - // Draw the grabber and the connecting line - m_grabbers[0].center = plane_center; - m_grabbers[0].center(2) = plane_center(2) + Offset; - - ::glDisable(GL_DEPTH_TEST); - ::glLineWidth(m_hover_id != -1 ? 2.0f : 1.5f); - ::glColor3f(1.0, 1.0, 0.0); - ::glBegin(GL_LINES); - ::glVertex3dv(plane_center.data()); - ::glVertex3dv(m_grabbers[0].center.data()); - ::glEnd(); - - std::copy(std::begin(GrabberColor), std::end(GrabberColor), m_grabbers[0].color); - m_grabbers[0].render(m_hover_id == 0, box.max_size()); -} - -void GLGizmoCut::on_render_for_picking(const GLCanvas3D::Selection& selection) const -{ - ::glDisable(GL_DEPTH_TEST); - - render_grabbers_for_picking(selection.get_bounding_box()); -} - -#if ENABLE_IMGUI -void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit, const GLCanvas3D::Selection& selection) -{ - m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); - m_imgui->set_next_window_bg_alpha(0.5f); - m_imgui->begin(_(L("Cut")), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - - ImGui::PushItemWidth(100.0f); - bool _value_changed = ImGui::InputDouble("Z", &m_cut_z, 0.0f, 0.0f, "%.2f"); - - m_imgui->checkbox(_(L("Keep upper part")), m_keep_upper); - m_imgui->checkbox(_(L("Keep lower part")), m_keep_lower); - m_imgui->checkbox(_(L("Rotate lower part upwards")), m_rotate_lower); - - m_imgui->disabled_begin(!m_keep_upper && !m_keep_lower); - const bool cut_clicked = m_imgui->button(_(L("Perform cut"))); - m_imgui->disabled_end(); - - m_imgui->end(); - - if (cut_clicked && (m_keep_upper || m_keep_lower)) { - perform_cut(selection); - } -} -#endif // ENABLE_IMGUI - -void GLGizmoCut::update_max_z(const GLCanvas3D::Selection& selection) const -{ - m_max_z = selection.get_bounding_box().size()(2); - set_cut_z(m_cut_z); -} - -void GLGizmoCut::set_cut_z(double cut_z) const -{ - // Clamp the plane to the object's bounding box - m_cut_z = std::max(0.0, std::min(m_max_z, cut_z)); -} - -void GLGizmoCut::perform_cut(const GLCanvas3D::Selection& selection) -{ - const auto instance_idx = selection.get_instance_idx(); - const auto object_idx = selection.get_object_idx(); - - wxCHECK_RET(instance_idx >= 0 && object_idx >= 0, "GLGizmoCut: Invalid object selection"); - - wxGetApp().plater()->cut(object_idx, instance_idx, m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); -} - -double GLGizmoCut::calc_projection(const Linef3& mouse_ray) const -{ - double projection = 0.0; - - const Vec3d starting_vec = m_drag_pos - m_drag_center; - const double len_starting_vec = starting_vec.norm(); - if (len_starting_vec != 0.0) - { - Vec3d mouse_dir = mouse_ray.unit_vector(); - // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position - // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form - // in our case plane normal and ray direction are the same (orthogonal view) - // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal - Vec3d inters = mouse_ray.a + (m_drag_pos - mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; - // vector from the starting position to the found intersection - Vec3d inters_vec = inters - m_drag_pos; - - // finds projection of the vector along the staring direction - projection = inters_vec.dot(starting_vec.normalized()); - } - return projection; -} - - -} // namespace GUI -} // namespace Slic3r diff --git a/src/slic3r/GUI/GLGizmo.hpp b/src/slic3r/GUI/GLGizmo.hpp deleted file mode 100644 index fc8d40a65..000000000 --- a/src/slic3r/GUI/GLGizmo.hpp +++ /dev/null @@ -1,629 +0,0 @@ -#ifndef slic3r_GLGizmo_hpp_ -#define slic3r_GLGizmo_hpp_ - -#include <igl/AABB.h> - -#include "../../slic3r/GUI/GLTexture.hpp" -#include "../../slic3r/GUI/GLCanvas3D.hpp" - -#include "libslic3r/Point.hpp" -#include "libslic3r/BoundingBox.hpp" -#include "libslic3r/SLA/SLAAutoSupports.hpp" - -#include <array> -#include <vector> -#include <memory> - - -class wxWindow; -class GLUquadric; -typedef class GLUquadric GLUquadricObj; - - -namespace Slic3r { - -class BoundingBoxf3; -class Linef3; -class ModelObject; - -namespace GUI { - -class GLCanvas3D; -#if ENABLE_IMGUI -class ImGuiWrapper; -#endif // ENABLE_IMGUI - -class GLGizmoBase -{ -protected: - struct Grabber - { - static const float SizeFactor; - static const float MinHalfSize; - static const float DraggingScaleFactor; - - Vec3d center; - Vec3d angles; - float color[3]; - bool enabled; - bool dragging; - - Grabber(); - - void render(bool hover, float size) const; - void render_for_picking(float size) const { render(size, color, false); } - - float get_half_size(float size) const; - float get_dragging_half_size(float size) const; - - private: - void render(float size, const float* render_color, bool use_lighting) const; - void render_face(float half_size) const; - }; - -public: - enum EState - { - Off, - Hover, - On, - Num_States - }; - - struct UpdateData - { - const Linef3 mouse_ray; - const Point* mouse_pos; - bool shift_down; - - UpdateData(const Linef3& mouse_ray, const Point* mouse_pos = nullptr, bool shift_down = false) - : mouse_ray(mouse_ray), mouse_pos(mouse_pos), shift_down(shift_down) - {} - }; - -protected: - GLCanvas3D& m_parent; - - int m_group_id; - EState m_state; - int m_shortcut_key; -#if ENABLE_SVG_ICONS - std::string m_icon_filename; -#endif // ENABLE_SVG_ICONS - unsigned int m_sprite_id; - int m_hover_id; - bool m_dragging; - float m_base_color[3]; - float m_drag_color[3]; - float m_highlight_color[3]; - mutable std::vector<Grabber> m_grabbers; -#if ENABLE_IMGUI - ImGuiWrapper* m_imgui; -#endif // ENABLE_IMGUI - -public: -#if ENABLE_SVG_ICONS - GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); -#else - GLGizmoBase(GLCanvas3D& parent, unsigned int sprite_id); -#endif // ENABLE_SVG_ICONS - virtual ~GLGizmoBase() {} - - bool init() { return on_init(); } - - std::string get_name() const { return on_get_name(); } - - int get_group_id() const { return m_group_id; } - void set_group_id(int id) { m_group_id = id; } - - EState get_state() const { return m_state; } - void set_state(EState state) { m_state = state; on_set_state(); } - - int get_shortcut_key() const { return m_shortcut_key; } - void set_shortcut_key(int key) { m_shortcut_key = key; } - -#if ENABLE_SVG_ICONS - const std::string& get_icon_filename() const { return m_icon_filename; } -#endif // ENABLE_SVG_ICONS - - bool is_activable(const GLCanvas3D::Selection& selection) const { return on_is_activable(selection); } - bool is_selectable() const { return on_is_selectable(); } - - unsigned int get_sprite_id() const { return m_sprite_id; } - - int get_hover_id() const { return m_hover_id; } - void set_hover_id(int id); - - void set_highlight_color(const float* color); - - void enable_grabber(unsigned int id); - void disable_grabber(unsigned int id); - - void start_dragging(const GLCanvas3D::Selection& selection); - void stop_dragging(); - bool is_dragging() const { return m_dragging; } - - void update(const UpdateData& data, const GLCanvas3D::Selection& selection); - - void render(const GLCanvas3D::Selection& selection) const { on_render(selection); } - void render_for_picking(const GLCanvas3D::Selection& selection) const { on_render_for_picking(selection); } - -#if !ENABLE_IMGUI - virtual void create_external_gizmo_widgets(wxWindow *parent); -#endif // not ENABLE_IMGUI - -#if ENABLE_IMGUI - void render_input_window(float x, float y, float bottom_limit, const GLCanvas3D::Selection& selection) { on_render_input_window(x, y, bottom_limit, selection); } -#endif // ENABLE_IMGUI - -protected: - virtual bool on_init() = 0; - virtual std::string on_get_name() const = 0; - virtual void on_set_state() {} - virtual void on_set_hover_id() {} - virtual bool on_is_activable(const GLCanvas3D::Selection& selection) const { return true; } - virtual bool on_is_selectable() const { return true; } - virtual void on_enable_grabber(unsigned int id) {} - virtual void on_disable_grabber(unsigned int id) {} - virtual void on_start_dragging(const GLCanvas3D::Selection& selection) {} - virtual void on_stop_dragging() {} - virtual void on_update(const UpdateData& data, const GLCanvas3D::Selection& selection) = 0; - virtual void on_render(const GLCanvas3D::Selection& selection) const = 0; - virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const = 0; - -#if ENABLE_IMGUI - virtual void on_render_input_window(float x, float y, float bottom_limit, const GLCanvas3D::Selection& selection) {} -#endif // ENABLE_IMGUI - - float picking_color_component(unsigned int id) const; - void render_grabbers(const BoundingBoxf3& box) const; - void render_grabbers(float size) const; - void render_grabbers_for_picking(const BoundingBoxf3& box) const; - - void set_tooltip(const std::string& tooltip) const; - std::string format(float value, unsigned int decimals) const; -}; - -class GLGizmoRotate : public GLGizmoBase -{ - static const float Offset; - static const unsigned int CircleResolution; - static const unsigned int AngleResolution; - static const unsigned int ScaleStepsCount; - static const float ScaleStepRad; - static const unsigned int ScaleLongEvery; - static const float ScaleLongTooth; - static const unsigned int SnapRegionsCount; - static const float GrabberOffset; - -public: - enum Axis : unsigned char - { - X, - Y, - Z - }; - -private: - Axis m_axis; - double m_angle; - - GLUquadricObj* m_quadric; - - mutable Vec3d m_center; - mutable float m_radius; - - mutable float m_snap_coarse_in_radius; - mutable float m_snap_coarse_out_radius; - mutable float m_snap_fine_in_radius; - mutable float m_snap_fine_out_radius; - -public: - GLGizmoRotate(GLCanvas3D& parent, Axis axis); - GLGizmoRotate(const GLGizmoRotate& other); - virtual ~GLGizmoRotate(); - - double get_angle() const { return m_angle; } - void set_angle(double angle); - -protected: - virtual bool on_init(); - virtual std::string on_get_name() const { return ""; } - virtual void on_start_dragging(const GLCanvas3D::Selection& selection); - virtual void on_update(const UpdateData& data, const GLCanvas3D::Selection& selection); - virtual void on_render(const GLCanvas3D::Selection& selection) const; - virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const; - -private: - void render_circle() const; - void render_scale() const; - void render_snap_radii() const; - void render_reference_radius() const; - void render_angle() const; - void render_grabber(const BoundingBoxf3& box) const; - void render_grabber_extension(const BoundingBoxf3& box, bool picking) const; - - void transform_to_local(const GLCanvas3D::Selection& selection) const; - // returns the intersection of the mouse ray with the plane perpendicular to the gizmo axis, in local coordinate - Vec3d mouse_position_in_local_plane(const Linef3& mouse_ray, const GLCanvas3D::Selection& selection) const; -}; - -class GLGizmoRotate3D : public GLGizmoBase -{ - std::vector<GLGizmoRotate> m_gizmos; - -public: -#if ENABLE_SVG_ICONS - GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); -#else - GLGizmoRotate3D(GLCanvas3D& parent, unsigned int sprite_id); -#endif // ENABLE_SVG_ICONS - - Vec3d get_rotation() const { return Vec3d(m_gizmos[X].get_angle(), m_gizmos[Y].get_angle(), m_gizmos[Z].get_angle()); } - void set_rotation(const Vec3d& rotation) { m_gizmos[X].set_angle(rotation(0)); m_gizmos[Y].set_angle(rotation(1)); m_gizmos[Z].set_angle(rotation(2)); } - -protected: - virtual bool on_init(); - virtual std::string on_get_name() const; - virtual void on_set_state() - { - for (GLGizmoRotate& g : m_gizmos) - { - g.set_state(m_state); - } - } - virtual void on_set_hover_id() - { - for (unsigned int i = 0; i < 3; ++i) - { - m_gizmos[i].set_hover_id((m_hover_id == i) ? 0 : -1); - } - } - virtual bool on_is_activable(const GLCanvas3D::Selection& selection) const { return !selection.is_wipe_tower(); } - virtual void on_enable_grabber(unsigned int id) - { - if ((0 <= id) && (id < 3)) - m_gizmos[id].enable_grabber(0); - } - virtual void on_disable_grabber(unsigned int id) - { - if ((0 <= id) && (id < 3)) - m_gizmos[id].disable_grabber(0); - } - virtual void on_start_dragging(const GLCanvas3D::Selection& selection); - virtual void on_stop_dragging(); - virtual void on_update(const UpdateData& data, const GLCanvas3D::Selection& selection) - { - for (GLGizmoRotate& g : m_gizmos) - { - g.update(data, selection); - } - } - virtual void on_render(const GLCanvas3D::Selection& selection) const; - virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const - { - for (const GLGizmoRotate& g : m_gizmos) - { - g.render_for_picking(selection); - } - } - -#if ENABLE_IMGUI - virtual void on_render_input_window(float x, float y, float bottom_limit, const GLCanvas3D::Selection& selection); -#endif // ENABLE_IMGUI -}; - -class GLGizmoScale3D : public GLGizmoBase -{ - static const float Offset; - - mutable BoundingBoxf3 m_box; - - Vec3d m_scale; - - double m_snap_step; - - Vec3d m_starting_scale; - Vec3d m_starting_drag_position; - BoundingBoxf3 m_starting_box; - -public: -#if ENABLE_SVG_ICONS - GLGizmoScale3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); -#else - GLGizmoScale3D(GLCanvas3D& parent, unsigned int sprite_id); -#endif // ENABLE_SVG_ICONS - - double get_snap_step(double step) const { return m_snap_step; } - void set_snap_step(double step) { m_snap_step = step; } - - const Vec3d& get_scale() const { return m_scale; } - void set_scale(const Vec3d& scale) { m_starting_scale = scale; m_scale = scale; } - -protected: - virtual bool on_init(); - virtual std::string on_get_name() const; - virtual bool on_is_activable(const GLCanvas3D::Selection& selection) const { return !selection.is_wipe_tower(); } - virtual void on_start_dragging(const GLCanvas3D::Selection& selection); - virtual void on_update(const UpdateData& data, const GLCanvas3D::Selection& selection); - virtual void on_render(const GLCanvas3D::Selection& selection) const; - virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const; - -#if ENABLE_IMGUI - virtual void on_render_input_window(float x, float y, float bottom_limit, const GLCanvas3D::Selection& selection); -#endif // ENABLE_IMGUI - -private: - void render_grabbers_connection(unsigned int id_1, unsigned int id_2) const; - - void do_scale_x(const UpdateData& data); - void do_scale_y(const UpdateData& data); - void do_scale_z(const UpdateData& data); - void do_scale_uniform(const UpdateData& data); - - double calc_ratio(const UpdateData& data) const; -}; - -class GLGizmoMove3D : public GLGizmoBase -{ - static const double Offset; - - Vec3d m_displacement; - - double m_snap_step; - - Vec3d m_starting_drag_position; - Vec3d m_starting_box_center; - Vec3d m_starting_box_bottom_center; - - GLUquadricObj* m_quadric; - -public: -#if ENABLE_SVG_ICONS - GLGizmoMove3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); -#else - GLGizmoMove3D(GLCanvas3D& parent, unsigned int sprite_id); -#endif // ENABLE_SVG_ICONS - virtual ~GLGizmoMove3D(); - - double get_snap_step(double step) const { return m_snap_step; } - void set_snap_step(double step) { m_snap_step = step; } - - const Vec3d& get_displacement() const { return m_displacement; } - -protected: - virtual bool on_init(); - virtual std::string on_get_name() const; - virtual void on_start_dragging(const GLCanvas3D::Selection& selection); - virtual void on_stop_dragging(); - virtual void on_update(const UpdateData& data, const GLCanvas3D::Selection& selection); - virtual void on_render(const GLCanvas3D::Selection& selection) const; - virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const; - -#if ENABLE_IMGUI - virtual void on_render_input_window(float x, float y, float bottom_limit, const GLCanvas3D::Selection& selection); -#endif // ENABLE_IMGUI - -private: - double calc_projection(const UpdateData& data) const; - void render_grabber_extension(Axis axis, const BoundingBoxf3& box, bool picking) const; -}; - -class GLGizmoFlatten : public GLGizmoBase -{ -// This gizmo does not use grabbers. The m_hover_id relates to polygon managed by the class itself. - -private: - mutable Vec3d m_normal; - - struct PlaneData { - std::vector<Vec3d> vertices; - Vec3d normal; - float area; - }; - - // This holds information to decide whether recalculation is necessary: - std::vector<Transform3d> m_volumes_matrices; - std::vector<ModelVolumeType> m_volumes_types; - Vec3d m_first_instance_scale; - Vec3d m_first_instance_mirror; - - std::vector<PlaneData> m_planes; - bool m_planes_valid = false; - mutable Vec3d m_starting_center; - const ModelObject* m_model_object = nullptr; - std::vector<const Transform3d*> instances_matrices; - - void update_planes(); - bool is_plane_update_necessary() const; - -public: -#if ENABLE_SVG_ICONS - GLGizmoFlatten(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); -#else - GLGizmoFlatten(GLCanvas3D& parent, unsigned int sprite_id); -#endif // ENABLE_SVG_ICONS - - void set_flattening_data(const ModelObject* model_object); - Vec3d get_flattening_normal() const; - -protected: - virtual bool on_init(); - virtual std::string on_get_name() const; - virtual bool on_is_activable(const GLCanvas3D::Selection& selection) const; - virtual void on_start_dragging(const GLCanvas3D::Selection& selection); - virtual void on_update(const UpdateData& data, const GLCanvas3D::Selection& selection) {} - virtual void on_render(const GLCanvas3D::Selection& selection) const; - virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const; - virtual void on_set_state() - { - if (m_state == On && is_plane_update_necessary()) - update_planes(); - } -}; - -#define SLAGIZMO_IMGUI_MODAL 0 - -class GLGizmoSlaSupports : public GLGizmoBase -{ -private: - ModelObject* m_model_object = nullptr; - ModelObject* m_old_model_object = nullptr; - int m_active_instance = -1; - int m_old_instance_id = -1; - Vec3f unproject_on_mesh(const Vec2d& mouse_pos); - - const float RenderPointScale = 1.f; - - GLUquadricObj* m_quadric; - Eigen::MatrixXf m_V; // vertices - Eigen::MatrixXi m_F; // facets indices - igl::AABB<Eigen::MatrixXf,3> m_AABB; - - struct SourceDataSummary { - Geometry::Transformation transformation; - }; - - // This holds information to decide whether recalculation is necessary: - SourceDataSummary m_source_data; - - mutable Vec3d m_starting_center; - -public: -#if ENABLE_SVG_ICONS - GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); -#else - GLGizmoSlaSupports(GLCanvas3D& parent, unsigned int sprite_id); -#endif // ENABLE_SVG_ICONS - virtual ~GLGizmoSlaSupports(); - void set_sla_support_data(ModelObject* model_object, const GLCanvas3D::Selection& selection); - bool mouse_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down); - void delete_selected_points(bool force = false); - -private: - bool on_init(); - void on_update(const UpdateData& data, const GLCanvas3D::Selection& selection); - virtual void on_render(const GLCanvas3D::Selection& selection) const; - virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const; - - void render_selection_rectangle() const; - void render_points(const GLCanvas3D::Selection& selection, bool picking = false) const; - bool is_mesh_update_necessary() const; - void update_mesh(); - -#if !ENABLE_IMGUI - void render_tooltip_texture() const; - mutable GLTexture m_tooltip_texture; - mutable GLTexture m_reset_texture; -#endif // not ENABLE_IMGUI - - bool m_lock_unique_islands = false; - bool m_editing_mode = false; // Is editing mode active? - bool m_old_editing_state = false; // To keep track of whether the user toggled between the modes (needed for imgui refreshes). - float m_new_point_head_diameter = 0.4f; // Size of a new point. - float m_minimal_point_distance = 20.f; - float m_density = 100.f; - std::vector<std::pair<sla::SupportPoint, bool>> m_editing_mode_cache; // a support point and whether it is currently selected - - bool m_selection_rectangle_active = false; - Vec2d m_selection_rectangle_start_corner; - Vec2d m_selection_rectangle_end_corner; - bool m_ignore_up_event = false; - bool m_combo_box_open = false; // To ensure proper rendering of the imgui combobox. - bool m_unsaved_changes = false; // Are there unsaved changes in manual mode? - bool m_selection_empty = true; - EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state) - int m_canvas_width; - int m_canvas_height; - - std::vector<ConfigOption*> get_config_options(const std::vector<std::string>& keys) const; - - // Methods that do the model_object and editing cache synchronization, - // editing mode selection, etc: - enum { - AllPoints = -2, - NoPoints, - }; - void select_point(int i); - void editing_mode_apply_changes(); - void editing_mode_discard_changes(); - void editing_mode_reload_cache(); - void get_data_from_backend(); - void auto_generate(); - void switch_to_editing_mode(); - -protected: - void on_set_state() override; - void on_start_dragging(const GLCanvas3D::Selection& selection) override; - -#if ENABLE_IMGUI - virtual void on_render_input_window(float x, float y, float bottom_limit, const GLCanvas3D::Selection& selection) override; -#endif // ENABLE_IMGUI - - virtual std::string on_get_name() const; - virtual bool on_is_activable(const GLCanvas3D::Selection& selection) const; - virtual bool on_is_selectable() const; -}; - - -#if !ENABLE_IMGUI -class GLGizmoCutPanel; -#endif // not ENABLE_IMGUI - -class GLGizmoCut : public GLGizmoBase -{ - static const double Offset; - static const double Margin; - static const std::array<float, 3> GrabberColor; - - mutable double m_cut_z; - double m_start_z; - mutable double m_max_z; - Vec3d m_drag_pos; - Vec3d m_drag_center; - bool m_keep_upper; - bool m_keep_lower; - bool m_rotate_lower; -#if !ENABLE_IMGUI - GLGizmoCutPanel *m_panel; -#endif // not ENABLE_IMGUI - -public: -#if ENABLE_SVG_ICONS - GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); -#else - GLGizmoCut(GLCanvas3D& parent, unsigned int sprite_id); -#endif // ENABLE_SVG_ICONS - -#if !ENABLE_IMGUI - virtual void create_external_gizmo_widgets(wxWindow *parent); -#endif // not ENABLE_IMGUI -#if !ENABLE_IMGUI -#endif // not ENABLE_IMGUI - -protected: - virtual bool on_init(); - virtual std::string on_get_name() const; - virtual void on_set_state(); - virtual bool on_is_activable(const GLCanvas3D::Selection& selection) const; - virtual void on_start_dragging(const GLCanvas3D::Selection& selection); - virtual void on_update(const UpdateData& data, const GLCanvas3D::Selection& selection); - virtual void on_render(const GLCanvas3D::Selection& selection) const; - virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const; - -#if ENABLE_IMGUI - virtual void on_render_input_window(float x, float y, float bottom_limit, const GLCanvas3D::Selection& selection); -#endif // ENABLE_IMGUI -private: - void update_max_z(const GLCanvas3D::Selection& selection) const; - void set_cut_z(double cut_z) const; - void perform_cut(const GLCanvas3D::Selection& selection); - double calc_projection(const Linef3& mouse_ray) const; -}; - - -} // namespace GUI -} // namespace Slic3r - -#endif // slic3r_GLGizmo_hpp_ - diff --git a/src/slic3r/GUI/GLTexture.hpp b/src/slic3r/GUI/GLTexture.hpp index 017255647..e00b3a3be 100644 --- a/src/slic3r/GUI/GLTexture.hpp +++ b/src/slic3r/GUI/GLTexture.hpp @@ -2,6 +2,7 @@ #define slic3r_GLTexture_hpp_ #include <string> +#include <vector> class wxImage; diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index 11e94f2ca..4a3ccc356 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -148,9 +148,6 @@ void config_wizard(int reason) _(L("Please check and fix your object list.")), _(L("Attention!"))); } - - // Load the currently selected preset into the GUI, update the preset selection box. - // wxGetApp().load_current_presets(); // #ys_FIXME_to_delete presets are loaded now in select_preset function } // opt_index = 0, by the reason of zero-index in ConfigOptionVector by default (in case only one element) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index b61457226..bd553eaa4 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -17,6 +17,7 @@ #include <wx/dir.h> #include <wx/wupdlock.h> #include <wx/filefn.h> +#include <wx/sysopt.h> #include "libslic3r/Utils.hpp" #include "libslic3r/Model.hpp" @@ -79,9 +80,7 @@ IMPLEMENT_APP(GUI_App) GUI_App::GUI_App() : wxApp() , m_em_unit(10) -#if ENABLE_IMGUI , m_imgui(new ImGuiWrapper()) -#endif // ENABLE_IMGUI {} bool GUI_App::OnInit() @@ -90,14 +89,17 @@ bool GUI_App::OnInit() const wxString resources_dir = from_u8(Slic3r::resources_dir()); wxCHECK_MSG(wxDirExists(resources_dir), false, wxString::Format("Resources path does not exist or is not a directory: %s", resources_dir)); - -#if ENABLE_IMGUI wxCHECK_MSG(m_imgui->init(), false, "Failed to initialize ImGui"); -#endif // ENABLE_IMGUI - SetAppName("Slic3rPE-alpha"); + SetAppName("Slic3rPE-beta"); SetAppDisplayName("Slic3r Prusa Edition"); +// Enable this to get the default Win32 COMCTRL32 behavior of static boxes. +// wxSystemOptions::SetOption("msw.staticbox.optimized-paint", 0); +// Enable this to disable Windows Vista themes for all wxNotebooks. The themes seem to lead to terrible +// performance when working on high resolution multi-display setups. +// wxSystemOptions::SetOption("msw.notebook.themed-background", 0); + // Slic3r::debugf "wxWidgets version %s, Wx version %s\n", wxVERSION_STRING, wxVERSION; // Set the Slic3r data directory at the Slic3r XS module. @@ -161,22 +163,13 @@ bool GUI_App::OnInit() Bind(wxEVT_IDLE, [this](wxIdleEvent& event) { + if (! plater_) + return; + if (app_config->dirty() && app_config->get("autosave") == "1") app_config->save(); - // ! Temporary workaround for the correct behavior of the Scrolled sidebar panel - // Do this "manipulations" only once ( after (re)create of the application ) - if (plater_ && sidebar().obj_list()->GetMinHeight() > 15 * wxGetApp().em_unit()) - { - wxWindowUpdateLocker noUpdates_sidebar(&sidebar()); - sidebar().obj_list()->SetMinSize(wxSize(-1, 15 * wxGetApp().em_unit())); - - // !!! to correct later layouts - update_mode(); // update view mode after fix of the object_list size - } - - if (this->plater() != nullptr) - this->obj_manipul()->update_if_dirty(); + this->obj_manipul()->update_if_dirty(); // Preset updating & Configwizard are done after the above initializations, // and after MainFrame is created & shown. @@ -203,12 +196,13 @@ bool GUI_App::OnInit() } preset_updater->sync(preset_bundle); }); - - load_current_presets(); } }); + load_current_presets(); + mainframe->Show(true); + update_mode(); // update view mode after fix of the object_list size m_initialized = true; return true; } @@ -259,6 +253,7 @@ void GUI_App::init_fonts() { m_small_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); m_bold_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold(); + m_normal_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); #ifdef __WXMAC__ m_small_font.SetPointSize(11); @@ -532,20 +527,7 @@ void GUI_App::save_mode(const /*ConfigOptionMode*/int mode) // Update view mode according to selected menu void GUI_App::update_mode() { - wxWindowUpdateLocker noUpdates(&sidebar()); - - const ConfigOptionMode mode = wxGetApp().get_mode(); - - obj_list()->get_sizer()->Show(mode > comSimple); - sidebar().set_mode_value(mode); -// sidebar().show_buttons(mode == comExpert); - obj_list()->unselect_objects(); - obj_list()->update_selections(); - obj_list()->update_object_menu(); - - sidebar().update_mode_sizer(mode); - - sidebar().Layout(); + sidebar().update_mode(); for (auto tab : tabs_list) tab->update_visibility(); diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 4c23e3eb7..e10a7b95e 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -5,9 +5,7 @@ #include <string> #include "libslic3r/PrintConfig.hpp" #include "MainFrame.hpp" -#if ENABLE_IMGUI #include "ImGuiWrapper.hpp" -#endif // ENABLE_IMGUI #include <wx/app.h> #include <wx/colour.h> @@ -80,16 +78,14 @@ class GUI_App : public wxApp wxFont m_small_font; wxFont m_bold_font; + wxFont m_normal_font; size_t m_em_unit; // width of a "m"-symbol in pixels for current system font // Note: for 100% Scale m_em_unit = 10 -> it's a good enough coefficient for a size setting of controls wxLocale* m_wxLocale{ nullptr }; -#if ENABLE_IMGUI std::unique_ptr<ImGuiWrapper> m_imgui; -#endif // ENABLE_IMGUI - std::unique_ptr<PrintHostJobQueue> m_printhost_job_queue; public: @@ -111,6 +107,7 @@ public: const wxFont& small_font() { return m_small_font; } const wxFont& bold_font() { return m_bold_font; } + const wxFont& normal_font() { return m_normal_font; } size_t em_unit() const { return m_em_unit; } void set_em_unit(const size_t em_unit) { m_em_unit = em_unit; } @@ -166,9 +163,7 @@ public: std::vector<Tab *> tabs_list; -#if ENABLE_IMGUI ImGuiWrapper* imgui() { return m_imgui.get(); } -#endif // ENABLE_IMGUI PrintHostJobQueue& printhost_job_queue() { return *m_printhost_job_queue.get(); } diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index eb3744bef..abfc067ff 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -43,6 +43,18 @@ static PrinterTechnology printer_technology() return wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology(); } +// Config from current edited printer preset +static DynamicPrintConfig& printer_config() +{ + return wxGetApp().preset_bundle->printers.get_edited_preset().config; +} + +static int extruders_count() +{ + return printer_technology() == ptSLA ? 1 : + printer_config().option<ConfigOptionFloats>("nozzle_diameter")->values.size(); +} + ObjectList::ObjectList(wxWindow* parent) : wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDV_MULTIPLE), m_parent(parent) @@ -115,10 +127,7 @@ ObjectList::~ObjectList() void ObjectList::create_objects_ctrl() { - // temporary workaround for the correct behavior of the Scrolled sidebar panel: - // 1. set a height of the list to some big value - // 2. change it to the normal min value (200) after first whole App updating/layouting - SetMinSize(wxSize(-1, 3000)); // #ys_FIXME + SetMinSize(wxSize(-1, 15 * wxGetApp().em_unit())); m_sizer = new wxBoxSizer(wxVERTICAL); m_sizer->Add(this, 1, wxGROW); @@ -430,11 +439,12 @@ void ObjectList::OnContextMenu(wxDataViewEvent&) else if (title == _("Name") && pt.x >15 && m_objects_model->GetBitmap(item).GetRefData() == m_bmp_manifold_warning.GetRefData()) { - if (is_windows10()) { - const auto obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); - wxGetApp().plater()->fix_through_netfabb(obj_idx); - } + if (is_windows10()) + fix_through_netfabb(); } + else if (title == _("Extruder")) + show_extruder_selection_menu(); + #ifndef __WXMSW__ GetMainWindow()->SetToolTip(""); // hide tooltip #endif //__WXMSW__ @@ -442,9 +452,13 @@ void ObjectList::OnContextMenu(wxDataViewEvent&) void ObjectList::show_context_menu() { - if (multiple_selection() && selected_instances_of_same_object()) + if (multiple_selection()) { - wxGetApp().plater()->PopupMenu(&m_menu_instance); + if (selected_instances_of_same_object()) + wxGetApp().plater()->PopupMenu(&m_menu_instance); + else + show_extruder_selection_menu(); + return; } @@ -649,8 +663,7 @@ void ObjectList::get_options_menu(settings_menu_hierarchy& settings_menu, const { auto options = get_options(is_part); - auto extruders_cnt = printer_technology() == ptSLA ? 1 : - wxGetApp().preset_bundle->printers.get_edited_preset().config.option<ConfigOptionFloats>("nozzle_diameter")->values.size(); + const int extruders_cnt = extruders_count(); DynamicPrintConfig config; for (auto& option : options) @@ -660,9 +673,7 @@ void ObjectList::get_options_menu(settings_menu_hierarchy& settings_menu, const if (category.empty() || (category == "Extruders" && extruders_cnt == 1)) continue; - const std::string& label = opt->label.empty() ? opt->full_label : - opt->full_label.empty() ? opt->label : - opt->full_label + " " + opt->label;; + const std::string& label = !opt->full_label.empty() ? opt->full_label : opt->label; std::pair<std::string, std::string> option_label(option, label); std::vector< std::pair<std::string, std::string> > new_category; auto& cat_opt_label = settings_menu.find(category) == settings_menu.end() ? new_category : settings_menu.at(category); @@ -1086,8 +1097,7 @@ void ObjectList::create_freq_settings_popupmenu(wxMenu *menu) const FreqSettingsBundle& bundle = printer_technology() == ptFFF ? FREQ_SETTINGS_BUNDLE_FFF : FREQ_SETTINGS_BUNDLE_SLA; - auto extruders_cnt = printer_technology() == ptSLA ? 1 : - wxGetApp().preset_bundle->printers.get_edited_preset().config.option<ConfigOptionFloats>("nozzle_diameter")->values.size(); + const int extruders_cnt = extruders_count(); for (auto& it : bundle) { if (it.first.empty() || it.first == "Extruders" && extruders_cnt == 1) @@ -1284,7 +1294,7 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode const wxString name = _(L("Generic")) + "-" + _(type_name); TriangleMesh mesh; - auto& bed_shape = wxGetApp().preset_bundle->printers.get_edited_preset().config.option<ConfigOptionPoints>("bed_shape")->values; + auto& bed_shape = printer_config().option<ConfigOptionPoints>("bed_shape")->values; const auto& sz = BoundingBoxf(bed_shape).size(); const auto side = 0.1 * std::max(sz(0), sz(1)); @@ -1412,12 +1422,14 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con // Cannot delete a wipe tower. return false; + ModelObject* object = (*m_objects)[obj_idx]; + if (type == itVolume) { - const auto volume = (*m_objects)[obj_idx]->volumes[idx]; + const auto volume = object->volumes[idx]; // if user is deleting the last solid part, throw error int solid_cnt = 0; - for (auto vol : (*m_objects)[obj_idx]->volumes) + for (auto vol : object->volumes) if (vol->is_model_part()) ++solid_cnt; if (volume->is_model_part() && solid_cnt == 1) { @@ -1425,14 +1437,23 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con return false; } - (*m_objects)[obj_idx]->delete_volume(idx); + object->delete_volume(idx); + + if (object->volumes.size() == 1) + { + const auto last_volume = object->volumes[0]; + if (!last_volume->config.empty()) { + object->config.apply(last_volume->config); + last_volume->config.clear(); + } + } } else if (type == itInstance) { - if ((*m_objects)[obj_idx]->instances.size() == 1) { + if (object->instances.size() == 1) { Slic3r::GUI::show_error(nullptr, _(L("You can't delete the last intance from object."))); return false; } - (*m_objects)[obj_idx]->delete_instance(idx); + object->delete_instance(idx); } else return false; @@ -1452,7 +1473,7 @@ void ObjectList::split() ModelVolume* volume; if (!get_volume_by_item(item, volume)) return; - DynamicPrintConfig& config = wxGetApp().preset_bundle->printers.get_edited_preset().config; + DynamicPrintConfig& config = printer_config(); const ConfigOption *nozzle_dmtrs_opt = config.option("nozzle_diameter", false); const auto nozzle_dmrs_cnt = (nozzle_dmtrs_opt == nullptr) ? size_t(1) : dynamic_cast<const ConfigOptionFloats*>(nozzle_dmtrs_opt)->values.size(); if (volume->split(nozzle_dmrs_cnt) == 1) { @@ -1522,12 +1543,7 @@ bool ObjectList::is_splittable() if (!get_volume_by_item(item, volume) || !volume) return false; - int splittable = volume->is_splittable(); - if (splittable == -1) { - splittable = (int)volume->mesh.has_multiple_patches(); - volume->set_splittable(splittable); - } - return splittable != 0; + return volume->is_splittable(); } bool ObjectList::selected_instances_of_same_object() @@ -1762,6 +1778,12 @@ void ObjectList::delete_from_model_and_list(const std::vector<ItemForDelete>& it if (item->type&itVolume) { m_objects_model->Delete(m_objects_model->GetItemByVolumeId(item->obj_idx, item->sub_obj_idx)); + if ((*m_objects)[item->obj_idx]->volumes.size() == 1 && + (*m_objects)[item->obj_idx]->config.has("extruder")) + { + const wxString extruder = wxString::Format("%d", (*m_objects)[item->obj_idx]->config.option<ConfigOptionInt>("extruder")->value); + m_objects_model->SetValue(extruder, m_objects_model->GetItemById(item->obj_idx), 1); + } wxGetApp().plater()->canvas3D()->ensure_on_bed(item->obj_idx); } else @@ -2000,6 +2022,8 @@ void ObjectList::update_selections_on_canvas() void ObjectList::select_item(const wxDataViewItem& item) { + if (! item.IsOk()) { return; } + m_prevent_list_events = true; UnselectAll(); @@ -2280,13 +2304,37 @@ void ObjectList::fix_through_netfabb() const if (!item) return; - ItemType type = m_objects_model->GetItemType(item); + const ItemType type = m_objects_model->GetItemType(item); + + const int obj_idx = type & itObject ? m_objects_model->GetIdByItem(item) : + type & itVolume ? m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)) : -1; + + const int vol_idx = type & itVolume ? m_objects_model->GetVolumeIdByItem(item) : -1; + + wxGetApp().plater()->fix_through_netfabb(obj_idx, vol_idx); - if (type & itObject) - wxGetApp().plater()->fix_through_netfabb(m_objects_model->GetIdByItem(item)); - else if (type & itVolume) - wxGetApp().plater()->fix_through_netfabb(m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)), - m_objects_model->GetVolumeIdByItem(item)); + update_item_error_icon(obj_idx, vol_idx); +} + +void ObjectList::update_item_error_icon(const int obj_idx, const int vol_idx) const +{ + const wxDataViewItem item = vol_idx <0 ? m_objects_model->GetItemById(obj_idx) : + m_objects_model->GetItemByVolumeId(obj_idx, vol_idx); + if (!item) + return; + + auto model_object = (*m_objects)[obj_idx]; + + const stl_stats& stats = model_object->volumes[vol_idx<0 ? 0 : vol_idx]->mesh.stl.stats; + const int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed + + stats.facets_added + stats.facets_reversed + stats.backwards_edges; + + if (errors == 0) { + // delete Error_icon if all errors are fixed + wxVariant variant; + variant << PrusaDataViewBitmapText(from_u8(model_object->name), wxNullBitmap); + m_objects_model->SetValue(variant, item, 0); + } } void ObjectList::ItemValueChanged(wxDataViewEvent &event) @@ -2309,5 +2357,86 @@ void ObjectList::OnEditingDone(wxDataViewEvent &event) _(L("the following characters are not allowed:")) + " <>:/\\|?*\""); } +void ObjectList::show_extruder_selection_menu() +{ + wxDataViewItemArray sels; + GetSelections(sels); + + for (const wxDataViewItem& item : sels) + if (!(m_objects_model->GetItemType(item) & (itVolume | itObject))) + // show this menu only for Object(s)/Volume(s) selection + return; + + wxMenu* menu = new wxMenu(); + append_menu_item(menu, wxID_ANY, _(L("Set extruder for selected items")), + _(L("Select extruder number for selected objects and/or parts")), + [this](wxCommandEvent&) { extruder_selection(); }, "", menu); + PopupMenu(menu); +} + +void ObjectList::extruder_selection() +{ + wxArrayString choices; + choices.Add("default"); + for (int i = 1; i <= extruders_count(); ++i) + choices.Add(wxString::Format("%d", i)); + + const wxString& selected_extruder = wxGetSingleChoice(_(L("Select extruder number:")), + _(L("This extruder will be set for selected items")), + choices, 0, this); + if (selected_extruder.IsEmpty()) + return; + + const int extruder_num = selected_extruder == "default" ? 0 : atoi(selected_extruder.c_str()); + +// /* Another variant for an extruder selection */ +// extruder_num = wxGetNumberFromUser(_(L("Attention!!! \n" +// "It's a possibile to set an extruder number \n" +// "for whole Object(s) and/or object Part(s), \n" +// "not for an Instance. ")), +// _(L("Enter extruder number:")), +// _(L("This extruder will be set for selected items")), +// 1, 1, 5, this); + + set_extruder_for_selected_items(extruder_num); +} + +void ObjectList::set_extruder_for_selected_items(const int extruder) const +{ + wxDataViewItemArray sels; + GetSelections(sels); + + for (const wxDataViewItem& item : sels) + { + const ItemType type = m_objects_model->GetItemType(item); + + const int obj_idx = type & itObject ? m_objects_model->GetIdByItem(item) : + m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); + + const int vol_idx = type & itVolume ? m_objects_model->GetVolumeIdByItem(item) : -1; + + DynamicPrintConfig& config = type & itObject ? (*m_objects)[obj_idx]->config : + (*m_objects)[obj_idx]->volumes[vol_idx]->config; + + if (config.has("extruder")) { + if (extruder == 0) + config.erase("extruder"); + else + config.option<ConfigOptionInt>("extruder")->value = extruder; + } + else if (extruder > 0) + config.set_key_value("extruder", new ConfigOptionInt(extruder)); + + const wxString extruder_str = extruder == 0 ? wxString ("default") : + wxString::Format("%d", config.option<ConfigOptionInt>("extruder")->value); + m_objects_model->SetValue(extruder_str, item, 1); + + wxGetApp().plater()->canvas3D()->ensure_on_bed(obj_idx); + } + + // update scene + wxGetApp().plater()->update(); +} + } //namespace GUI } //namespace Slic3r
\ No newline at end of file diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 762020bda..fa4db8f9d 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -270,6 +270,7 @@ public: void split_instances(); void rename_item(); void fix_through_netfabb() const; + void update_item_error_icon(const int obj_idx, int vol_idx) const ; private: void OnChar(wxKeyEvent& event); void OnContextMenu(wxDataViewEvent &event); @@ -282,6 +283,10 @@ private: void ItemValueChanged(wxDataViewEvent &event); void OnEditingDone(wxDataViewEvent &event); + void show_extruder_selection_menu(); + void extruder_selection(); + void set_extruder_for_selected_items(const int extruder) const ; + std::vector<std::string> get_options(const bool is_part); const std::vector<std::string>& get_options_for_bundle(const wxString& bundle_name); void get_options_menu(settings_menu_hierarchy& settings_menu, const bool is_part); diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index d48bb5f49..9860f26b1 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -27,17 +27,11 @@ namespace Slic3r { namespace GUI { -View3D::View3D(wxWindow* parent, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process) + View3D::View3D(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process) : m_canvas_widget(nullptr) , m_canvas(nullptr) -#if !ENABLE_IMGUI - , m_gizmo_widget(nullptr) -#endif // !ENABLE_IMGUI - , m_model(nullptr) - , m_config(nullptr) - , m_process(nullptr) { - init(parent, model, config, process); + init(parent, bed, camera, view_toolbar, model, config, process); } View3D::~View3D() @@ -50,13 +44,13 @@ View3D::~View3D() } } -bool View3D::init(wxWindow* parent, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process) +bool View3D::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process) { if (!Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 /* disable wxTAB_TRAVERSAL */)) return false; m_canvas_widget = GLCanvas3DManager::create_wxglcanvas(this); - _3DScene::add_canvas(m_canvas_widget); + _3DScene::add_canvas(m_canvas_widget, bed, camera, view_toolbar); m_canvas = _3DScene::get_canvas(this->m_canvas_widget); m_canvas->allow_multisample(GLCanvas3DManager::can_multisample()); @@ -70,17 +64,8 @@ bool View3D::init(wxWindow* parent, Model* model, DynamicPrintConfig* config, Ba m_canvas->enable_gizmos(true); m_canvas->enable_toolbar(true); -#if !ENABLE_IMGUI - m_gizmo_widget = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize); - m_gizmo_widget->SetSizer(new wxBoxSizer(wxVERTICAL)); - m_canvas->set_external_gizmo_widgets_parent(m_gizmo_widget); -#endif // !ENABLE_IMGUI - wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); main_sizer->Add(m_canvas_widget, 1, wxALL | wxEXPAND, 0); -#if !ENABLE_IMGUI - main_sizer->Add(m_gizmo_widget, 0, wxALL | wxEXPAND, 0); -#endif // !ENABLE_IMGUI SetSizer(main_sizer); SetMinSize(GetSize()); @@ -89,18 +74,6 @@ bool View3D::init(wxWindow* parent, Model* model, DynamicPrintConfig* config, Ba return true; } -void View3D::set_bed(Bed3D* bed) -{ - if (m_canvas != nullptr) - m_canvas->set_bed(bed); -} - -void View3D::set_view_toolbar(GLToolbar* toolbar) -{ - if (m_canvas != nullptr) - m_canvas->set_view_toolbar(toolbar); -} - void View3D::set_as_dirty() { if (m_canvas != nullptr) @@ -193,7 +166,7 @@ void View3D::render() m_canvas->set_as_dirty(); } -Preview::Preview(wxWindow* parent, DynamicPrintConfig* config, BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data, std::function<void()> schedule_background_process_func) +Preview::Preview(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, DynamicPrintConfig* config, BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data, std::function<void()> schedule_background_process_func) : m_canvas_widget(nullptr) , m_canvas(nullptr) , m_double_slider_sizer(nullptr) @@ -213,28 +186,26 @@ Preview::Preview(wxWindow* parent, DynamicPrintConfig* config, BackgroundSlicing , m_loaded(false) , m_enabled(false) , m_schedule_background_process(schedule_background_process_func) + , m_volumes_cleanup_required(false) { - if (init(parent, config, process, gcode_preview_data)) + if (init(parent, bed, camera, view_toolbar)) { show_hide_ui_elements("none"); load_print(); } } -bool Preview::init(wxWindow* parent, DynamicPrintConfig* config, BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data) +bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar) { - if ((config == nullptr) || (process == nullptr) || (gcode_preview_data == nullptr)) - return false; - if (!Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 /* disable wxTAB_TRAVERSAL */)) return false; m_canvas_widget = GLCanvas3DManager::create_wxglcanvas(this); - _3DScene::add_canvas(m_canvas_widget); - m_canvas = _3DScene::get_canvas(this->m_canvas_widget); + _3DScene::add_canvas(m_canvas_widget, bed, camera, view_toolbar); + m_canvas = _3DScene::get_canvas(this->m_canvas_widget); m_canvas->allow_multisample(GLCanvas3DManager::can_multisample()); m_canvas->set_config(m_config); - m_canvas->set_process(process); + m_canvas->set_process(m_process); m_canvas->enable_legend_texture(true); m_canvas->enable_dynamic_background(true); @@ -342,18 +313,6 @@ Preview::~Preview() } } -void Preview::set_bed(Bed3D* bed) -{ - if (m_canvas != nullptr) - m_canvas->set_bed(bed); -} - -void Preview::set_view_toolbar(GLToolbar* toolbar) -{ - if (m_canvas != nullptr) - m_canvas->set_view_toolbar(toolbar); -} - void Preview::set_number_extruders(unsigned int number_extruders) { if (m_number_extruders != number_extruders) @@ -390,18 +349,6 @@ void Preview::select_view(const std::string& direction) m_canvas->select_view(direction); } -void Preview::set_viewport_from_scene(GLCanvas3D* canvas) -{ - if (canvas != nullptr) - m_canvas->set_viewport_from_scene(*canvas); -} - -void Preview::set_viewport_into_scene(GLCanvas3D* canvas) -{ - if (canvas != nullptr) - canvas->set_viewport_from_scene(*m_canvas); -} - void Preview::set_drop_target(wxDropTarget* target) { if (target != nullptr) @@ -417,18 +364,22 @@ void Preview::load_print() load_print_as_sla(); } -void Preview::reload_print(bool force, bool keep_volumes) +void Preview::reload_print(bool keep_volumes) { - if (!keep_volumes) + if (!IsShown()) + { + m_volumes_cleanup_required = !keep_volumes; + return; + } + + if (m_volumes_cleanup_required || !keep_volumes) { m_canvas->reset_volumes(); m_canvas->reset_legend_texture(); m_loaded = false; + m_volumes_cleanup_required = false; } - if (!IsShown() && !force) - return; - load_print(); } @@ -608,15 +559,14 @@ static int find_close_layer_idx(const std::vector<double>& zs, double &z, double return -1; } -void Preview::update_double_slider(const std::vector<double>& layers_z, bool force_sliders_full_range) +void Preview::update_double_slider(const std::vector<double>& layers_z) { // Save the initial slider span. double z_low = m_slider->GetLowerValueD(); double z_high = m_slider->GetHigherValueD(); bool was_empty = m_slider->GetMaxValue() == 0; - bool span_changed = layers_z.empty() || std::abs(layers_z.back() - m_slider->GetMaxValueD()) > 1e-6; - force_sliders_full_range |= was_empty | span_changed; - bool snap_to_min = force_sliders_full_range || m_slider->is_lower_at_min(); + bool force_sliders_full_range = was_empty; + bool snap_to_min = force_sliders_full_range || m_slider->is_lower_at_min(); bool snap_to_max = force_sliders_full_range || m_slider->is_higher_at_max(); std::vector<std::pair<int, double>> values; @@ -788,10 +738,11 @@ void Preview::load_print_as_fff() if (IsShown()) { - if (gcode_preview_data_valid) + if (gcode_preview_data_valid) { // Load the real G-code preview. m_canvas->load_gcode_preview(*m_gcode_preview_data, colors); - else + m_loaded = true; + } else // Load the initial preview based on slices, not the final G-code. m_canvas->load_preview(colors, color_print_values); show_hide_ui_elements(gcode_preview_data_valid ? "full" : "simple"); @@ -803,7 +754,6 @@ void Preview::load_print_as_fff() m_canvas_widget->Refresh(); } else update_sliders(zs); - m_loaded = true; } } diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index 1d65aff1b..59f62ded1 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -28,30 +28,20 @@ namespace GUI { class GLCanvas3D; class GLToolbar; class Bed3D; +struct Camera; class View3D : public wxPanel { wxGLCanvas* m_canvas_widget; GLCanvas3D* m_canvas; -#if !ENABLE_IMGUI - wxPanel* m_gizmo_widget; -#endif // !ENABLE_IMGUI - - Model* m_model; - DynamicPrintConfig* m_config; - BackgroundSlicingProcess* m_process; - public: - View3D(wxWindow* parent, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process); + View3D(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process); virtual ~View3D(); wxGLCanvas* get_wxglcanvas() { return m_canvas_widget; } GLCanvas3D* get_canvas3d() { return m_canvas; } - void set_bed(Bed3D* bed); - void set_view_toolbar(GLToolbar* toolbar); - void set_as_dirty(); void bed_shape_changed(); @@ -75,7 +65,7 @@ public: void render(); private: - bool init(wxWindow* parent, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process); + bool init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process); }; class Preview : public wxPanel @@ -96,6 +86,8 @@ class Preview : public wxPanel BackgroundSlicingProcess* m_process; GCodePreviewData* m_gcode_preview_data; + bool m_volumes_cleanup_required; + // Calling this function object forces Plater::schedule_background_process. std::function<void()> m_schedule_background_process; @@ -108,30 +100,25 @@ class Preview : public wxPanel PrusaDoubleSlider* m_slider {nullptr}; public: - Preview(wxWindow* parent, DynamicPrintConfig* config, BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data, std::function<void()> schedule_background_process = [](){}); + Preview(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, DynamicPrintConfig* config, BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data, std::function<void()> schedule_background_process = [](){}); virtual ~Preview(); wxGLCanvas* get_wxglcanvas() { return m_canvas_widget; } GLCanvas3D* get_canvas3d() { return m_canvas; } - void set_bed(Bed3D* bed); - void set_view_toolbar(GLToolbar* toolbar); - void set_number_extruders(unsigned int number_extruders); void set_canvas_as_dirty(); void set_enabled(bool enabled); void bed_shape_changed(); void select_view(const std::string& direction); - void set_viewport_from_scene(GLCanvas3D* canvas); - void set_viewport_into_scene(GLCanvas3D* canvas); void set_drop_target(wxDropTarget* target); void load_print(); - void reload_print(bool force = false, bool keep_volumes = false); + void reload_print(bool keep_volumes = false); void refresh_print(); private: - bool init(wxWindow* parent, DynamicPrintConfig* config, BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data); + bool init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar); void bind_event_handlers(); void unbind_event_handlers(); @@ -151,7 +138,7 @@ private: // Create/Update/Reset double slider on 3dPreview void create_double_slider(); - void update_double_slider(const std::vector<double>& layers_z, bool force_sliders_full_range = false); + void update_double_slider(const std::vector<double>& layers_z); void fill_slider_values(std::vector<std::pair<int, double>> &values, const std::vector<double> &layers_z); void reset_double_slider(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp new file mode 100644 index 000000000..290541a18 --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp @@ -0,0 +1,281 @@ +#include "GLGizmoBase.hpp" + +#include <GL/glew.h> + +#include "slic3r/GUI/GUI_App.hpp" + + + + + +// TODO: Display tooltips quicker on Linux + + + +namespace Slic3r { +namespace GUI { + +const float GLGizmoBase::Grabber::SizeFactor = 0.025f; +const float GLGizmoBase::Grabber::MinHalfSize = 1.5f; +const float GLGizmoBase::Grabber::DraggingScaleFactor = 1.25f; + +GLGizmoBase::Grabber::Grabber() + : center(Vec3d::Zero()) + , angles(Vec3d::Zero()) + , dragging(false) + , enabled(true) +{ + color[0] = 1.0f; + color[1] = 1.0f; + color[2] = 1.0f; +} + +void GLGizmoBase::Grabber::render(bool hover, float size) const +{ + float render_color[3]; + if (hover) + { + render_color[0] = 1.0f - color[0]; + render_color[1] = 1.0f - color[1]; + render_color[2] = 1.0f - color[2]; + } + else + ::memcpy((void*)render_color, (const void*)color, 3 * sizeof(float)); + + render(size, render_color, true); +} + +float GLGizmoBase::Grabber::get_half_size(float size) const +{ + return std::max(size * SizeFactor, MinHalfSize); +} + +float GLGizmoBase::Grabber::get_dragging_half_size(float size) const +{ + return std::max(size * SizeFactor * DraggingScaleFactor, MinHalfSize); +} + +void GLGizmoBase::Grabber::render(float size, const float* render_color, bool use_lighting) const +{ + float half_size = dragging ? get_dragging_half_size(size) : get_half_size(size); + + if (use_lighting) + ::glEnable(GL_LIGHTING); + + ::glColor3fv(render_color); + + ::glPushMatrix(); + ::glTranslated(center(0), center(1), center(2)); + + ::glRotated(Geometry::rad2deg(angles(2)), 0.0, 0.0, 1.0); + ::glRotated(Geometry::rad2deg(angles(1)), 0.0, 1.0, 0.0); + ::glRotated(Geometry::rad2deg(angles(0)), 1.0, 0.0, 0.0); + + // face min x + ::glPushMatrix(); + ::glTranslatef(-(GLfloat)half_size, 0.0f, 0.0f); + ::glRotatef(-90.0f, 0.0f, 1.0f, 0.0f); + render_face(half_size); + ::glPopMatrix(); + + // face max x + ::glPushMatrix(); + ::glTranslatef((GLfloat)half_size, 0.0f, 0.0f); + ::glRotatef(90.0f, 0.0f, 1.0f, 0.0f); + render_face(half_size); + ::glPopMatrix(); + + // face min y + ::glPushMatrix(); + ::glTranslatef(0.0f, -(GLfloat)half_size, 0.0f); + ::glRotatef(90.0f, 1.0f, 0.0f, 0.0f); + render_face(half_size); + ::glPopMatrix(); + + // face max y + ::glPushMatrix(); + ::glTranslatef(0.0f, (GLfloat)half_size, 0.0f); + ::glRotatef(-90.0f, 1.0f, 0.0f, 0.0f); + render_face(half_size); + ::glPopMatrix(); + + // face min z + ::glPushMatrix(); + ::glTranslatef(0.0f, 0.0f, -(GLfloat)half_size); + ::glRotatef(180.0f, 1.0f, 0.0f, 0.0f); + render_face(half_size); + ::glPopMatrix(); + + // face max z + ::glPushMatrix(); + ::glTranslatef(0.0f, 0.0f, (GLfloat)half_size); + render_face(half_size); + ::glPopMatrix(); + + ::glPopMatrix(); + + if (use_lighting) + ::glDisable(GL_LIGHTING); +} + +void GLGizmoBase::Grabber::render_face(float half_size) const +{ + ::glBegin(GL_TRIANGLES); + ::glNormal3f(0.0f, 0.0f, 1.0f); + ::glVertex3f(-(GLfloat)half_size, -(GLfloat)half_size, 0.0f); + ::glVertex3f((GLfloat)half_size, -(GLfloat)half_size, 0.0f); + ::glVertex3f((GLfloat)half_size, (GLfloat)half_size, 0.0f); + ::glVertex3f((GLfloat)half_size, (GLfloat)half_size, 0.0f); + ::glVertex3f(-(GLfloat)half_size, (GLfloat)half_size, 0.0f); + ::glVertex3f(-(GLfloat)half_size, -(GLfloat)half_size, 0.0f); + ::glEnd(); +} + +#if ENABLE_SVG_ICONS +GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) +#else +GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, unsigned int sprite_id) +#endif // ENABLE_SVG_ICONS + : m_parent(parent) + , m_group_id(-1) + , m_state(Off) + , m_shortcut_key(0) +#if ENABLE_SVG_ICONS + , m_icon_filename(icon_filename) +#endif // ENABLE_SVG_ICONS + , m_sprite_id(sprite_id) + , m_hover_id(-1) + , m_dragging(false) + , m_imgui(wxGetApp().imgui()) +{ + ::memcpy((void*)m_base_color, (const void*)DEFAULT_BASE_COLOR, 3 * sizeof(float)); + ::memcpy((void*)m_drag_color, (const void*)DEFAULT_DRAG_COLOR, 3 * sizeof(float)); + ::memcpy((void*)m_highlight_color, (const void*)DEFAULT_HIGHLIGHT_COLOR, 3 * sizeof(float)); +} + +void GLGizmoBase::set_hover_id(int id) +{ + if (m_grabbers.empty() || (id < (int)m_grabbers.size())) + { + m_hover_id = id; + on_set_hover_id(); + } +} + +void GLGizmoBase::set_highlight_color(const float* color) +{ + if (color != nullptr) + ::memcpy((void*)m_highlight_color, (const void*)color, 3 * sizeof(float)); +} + +void GLGizmoBase::enable_grabber(unsigned int id) +{ + if ((0 <= id) && (id < (unsigned int)m_grabbers.size())) + m_grabbers[id].enabled = true; + + on_enable_grabber(id); +} + +void GLGizmoBase::disable_grabber(unsigned int id) +{ + if ((0 <= id) && (id < (unsigned int)m_grabbers.size())) + m_grabbers[id].enabled = false; + + on_disable_grabber(id); +} + +void GLGizmoBase::start_dragging(const GLCanvas3D::Selection& selection) +{ + m_dragging = true; + + for (int i = 0; i < (int)m_grabbers.size(); ++i) + { + m_grabbers[i].dragging = (m_hover_id == i); + } + + on_start_dragging(selection); +} + +void GLGizmoBase::stop_dragging() +{ + m_dragging = false; + + for (int i = 0; i < (int)m_grabbers.size(); ++i) + { + m_grabbers[i].dragging = false; + } + + on_stop_dragging(); +} + +void GLGizmoBase::update(const UpdateData& data, const GLCanvas3D::Selection& selection) +{ + if (m_hover_id != -1) + on_update(data, selection); +} + +std::array<float, 3> GLGizmoBase::picking_color_component(unsigned int id) const +{ + static const float INV_255 = 1.0f / 255.0f; + + id = BASE_ID - id; + + if (m_group_id > -1) + id -= m_group_id; + + // color components are encoded to match the calculation of volume_id made into GLCanvas3D::_picking_pass() + return std::array<float, 3> { (float)((id >> 0) & 0xff) * INV_255, // red + (float)((id >> 8) & 0xff) * INV_255, // green + (float)((id >> 16) & 0xff) * INV_255 }; // blue +} + +void GLGizmoBase::render_grabbers(const BoundingBoxf3& box) const +{ + float size = (float)box.max_size(); + + for (int i = 0; i < (int)m_grabbers.size(); ++i) + { + if (m_grabbers[i].enabled) + m_grabbers[i].render((m_hover_id == i), size); + } +} + +void GLGizmoBase::render_grabbers(float size) const +{ + for (int i = 0; i < (int)m_grabbers.size(); ++i) + { + if (m_grabbers[i].enabled) + m_grabbers[i].render((m_hover_id == i), size); + } +} + +void GLGizmoBase::render_grabbers_for_picking(const BoundingBoxf3& box) const +{ + float size = (float)box.max_size(); + + for (unsigned int i = 0; i < (unsigned int)m_grabbers.size(); ++i) + { + if (m_grabbers[i].enabled) + { + std::array<float, 3> color = picking_color_component(i); + m_grabbers[i].color[0] = color[0]; + m_grabbers[i].color[1] = color[1]; + m_grabbers[i].color[2] = color[2]; + m_grabbers[i].render_for_picking(size); + } + } +} + + +void GLGizmoBase::set_tooltip(const std::string& tooltip) const +{ + m_parent.set_tooltip(tooltip); +} + +std::string GLGizmoBase::format(float value, unsigned int decimals) const +{ + return Slic3r::string_printf("%.*f", decimals, value); +} + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp new file mode 100644 index 000000000..e8328bcd4 --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp @@ -0,0 +1,182 @@ +#ifndef slic3r_GLGizmoBase_hpp_ +#define slic3r_GLGizmoBase_hpp_ + +#include "libslic3r/Point.hpp" + +#include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/I18N.hpp" + + +class wxWindow; +class GLUquadric; +typedef class GLUquadric GLUquadricObj; + + +namespace Slic3r { + +class BoundingBoxf3; +class Linef3; +class ModelObject; + +namespace GUI { + +static const float DEFAULT_BASE_COLOR[3] = { 0.625f, 0.625f, 0.625f }; +static const float DEFAULT_DRAG_COLOR[3] = { 1.0f, 1.0f, 1.0f }; +static const float DEFAULT_HIGHLIGHT_COLOR[3] = { 1.0f, 0.38f, 0.0f }; +static const float AXES_COLOR[3][3] = { { 1.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f } }; + + + +class ImGuiWrapper; + + +class GLGizmoBase +{ +public: + // Starting value for ids to avoid clashing with ids used by GLVolumes + // (254 is choosen to leave some space for forward compatibility) + static const unsigned int BASE_ID = 255 * 255 * 254; + +protected: + struct Grabber + { + static const float SizeFactor; + static const float MinHalfSize; + static const float DraggingScaleFactor; + + Vec3d center; + Vec3d angles; + float color[3]; + bool enabled; + bool dragging; + + Grabber(); + + void render(bool hover, float size) const; + void render_for_picking(float size) const { render(size, color, false); } + + float get_half_size(float size) const; + float get_dragging_half_size(float size) const; + + private: + void render(float size, const float* render_color, bool use_lighting) const; + void render_face(float half_size) const; + }; + +public: + enum EState + { + Off, + Hover, + On, + Num_States + }; + + struct UpdateData + { + const Linef3 mouse_ray; + const Point* mouse_pos; + bool shift_down; + + UpdateData(const Linef3& mouse_ray, const Point* mouse_pos = nullptr, bool shift_down = false) + : mouse_ray(mouse_ray), mouse_pos(mouse_pos), shift_down(shift_down) + {} + }; + +protected: + GLCanvas3D& m_parent; + + int m_group_id; + EState m_state; + int m_shortcut_key; +#if ENABLE_SVG_ICONS + std::string m_icon_filename; +#endif // ENABLE_SVG_ICONS + unsigned int m_sprite_id; + int m_hover_id; + bool m_dragging; + float m_base_color[3]; + float m_drag_color[3]; + float m_highlight_color[3]; + mutable std::vector<Grabber> m_grabbers; + ImGuiWrapper* m_imgui; + +public: +#if ENABLE_SVG_ICONS + GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); +#else + GLGizmoBase(GLCanvas3D& parent, unsigned int sprite_id); +#endif // ENABLE_SVG_ICONS + virtual ~GLGizmoBase() {} + + bool init() { return on_init(); } + + std::string get_name() const { return on_get_name(); } + + int get_group_id() const { return m_group_id; } + void set_group_id(int id) { m_group_id = id; } + + EState get_state() const { return m_state; } + void set_state(EState state) { m_state = state; on_set_state(); } + + int get_shortcut_key() const { return m_shortcut_key; } + void set_shortcut_key(int key) { m_shortcut_key = key; } + +#if ENABLE_SVG_ICONS + const std::string& get_icon_filename() const { return m_icon_filename; } +#endif // ENABLE_SVG_ICONS + + bool is_activable(const GLCanvas3D::Selection& selection) const { return on_is_activable(selection); } + bool is_selectable() const { return on_is_selectable(); } + + unsigned int get_sprite_id() const { return m_sprite_id; } + + int get_hover_id() const { return m_hover_id; } + void set_hover_id(int id); + + void set_highlight_color(const float* color); + + void enable_grabber(unsigned int id); + void disable_grabber(unsigned int id); + + void start_dragging(const GLCanvas3D::Selection& selection); + void stop_dragging(); + bool is_dragging() const { return m_dragging; } + + void update(const UpdateData& data, const GLCanvas3D::Selection& selection); + + void render(const GLCanvas3D::Selection& selection) const { on_render(selection); } + void render_for_picking(const GLCanvas3D::Selection& selection) const { on_render_for_picking(selection); } + void render_input_window(float x, float y, float bottom_limit, const GLCanvas3D::Selection& selection) { on_render_input_window(x, y, bottom_limit, selection); } + +protected: + virtual bool on_init() = 0; + virtual std::string on_get_name() const = 0; + virtual void on_set_state() {} + virtual void on_set_hover_id() {} + virtual bool on_is_activable(const GLCanvas3D::Selection& selection) const { return true; } + virtual bool on_is_selectable() const { return true; } + virtual void on_enable_grabber(unsigned int id) {} + virtual void on_disable_grabber(unsigned int id) {} + virtual void on_start_dragging(const GLCanvas3D::Selection& selection) {} + virtual void on_stop_dragging() {} + virtual void on_update(const UpdateData& data, const GLCanvas3D::Selection& selection) = 0; + virtual void on_render(const GLCanvas3D::Selection& selection) const = 0; + virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const = 0; + virtual void on_render_input_window(float x, float y, float bottom_limit, const GLCanvas3D::Selection& selection) {} + + // Returns the picking color for the given id, based on the BASE_ID constant + // No check is made for clashing with other picking color (i.e. GLVolumes) + std::array<float, 3> picking_color_component(unsigned int id) const; + void render_grabbers(const BoundingBoxf3& box) const; + void render_grabbers(float size) const; + void render_grabbers_for_picking(const BoundingBoxf3& box) const; + + void set_tooltip(const std::string& tooltip) const; + std::string format(float value, unsigned int decimals) const; +}; + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GLGizmoBase_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp new file mode 100644 index 000000000..54a39c6d5 --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -0,0 +1,257 @@ +// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. +#include "GLGizmoCut.hpp" + +#include <GL/glew.h> + +#include <wx/button.h> +#include <wx/checkbox.h> +#include <wx/stattext.h> +#include <wx/sizer.h> + +#include "slic3r/GUI/GUI_App.hpp" + + +namespace Slic3r { +namespace GUI { + + + + + + +// GLGizmoCut + +class GLGizmoCutPanel : public wxPanel +{ +public: + GLGizmoCutPanel(wxWindow *parent); + + void display(bool display); +private: + bool m_active; + wxCheckBox *m_cb_rotate; + wxButton *m_btn_cut; + wxButton *m_btn_cancel; +}; + +GLGizmoCutPanel::GLGizmoCutPanel(wxWindow *parent) + : wxPanel(parent) + , m_active(false) + , m_cb_rotate(new wxCheckBox(this, wxID_ANY, _(L("Rotate lower part upwards")))) + , m_btn_cut(new wxButton(this, wxID_OK, _(L("Perform cut")))) + , m_btn_cancel(new wxButton(this, wxID_CANCEL, _(L("Cancel")))) +{ + enum { MARGIN = 5 }; + + auto *sizer = new wxBoxSizer(wxHORIZONTAL); + + auto *label = new wxStaticText(this, wxID_ANY, _(L("Cut object:"))); + sizer->Add(label, 0, wxALL | wxALIGN_CENTER, MARGIN); + sizer->Add(m_cb_rotate, 0, wxALL | wxALIGN_CENTER, MARGIN); + sizer->AddStretchSpacer(); + sizer->Add(m_btn_cut, 0, wxALL | wxALIGN_CENTER, MARGIN); + sizer->Add(m_btn_cancel, 0, wxALL | wxALIGN_CENTER, MARGIN); + + SetSizer(sizer); +} + +void GLGizmoCutPanel::display(bool display) +{ + Show(display); + GetParent()->Layout(); +} + + +const double GLGizmoCut::Offset = 10.0; +const double GLGizmoCut::Margin = 20.0; +const std::array<float, 3> GLGizmoCut::GrabberColor = { 1.0, 0.5, 0.0 }; + +#if ENABLE_SVG_ICONS +GLGizmoCut::GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) + : GLGizmoBase(parent, icon_filename, sprite_id) +#else +GLGizmoCut::GLGizmoCut(GLCanvas3D& parent, unsigned int sprite_id) + : GLGizmoBase(parent, sprite_id) +#endif // ENABLE_SVG_ICONS + , m_cut_z(0.0) + , m_max_z(0.0) + , m_keep_upper(true) + , m_keep_lower(true) + , m_rotate_lower(false) +{} + + +bool GLGizmoCut::on_init() +{ + m_grabbers.emplace_back(); + m_shortcut_key = WXK_CONTROL_C; + return true; +} + +std::string GLGizmoCut::on_get_name() const +{ + return L("Cut [C]"); +} + +void GLGizmoCut::on_set_state() +{ + // Reset m_cut_z on gizmo activation + if (get_state() == On) { + m_cut_z = m_parent.get_selection().get_bounding_box().size()(2) / 2.0; + } +} + +bool GLGizmoCut::on_is_activable(const GLCanvas3D::Selection& selection) const +{ + return selection.is_single_full_instance() && !selection.is_wipe_tower(); +} + +void GLGizmoCut::on_start_dragging(const GLCanvas3D::Selection& selection) +{ + if (m_hover_id == -1) { return; } + + const BoundingBoxf3& box = selection.get_bounding_box(); + m_start_z = m_cut_z; + update_max_z(selection); + m_drag_pos = m_grabbers[m_hover_id].center; + m_drag_center = box.center(); + m_drag_center(2) = m_cut_z; +} + +void GLGizmoCut::on_update(const UpdateData& data, const GLCanvas3D::Selection& selection) +{ + if (m_hover_id != -1) { + set_cut_z(m_start_z + calc_projection(data.mouse_ray)); + } +} + +void GLGizmoCut::on_render(const GLCanvas3D::Selection& selection) const +{ + if (m_grabbers[0].dragging) { + set_tooltip("Z: " + format(m_cut_z, 2)); + } + + update_max_z(selection); + + const BoundingBoxf3& box = selection.get_bounding_box(); + Vec3d plane_center = box.center(); + plane_center(2) = m_cut_z; + + const float min_x = box.min(0) - Margin; + const float max_x = box.max(0) + Margin; + const float min_y = box.min(1) - Margin; + const float max_y = box.max(1) + Margin; + ::glEnable(GL_DEPTH_TEST); + ::glDisable(GL_CULL_FACE); + ::glEnable(GL_BLEND); + ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + // Draw the cutting plane + ::glBegin(GL_QUADS); + ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f); + ::glVertex3f(min_x, min_y, plane_center(2)); + ::glVertex3f(max_x, min_y, plane_center(2)); + ::glVertex3f(max_x, max_y, plane_center(2)); + ::glVertex3f(min_x, max_y, plane_center(2)); + ::glEnd(); + + ::glEnable(GL_CULL_FACE); + ::glDisable(GL_BLEND); + + // TODO: draw cut part contour? + + // Draw the grabber and the connecting line + m_grabbers[0].center = plane_center; + m_grabbers[0].center(2) = plane_center(2) + Offset; + + ::glDisable(GL_DEPTH_TEST); + ::glLineWidth(m_hover_id != -1 ? 2.0f : 1.5f); + ::glColor3f(1.0, 1.0, 0.0); + ::glBegin(GL_LINES); + ::glVertex3dv(plane_center.data()); + ::glVertex3dv(m_grabbers[0].center.data()); + ::glEnd(); + + std::copy(std::begin(GrabberColor), std::end(GrabberColor), m_grabbers[0].color); + m_grabbers[0].render(m_hover_id == 0, box.max_size()); +} + +void GLGizmoCut::on_render_for_picking(const GLCanvas3D::Selection& selection) const +{ + ::glDisable(GL_DEPTH_TEST); + + render_grabbers_for_picking(selection.get_bounding_box()); +} + +void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit, const GLCanvas3D::Selection& selection) +{ + m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); + m_imgui->set_next_window_bg_alpha(0.5f); + m_imgui->begin(_(L("Cut")), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + + ImGui::PushItemWidth(100.0f); + bool _value_changed = ImGui::InputDouble("Z", &m_cut_z, 0.0f, 0.0f, "%.2f"); + + m_imgui->checkbox(_(L("Keep upper part")), m_keep_upper); + m_imgui->checkbox(_(L("Keep lower part")), m_keep_lower); + m_imgui->checkbox(_(L("Rotate lower part upwards")), m_rotate_lower); + + m_imgui->disabled_begin(!m_keep_upper && !m_keep_lower); + const bool cut_clicked = m_imgui->button(_(L("Perform cut"))); + m_imgui->disabled_end(); + + m_imgui->end(); + + if (cut_clicked && (m_keep_upper || m_keep_lower)) { + perform_cut(selection); + } +} + +void GLGizmoCut::update_max_z(const GLCanvas3D::Selection& selection) const +{ + m_max_z = selection.get_bounding_box().size()(2); + set_cut_z(m_cut_z); +} + +void GLGizmoCut::set_cut_z(double cut_z) const +{ + // Clamp the plane to the object's bounding box + m_cut_z = std::max(0.0, std::min(m_max_z, cut_z)); +} + +void GLGizmoCut::perform_cut(const GLCanvas3D::Selection& selection) +{ + const auto instance_idx = selection.get_instance_idx(); + const auto object_idx = selection.get_object_idx(); + + wxCHECK_RET(instance_idx >= 0 && object_idx >= 0, "GLGizmoCut: Invalid object selection"); + + wxGetApp().plater()->cut(object_idx, instance_idx, m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); +} + +double GLGizmoCut::calc_projection(const Linef3& mouse_ray) const +{ + double projection = 0.0; + + const Vec3d starting_vec = m_drag_pos - m_drag_center; + const double len_starting_vec = starting_vec.norm(); + if (len_starting_vec != 0.0) + { + Vec3d mouse_dir = mouse_ray.unit_vector(); + // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position + // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form + // in our case plane normal and ray direction are the same (orthogonal view) + // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal + Vec3d inters = mouse_ray.a + (m_drag_pos - mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; + // vector from the starting position to the found intersection + Vec3d inters_vec = inters - m_drag_pos; + + // finds projection of the vector along the staring direction + projection = inters_vec.dot(starting_vec.normalized()); + } + return projection; +} + + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp new file mode 100644 index 000000000..beec6d1da --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -0,0 +1,53 @@ +#ifndef slic3r_GLGizmoCut_hpp_ +#define slic3r_GLGizmoCut_hpp_ + +#include "GLGizmoBase.hpp" + + +namespace Slic3r { +namespace GUI { + +class GLGizmoCut : public GLGizmoBase +{ + static const double Offset; + static const double Margin; + static const std::array<float, 3> GrabberColor; + + mutable double m_cut_z; + double m_start_z; + mutable double m_max_z; + Vec3d m_drag_pos; + Vec3d m_drag_center; + bool m_keep_upper; + bool m_keep_lower; + bool m_rotate_lower; + +public: +#if ENABLE_SVG_ICONS + GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); +#else + GLGizmoCut(GLCanvas3D& parent, unsigned int sprite_id); +#endif // ENABLE_SVG_ICONS + +protected: + virtual bool on_init(); + virtual std::string on_get_name() const; + virtual void on_set_state(); + virtual bool on_is_activable(const GLCanvas3D::Selection& selection) const; + virtual void on_start_dragging(const GLCanvas3D::Selection& selection); + virtual void on_update(const UpdateData& data, const GLCanvas3D::Selection& selection); + virtual void on_render(const GLCanvas3D::Selection& selection) const; + virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const; + virtual void on_render_input_window(float x, float y, float bottom_limit, const GLCanvas3D::Selection& selection); + +private: + void update_max_z(const GLCanvas3D::Selection& selection) const; + void set_cut_z(double cut_z) const; + void perform_cut(const GLCanvas3D::Selection& selection); + double calc_projection(const Linef3& mouse_ray) const; +}; + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GLGizmoCut_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp new file mode 100644 index 000000000..d72dc4913 --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp @@ -0,0 +1,357 @@ +// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. +#include "GLGizmoFlatten.hpp" + +#include <numeric> + +#include <GL/glew.h> + +namespace Slic3r { +namespace GUI { + + +#if ENABLE_SVG_ICONS +GLGizmoFlatten::GLGizmoFlatten(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) + : GLGizmoBase(parent, icon_filename, sprite_id) +#else +GLGizmoFlatten::GLGizmoFlatten(GLCanvas3D& parent, unsigned int sprite_id) + : GLGizmoBase(parent, sprite_id) +#endif // ENABLE_SVG_ICONS + , m_normal(Vec3d::Zero()) + , m_starting_center(Vec3d::Zero()) +{ +} + +bool GLGizmoFlatten::on_init() +{ + m_shortcut_key = WXK_CONTROL_F; + return true; +} + +std::string GLGizmoFlatten::on_get_name() const +{ + return L("Place on face [F]"); +} + +bool GLGizmoFlatten::on_is_activable(const GLCanvas3D::Selection& selection) const +{ + return selection.is_single_full_instance(); +} + +void GLGizmoFlatten::on_start_dragging(const GLCanvas3D::Selection& selection) +{ + if (m_hover_id != -1) + { + assert(m_planes_valid); + m_normal = m_planes[m_hover_id].normal; + m_starting_center = selection.get_bounding_box().center(); + } +} + +void GLGizmoFlatten::on_render(const GLCanvas3D::Selection& selection) const +{ + ::glClear(GL_DEPTH_BUFFER_BIT); + + ::glEnable(GL_DEPTH_TEST); + ::glEnable(GL_BLEND); + + if (selection.is_single_full_instance()) + { + const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(); + ::glPushMatrix(); + ::glTranslatef(0.f, 0.f, selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z()); + ::glMultMatrixd(m.data()); + if (this->is_plane_update_necessary()) + const_cast<GLGizmoFlatten*>(this)->update_planes(); + for (int i = 0; i < (int)m_planes.size(); ++i) + { + if (i == m_hover_id) + ::glColor4f(0.9f, 0.9f, 0.9f, 0.75f); + else + ::glColor4f(0.9f, 0.9f, 0.9f, 0.5f); + + ::glBegin(GL_POLYGON); + for (const Vec3d& vertex : m_planes[i].vertices) + { + ::glVertex3dv(vertex.data()); + } + ::glEnd(); + } + ::glPopMatrix(); + } + + ::glEnable(GL_CULL_FACE); + ::glDisable(GL_BLEND); +} + +void GLGizmoFlatten::on_render_for_picking(const GLCanvas3D::Selection& selection) const +{ + ::glDisable(GL_DEPTH_TEST); + ::glDisable(GL_BLEND); + + if (selection.is_single_full_instance()) + { + const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(); + ::glPushMatrix(); + ::glTranslatef(0.f, 0.f, selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z()); + ::glMultMatrixd(m.data()); + if (this->is_plane_update_necessary()) + const_cast<GLGizmoFlatten*>(this)->update_planes(); + for (int i = 0; i < (int)m_planes.size(); ++i) + { + ::glColor3fv(picking_color_component(i).data()); + ::glBegin(GL_POLYGON); + for (const Vec3d& vertex : m_planes[i].vertices) + { + ::glVertex3dv(vertex.data()); + } + ::glEnd(); + } + ::glPopMatrix(); + } + + ::glEnable(GL_CULL_FACE); +} + +void GLGizmoFlatten::set_flattening_data(const ModelObject* model_object) +{ + m_starting_center = Vec3d::Zero(); + if (m_model_object != model_object) { + m_planes.clear(); + m_planes_valid = false; + } + m_model_object = model_object; +} + +void GLGizmoFlatten::update_planes() +{ + TriangleMesh ch; + for (const ModelVolume* vol : m_model_object->volumes) + { + if (vol->type() != ModelVolumeType::MODEL_PART) + continue; + TriangleMesh vol_ch = vol->get_convex_hull(); + vol_ch.transform(vol->get_matrix()); + ch.merge(vol_ch); + } + ch = ch.convex_hull_3d(); + m_planes.clear(); + const Transform3d& inst_matrix = m_model_object->instances.front()->get_matrix(true); + + // Following constants are used for discarding too small polygons. + const float minimal_area = 5.f; // in square mm (world coordinates) + const float minimal_side = 1.f; // mm + + // Now we'll go through all the facets and append Points of facets sharing the same normal. + // This part is still performed in mesh coordinate system. + const int num_of_facets = ch.stl.stats.number_of_facets; + std::vector<int> facet_queue(num_of_facets, 0); + std::vector<bool> facet_visited(num_of_facets, false); + int facet_queue_cnt = 0; + const stl_normal* normal_ptr = nullptr; + while (1) { + // Find next unvisited triangle: + int facet_idx = 0; + for (; facet_idx < num_of_facets; ++ facet_idx) + if (!facet_visited[facet_idx]) { + facet_queue[facet_queue_cnt ++] = facet_idx; + facet_visited[facet_idx] = true; + normal_ptr = &ch.stl.facet_start[facet_idx].normal; + m_planes.emplace_back(); + break; + } + if (facet_idx == num_of_facets) + break; // Everything was visited already + + while (facet_queue_cnt > 0) { + int facet_idx = facet_queue[-- facet_queue_cnt]; + const stl_normal& this_normal = ch.stl.facet_start[facet_idx].normal; + if (std::abs(this_normal(0) - (*normal_ptr)(0)) < 0.001 && std::abs(this_normal(1) - (*normal_ptr)(1)) < 0.001 && std::abs(this_normal(2) - (*normal_ptr)(2)) < 0.001) { + stl_vertex* first_vertex = ch.stl.facet_start[facet_idx].vertex; + for (int j=0; j<3; ++j) + m_planes.back().vertices.emplace_back((double)first_vertex[j](0), (double)first_vertex[j](1), (double)first_vertex[j](2)); + + facet_visited[facet_idx] = true; + for (int j = 0; j < 3; ++ j) { + int neighbor_idx = ch.stl.neighbors_start[facet_idx].neighbor[j]; + if (! facet_visited[neighbor_idx]) + facet_queue[facet_queue_cnt ++] = neighbor_idx; + } + } + } + m_planes.back().normal = normal_ptr->cast<double>(); + + // Now we'll transform all the points into world coordinates, so that the areas, angles and distances + // make real sense. + m_planes.back().vertices = transform(m_planes.back().vertices, inst_matrix); + + // if this is a just a very small triangle, remove it to speed up further calculations (it would be rejected later anyway): + if (m_planes.back().vertices.size() == 3 && + ((m_planes.back().vertices[0] - m_planes.back().vertices[1]).norm() < minimal_side + || (m_planes.back().vertices[0] - m_planes.back().vertices[2]).norm() < minimal_side + || (m_planes.back().vertices[1] - m_planes.back().vertices[2]).norm() < minimal_side)) + m_planes.pop_back(); + } + + // Let's prepare transformation of the normal vector from mesh to instance coordinates. + Geometry::Transformation t(inst_matrix); + Vec3d scaling = t.get_scaling_factor(); + t.set_scaling_factor(Vec3d(1./scaling(0), 1./scaling(1), 1./scaling(2))); + + // Now we'll go through all the polygons, transform the points into xy plane to process them: + for (unsigned int polygon_id=0; polygon_id < m_planes.size(); ++polygon_id) { + Pointf3s& polygon = m_planes[polygon_id].vertices; + const Vec3d& normal = m_planes[polygon_id].normal; + + // transform the normal according to the instance matrix: + Vec3d normal_transformed = t.get_matrix() * normal; + + // We are going to rotate about z and y to flatten the plane + Eigen::Quaterniond q; + Transform3d m = Transform3d::Identity(); + m.matrix().block(0, 0, 3, 3) = q.setFromTwoVectors(normal_transformed, Vec3d::UnitZ()).toRotationMatrix(); + polygon = transform(polygon, m); + + // Now to remove the inner points. We'll misuse Geometry::convex_hull for that, but since + // it works in fixed point representation, we will rescale the polygon to avoid overflows. + // And yes, it is a nasty thing to do. Whoever has time is free to refactor. + Vec3d bb_size = BoundingBoxf3(polygon).size(); + float sf = std::min(1./bb_size(0), 1./bb_size(1)); + Transform3d tr = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d(sf, sf, 1.f)); + polygon = transform(polygon, tr); + polygon = Slic3r::Geometry::convex_hull(polygon); + polygon = transform(polygon, tr.inverse()); + + // Calculate area of the polygons and discard ones that are too small + float& area = m_planes[polygon_id].area; + area = 0.f; + for (unsigned int i = 0; i < polygon.size(); i++) // Shoelace formula + area += polygon[i](0)*polygon[i + 1 < polygon.size() ? i + 1 : 0](1) - polygon[i + 1 < polygon.size() ? i + 1 : 0](0)*polygon[i](1); + area = 0.5f * std::abs(area); + + bool discard = false; + if (area < minimal_area) + discard = true; + else { + // We also check the inner angles and discard polygons with angles smaller than the following threshold + const double angle_threshold = ::cos(10.0 * (double)PI / 180.0); + + for (unsigned int i = 0; i < polygon.size(); ++i) { + const Vec3d& prec = polygon[(i == 0) ? polygon.size() - 1 : i - 1]; + const Vec3d& curr = polygon[i]; + const Vec3d& next = polygon[(i == polygon.size() - 1) ? 0 : i + 1]; + + if ((prec - curr).normalized().dot((next - curr).normalized()) > angle_threshold) { + discard = true; + break; + } + } + } + + if (discard) { + m_planes.erase(m_planes.begin() + (polygon_id--)); + continue; + } + + // We will shrink the polygon a little bit so it does not touch the object edges: + Vec3d centroid = std::accumulate(polygon.begin(), polygon.end(), Vec3d(0.0, 0.0, 0.0)); + centroid /= (double)polygon.size(); + for (auto& vertex : polygon) + vertex = 0.9f*vertex + 0.1f*centroid; + + // Polygon is now simple and convex, we'll round the corners to make them look nicer. + // The algorithm takes a vertex, calculates middles of respective sides and moves the vertex + // towards their average (controlled by 'aggressivity'). This is repeated k times. + // In next iterations, the neighbours are not always taken at the middle (to increase the + // rounding effect at the corners, where we need it most). + const unsigned int k = 10; // number of iterations + const float aggressivity = 0.2f; // agressivity + const unsigned int N = polygon.size(); + std::vector<std::pair<unsigned int, unsigned int>> neighbours; + if (k != 0) { + Pointf3s points_out(2*k*N); // vector long enough to store the future vertices + for (unsigned int j=0; j<N; ++j) { + points_out[j*2*k] = polygon[j]; + neighbours.push_back(std::make_pair((int)(j*2*k-k) < 0 ? (N-1)*2*k+k : j*2*k-k, j*2*k+k)); + } + + for (unsigned int i=0; i<k; ++i) { + // Calculate middle of each edge so that neighbours points to something useful: + for (unsigned int j=0; j<N; ++j) + if (i==0) + points_out[j*2*k+k] = 0.5f * (points_out[j*2*k] + points_out[j==N-1 ? 0 : (j+1)*2*k]); + else { + float r = 0.2+0.3/(k-1)*i; // the neighbours are not always taken in the middle + points_out[neighbours[j].first] = r*points_out[j*2*k] + (1-r) * points_out[neighbours[j].first-1]; + points_out[neighbours[j].second] = r*points_out[j*2*k] + (1-r) * points_out[neighbours[j].second+1]; + } + // Now we have a triangle and valid neighbours, we can do an iteration: + for (unsigned int j=0; j<N; ++j) + points_out[2*k*j] = (1-aggressivity) * points_out[2*k*j] + + aggressivity*0.5f*(points_out[neighbours[j].first] + points_out[neighbours[j].second]); + + for (auto& n : neighbours) { + ++n.first; + --n.second; + } + } + polygon = points_out; // replace the coarse polygon with the smooth one that we just created + } + + + // Raise a bit above the object surface to avoid flickering: + for (auto& b : polygon) + b(2) += 0.1f; + + // Transform back to 3D (and also back to mesh coordinates) + polygon = transform(polygon, inst_matrix.inverse() * m.inverse()); + } + + // We'll sort the planes by area and only keep the 254 largest ones (because of the picking pass limitations): + std::sort(m_planes.rbegin(), m_planes.rend(), [](const PlaneData& a, const PlaneData& b) { return a.area < b.area; }); + m_planes.resize(std::min((int)m_planes.size(), 254)); + + // Planes are finished - let's save what we calculated it from: + m_volumes_matrices.clear(); + m_volumes_types.clear(); + for (const ModelVolume* vol : m_model_object->volumes) { + m_volumes_matrices.push_back(vol->get_matrix()); + m_volumes_types.push_back(vol->type()); + } + m_first_instance_scale = m_model_object->instances.front()->get_scaling_factor(); + m_first_instance_mirror = m_model_object->instances.front()->get_mirror(); + + m_planes_valid = true; +} + + +bool GLGizmoFlatten::is_plane_update_necessary() const +{ + if (m_state != On || !m_model_object || m_model_object->instances.empty()) + return false; + + if (! m_planes_valid || m_model_object->volumes.size() != m_volumes_matrices.size()) + return true; + + // We want to recalculate when the scale changes - some planes could (dis)appear. + if (! m_model_object->instances.front()->get_scaling_factor().isApprox(m_first_instance_scale) + || ! m_model_object->instances.front()->get_mirror().isApprox(m_first_instance_mirror)) + return true; + + for (unsigned int i=0; i < m_model_object->volumes.size(); ++i) + if (! m_model_object->volumes[i]->get_matrix().isApprox(m_volumes_matrices[i]) + || m_model_object->volumes[i]->type() != m_volumes_types[i]) + return true; + + return false; +} + +Vec3d GLGizmoFlatten::get_flattening_normal() const +{ + Vec3d out = m_normal; + m_normal = Vec3d::Zero(); + m_starting_center = Vec3d::Zero(); + return out; +} + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp new file mode 100644 index 000000000..c91924c5a --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp @@ -0,0 +1,67 @@ +#ifndef slic3r_GLGizmoFlatten_hpp_ +#define slic3r_GLGizmoFlatten_hpp_ + +#include "GLGizmoBase.hpp" + + +namespace Slic3r { +namespace GUI { + + +class GLGizmoFlatten : public GLGizmoBase +{ +// This gizmo does not use grabbers. The m_hover_id relates to polygon managed by the class itself. + +private: + mutable Vec3d m_normal; + + struct PlaneData { + std::vector<Vec3d> vertices; + Vec3d normal; + float area; + }; + + // This holds information to decide whether recalculation is necessary: + std::vector<Transform3d> m_volumes_matrices; + std::vector<ModelVolumeType> m_volumes_types; + Vec3d m_first_instance_scale; + Vec3d m_first_instance_mirror; + + std::vector<PlaneData> m_planes; + bool m_planes_valid = false; + mutable Vec3d m_starting_center; + const ModelObject* m_model_object = nullptr; + std::vector<const Transform3d*> instances_matrices; + + void update_planes(); + bool is_plane_update_necessary() const; + +public: +#if ENABLE_SVG_ICONS + GLGizmoFlatten(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); +#else + GLGizmoFlatten(GLCanvas3D& parent, unsigned int sprite_id); +#endif // ENABLE_SVG_ICONS + + void set_flattening_data(const ModelObject* model_object); + Vec3d get_flattening_normal() const; + +protected: + virtual bool on_init(); + virtual std::string on_get_name() const; + virtual bool on_is_activable(const GLCanvas3D::Selection& selection) const; + virtual void on_start_dragging(const GLCanvas3D::Selection& selection); + virtual void on_update(const UpdateData& data, const GLCanvas3D::Selection& selection) {} + virtual void on_render(const GLCanvas3D::Selection& selection) const; + virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const; + virtual void on_set_state() + { + if (m_state == On && is_plane_update_necessary()) + update_planes(); + } +}; + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GLGizmoFlatten_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp new file mode 100644 index 000000000..d18d71c83 --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -0,0 +1,255 @@ +// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. +#include "GLGizmoMove.hpp" + +#include <GL/glew.h> + + +namespace Slic3r { +namespace GUI { + +const double GLGizmoMove3D::Offset = 10.0; + +#if ENABLE_SVG_ICONS +GLGizmoMove3D::GLGizmoMove3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) + : GLGizmoBase(parent, icon_filename, sprite_id) +#else +GLGizmoMove3D::GLGizmoMove3D(GLCanvas3D& parent, unsigned int sprite_id) + : GLGizmoBase(parent, sprite_id) +#endif // ENABLE_SVG_ICONS + , m_displacement(Vec3d::Zero()) + , m_snap_step(1.0) + , m_starting_drag_position(Vec3d::Zero()) + , m_starting_box_center(Vec3d::Zero()) + , m_starting_box_bottom_center(Vec3d::Zero()) + , m_quadric(nullptr) +{ + m_quadric = ::gluNewQuadric(); + if (m_quadric != nullptr) + ::gluQuadricDrawStyle(m_quadric, GLU_FILL); +} + +GLGizmoMove3D::~GLGizmoMove3D() +{ + if (m_quadric != nullptr) + ::gluDeleteQuadric(m_quadric); +} + +bool GLGizmoMove3D::on_init() +{ + for (int i = 0; i < 3; ++i) + { + m_grabbers.push_back(Grabber()); + } + + m_shortcut_key = WXK_CONTROL_M; + + return true; +} + +std::string GLGizmoMove3D::on_get_name() const +{ + return L("Move [M]"); +} + +void GLGizmoMove3D::on_start_dragging(const GLCanvas3D::Selection& selection) +{ + if (m_hover_id != -1) + { + m_displacement = Vec3d::Zero(); + const BoundingBoxf3& box = selection.get_bounding_box(); + m_starting_drag_position = m_grabbers[m_hover_id].center; + m_starting_box_center = box.center(); + m_starting_box_bottom_center = box.center(); + m_starting_box_bottom_center(2) = box.min(2); + } +} + +void GLGizmoMove3D::on_stop_dragging() +{ + m_displacement = Vec3d::Zero(); +} + +void GLGizmoMove3D::on_update(const UpdateData& data, const GLCanvas3D::Selection& selection) +{ + if (m_hover_id == 0) + m_displacement(0) = calc_projection(data); + else if (m_hover_id == 1) + m_displacement(1) = calc_projection(data); + else if (m_hover_id == 2) + m_displacement(2) = calc_projection(data); +} + +void GLGizmoMove3D::on_render(const GLCanvas3D::Selection& selection) const +{ + bool show_position = selection.is_single_full_instance(); + const Vec3d& position = selection.get_bounding_box().center(); + + if ((show_position && (m_hover_id == 0)) || m_grabbers[0].dragging) + set_tooltip("X: " + format(show_position ? position(0) : m_displacement(0), 2)); + else if (!m_grabbers[0].dragging && (m_hover_id == 0)) + set_tooltip("X"); + else if ((show_position && (m_hover_id == 1)) || m_grabbers[1].dragging) + set_tooltip("Y: " + format(show_position ? position(1) : m_displacement(1), 2)); + else if (!m_grabbers[1].dragging && (m_hover_id == 1)) + set_tooltip("Y"); + else if ((show_position && (m_hover_id == 2)) || m_grabbers[2].dragging) + set_tooltip("Z: " + format(show_position ? position(2) : m_displacement(2), 2)); + else if (!m_grabbers[2].dragging && (m_hover_id == 2)) + set_tooltip("Z"); + + ::glClear(GL_DEPTH_BUFFER_BIT); + ::glEnable(GL_DEPTH_TEST); + + const BoundingBoxf3& box = selection.get_bounding_box(); + const Vec3d& center = box.center(); + + // x axis + m_grabbers[0].center = Vec3d(box.max(0) + Offset, center(1), center(2)); + ::memcpy((void*)m_grabbers[0].color, (const void*)&AXES_COLOR[0], 3 * sizeof(float)); + + // y axis + m_grabbers[1].center = Vec3d(center(0), box.max(1) + Offset, center(2)); + ::memcpy((void*)m_grabbers[1].color, (const void*)&AXES_COLOR[1], 3 * sizeof(float)); + + // z axis + m_grabbers[2].center = Vec3d(center(0), center(1), box.max(2) + Offset); + ::memcpy((void*)m_grabbers[2].color, (const void*)&AXES_COLOR[2], 3 * sizeof(float)); + + ::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f); + + if (m_hover_id == -1) + { + // draw axes + for (unsigned int i = 0; i < 3; ++i) + { + if (m_grabbers[i].enabled) + { + ::glColor3fv(AXES_COLOR[i]); + ::glBegin(GL_LINES); + ::glVertex3dv(center.data()); + ::glVertex3dv(m_grabbers[i].center.data()); + ::glEnd(); + } + } + + // draw grabbers + render_grabbers(box); + for (unsigned int i = 0; i < 3; ++i) + { + if (m_grabbers[i].enabled) + render_grabber_extension((Axis)i, box, false); + } + } + else + { + // draw axis + ::glColor3fv(AXES_COLOR[m_hover_id]); + ::glBegin(GL_LINES); + ::glVertex3dv(center.data()); + ::glVertex3dv(m_grabbers[m_hover_id].center.data()); + ::glEnd(); + + // draw grabber + m_grabbers[m_hover_id].render(true, box.max_size()); + render_grabber_extension((Axis)m_hover_id, box, false); + } +} + +void GLGizmoMove3D::on_render_for_picking(const GLCanvas3D::Selection& selection) const +{ + ::glDisable(GL_DEPTH_TEST); + + const BoundingBoxf3& box = selection.get_bounding_box(); + render_grabbers_for_picking(box); + render_grabber_extension(X, box, true); + render_grabber_extension(Y, box, true); + render_grabber_extension(Z, box, true); +} + +void GLGizmoMove3D::on_render_input_window(float x, float y, float bottom_limit, const GLCanvas3D::Selection& selection) +{ +#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI + bool show_position = selection.is_single_full_instance(); + const Vec3d& position = selection.get_bounding_box().center(); + + Vec3d displacement = show_position ? position : m_displacement; + wxString label = show_position ? _(L("Position (mm)")) : _(L("Displacement (mm)")); + + m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); + m_imgui->set_next_window_bg_alpha(0.5f); + m_imgui->begin(label, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + m_imgui->input_vec3("", displacement, 100.0f, "%.2f"); + + m_imgui->end(); +#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI +} + +double GLGizmoMove3D::calc_projection(const UpdateData& data) const +{ + double projection = 0.0; + + Vec3d starting_vec = m_starting_drag_position - m_starting_box_center; + double len_starting_vec = starting_vec.norm(); + if (len_starting_vec != 0.0) + { + Vec3d mouse_dir = data.mouse_ray.unit_vector(); + // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position + // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form + // in our case plane normal and ray direction are the same (orthogonal view) + // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal + Vec3d inters = data.mouse_ray.a + (m_starting_drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; + // vector from the starting position to the found intersection + Vec3d inters_vec = inters - m_starting_drag_position; + + // finds projection of the vector along the staring direction + projection = inters_vec.dot(starting_vec.normalized()); + } + + if (data.shift_down) + projection = m_snap_step * (double)std::round(projection / m_snap_step); + + return projection; +} + +void GLGizmoMove3D::render_grabber_extension(Axis axis, const BoundingBoxf3& box, bool picking) const +{ + if (m_quadric == nullptr) + return; + + double size = m_dragging ? (double)m_grabbers[axis].get_dragging_half_size((float)box.max_size()) : (double)m_grabbers[axis].get_half_size((float)box.max_size()); + + float color[3]; + ::memcpy((void*)color, (const void*)m_grabbers[axis].color, 3 * sizeof(float)); + if (!picking && (m_hover_id != -1)) + { + color[0] = 1.0f - color[0]; + color[1] = 1.0f - color[1]; + color[2] = 1.0f - color[2]; + } + + if (!picking) + ::glEnable(GL_LIGHTING); + + ::glColor3fv(color); + ::glPushMatrix(); + ::glTranslated(m_grabbers[axis].center(0), m_grabbers[axis].center(1), m_grabbers[axis].center(2)); + if (axis == X) + ::glRotated(90.0, 0.0, 1.0, 0.0); + else if (axis == Y) + ::glRotated(-90.0, 1.0, 0.0, 0.0); + + ::glTranslated(0.0, 0.0, 2.0 * size); + ::gluQuadricOrientation(m_quadric, GLU_OUTSIDE); + ::gluCylinder(m_quadric, 0.75 * size, 0.0, 3.0 * size, 36, 1); + ::gluQuadricOrientation(m_quadric, GLU_INSIDE); + ::gluDisk(m_quadric, 0.0, 0.75 * size, 36, 1); + ::glPopMatrix(); + + if (!picking) + ::glDisable(GL_LIGHTING); +} + + + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp new file mode 100644 index 000000000..d4d97de7f --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp @@ -0,0 +1,57 @@ +#ifndef slic3r_GLGizmoMove_hpp_ +#define slic3r_GLGizmoMove_hpp_ + +#include "GLGizmoBase.hpp" + + +namespace Slic3r { +namespace GUI { + +class GLGizmoMove3D : public GLGizmoBase +{ + static const double Offset; + + Vec3d m_displacement; + + double m_snap_step; + + Vec3d m_starting_drag_position; + Vec3d m_starting_box_center; + Vec3d m_starting_box_bottom_center; + + GLUquadricObj* m_quadric; + +public: +#if ENABLE_SVG_ICONS + GLGizmoMove3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); +#else + GLGizmoMove3D(GLCanvas3D& parent, unsigned int sprite_id); +#endif // ENABLE_SVG_ICONS + virtual ~GLGizmoMove3D(); + + double get_snap_step(double step) const { return m_snap_step; } + void set_snap_step(double step) { m_snap_step = step; } + + const Vec3d& get_displacement() const { return m_displacement; } + +protected: + virtual bool on_init(); + virtual std::string on_get_name() const; + virtual void on_start_dragging(const GLCanvas3D::Selection& selection); + virtual void on_stop_dragging(); + virtual void on_update(const UpdateData& data, const GLCanvas3D::Selection& selection); + virtual void on_render(const GLCanvas3D::Selection& selection) const; + virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const; + virtual void on_render_input_window(float x, float y, float bottom_limit, const GLCanvas3D::Selection& selection); + +private: + double calc_projection(const UpdateData& data) const; + void render_grabber_extension(Axis axis, const BoundingBoxf3& box, bool picking) const; +}; + + + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GLGizmoMove_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp new file mode 100644 index 000000000..4c6f66f4c --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -0,0 +1,503 @@ +// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. +#include "GLGizmoRotate.hpp" + +#include <GL/glew.h> + + +namespace Slic3r { +namespace GUI { + + +const float GLGizmoRotate::Offset = 5.0f; +const unsigned int GLGizmoRotate::CircleResolution = 64; +const unsigned int GLGizmoRotate::AngleResolution = 64; +const unsigned int GLGizmoRotate::ScaleStepsCount = 72; +const float GLGizmoRotate::ScaleStepRad = 2.0f * (float)PI / GLGizmoRotate::ScaleStepsCount; +const unsigned int GLGizmoRotate::ScaleLongEvery = 2; +const float GLGizmoRotate::ScaleLongTooth = 0.1f; // in percent of radius +const unsigned int GLGizmoRotate::SnapRegionsCount = 8; +const float GLGizmoRotate::GrabberOffset = 0.15f; // in percent of radius + +GLGizmoRotate::GLGizmoRotate(GLCanvas3D& parent, GLGizmoRotate::Axis axis) +#if ENABLE_SVG_ICONS + : GLGizmoBase(parent, "", -1) +#else + : GLGizmoBase(parent, -1) +#endif // ENABLE_SVG_ICONS + , m_axis(axis) + , m_angle(0.0) + , m_quadric(nullptr) + , m_center(0.0, 0.0, 0.0) + , m_radius(0.0f) + , m_snap_coarse_in_radius(0.0f) + , m_snap_coarse_out_radius(0.0f) + , m_snap_fine_in_radius(0.0f) + , m_snap_fine_out_radius(0.0f) +{ + m_quadric = ::gluNewQuadric(); + if (m_quadric != nullptr) + ::gluQuadricDrawStyle(m_quadric, GLU_FILL); +} + +GLGizmoRotate::GLGizmoRotate(const GLGizmoRotate& other) +#if ENABLE_SVG_ICONS + : GLGizmoBase(other.m_parent, other.m_icon_filename, other.m_sprite_id) +#else + : GLGizmoBase(other.m_parent, other.m_sprite_id) +#endif // ENABLE_SVG_ICONS + , m_axis(other.m_axis) + , m_angle(other.m_angle) + , m_quadric(nullptr) + , m_center(other.m_center) + , m_radius(other.m_radius) + , m_snap_coarse_in_radius(other.m_snap_coarse_in_radius) + , m_snap_coarse_out_radius(other.m_snap_coarse_out_radius) + , m_snap_fine_in_radius(other.m_snap_fine_in_radius) + , m_snap_fine_out_radius(other.m_snap_fine_out_radius) +{ + m_quadric = ::gluNewQuadric(); + if (m_quadric != nullptr) + ::gluQuadricDrawStyle(m_quadric, GLU_FILL); +} + +GLGizmoRotate::~GLGizmoRotate() +{ + if (m_quadric != nullptr) + ::gluDeleteQuadric(m_quadric); +} + +void GLGizmoRotate::set_angle(double angle) +{ + if (std::abs(angle - 2.0 * (double)PI) < EPSILON) + angle = 0.0; + + m_angle = angle; +} + +bool GLGizmoRotate::on_init() +{ + m_grabbers.push_back(Grabber()); + return true; +} + +void GLGizmoRotate::on_start_dragging(const GLCanvas3D::Selection& selection) +{ + const BoundingBoxf3& box = selection.get_bounding_box(); + m_center = box.center(); + m_radius = Offset + box.radius(); + m_snap_coarse_in_radius = m_radius / 3.0f; + m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius; + m_snap_fine_in_radius = m_radius; + m_snap_fine_out_radius = m_snap_fine_in_radius + m_radius * ScaleLongTooth; +} + +void GLGizmoRotate::on_update(const UpdateData& data, const GLCanvas3D::Selection& selection) +{ + Vec2d mouse_pos = to_2d(mouse_position_in_local_plane(data.mouse_ray, selection)); + + Vec2d orig_dir = Vec2d::UnitX(); + Vec2d new_dir = mouse_pos.normalized(); + + double theta = ::acos(clamp(-1.0, 1.0, new_dir.dot(orig_dir))); + if (cross2(orig_dir, new_dir) < 0.0) + theta = 2.0 * (double)PI - theta; + + double len = mouse_pos.norm(); + + // snap to coarse snap region + if ((m_snap_coarse_in_radius <= len) && (len <= m_snap_coarse_out_radius)) + { + double step = 2.0 * (double)PI / (double)SnapRegionsCount; + theta = step * (double)std::round(theta / step); + } + else + { + // snap to fine snap region (scale) + if ((m_snap_fine_in_radius <= len) && (len <= m_snap_fine_out_radius)) + { + double step = 2.0 * (double)PI / (double)ScaleStepsCount; + theta = step * (double)std::round(theta / step); + } + } + + if (theta == 2.0 * (double)PI) + theta = 0.0; + + m_angle = theta; +} + +void GLGizmoRotate::on_render(const GLCanvas3D::Selection& selection) const +{ + if (!m_grabbers[0].enabled) + return; + + const BoundingBoxf3& box = selection.get_bounding_box(); + + std::string axis; + switch (m_axis) + { + case X: { axis = "X"; break; } + case Y: { axis = "Y"; break; } + case Z: { axis = "Z"; break; } + } + + if (!m_dragging && (m_hover_id == 0)) + set_tooltip(axis); + else if (m_dragging) + set_tooltip(axis + ": " + format((float)Geometry::rad2deg(m_angle), 4) + "\u00B0"); + else + { + m_center = box.center(); + m_radius = Offset + box.radius(); + m_snap_coarse_in_radius = m_radius / 3.0f; + m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius; + m_snap_fine_in_radius = m_radius; + m_snap_fine_out_radius = m_radius * (1.0f + ScaleLongTooth); + } + + ::glEnable(GL_DEPTH_TEST); + + ::glPushMatrix(); + transform_to_local(selection); + + ::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f); + ::glColor3fv((m_hover_id != -1) ? m_drag_color : m_highlight_color); + + render_circle(); + + if (m_hover_id != -1) + { + render_scale(); + render_snap_radii(); + render_reference_radius(); + } + + ::glColor3fv(m_highlight_color); + + if (m_hover_id != -1) + render_angle(); + + render_grabber(box); + render_grabber_extension(box, false); + + ::glPopMatrix(); +} + +void GLGizmoRotate::on_render_for_picking(const GLCanvas3D::Selection& selection) const +{ + ::glDisable(GL_DEPTH_TEST); + + ::glPushMatrix(); + + transform_to_local(selection); + + const BoundingBoxf3& box = selection.get_bounding_box(); + render_grabbers_for_picking(box); + render_grabber_extension(box, true); + + ::glPopMatrix(); +} + +void GLGizmoRotate::render_circle() const +{ + ::glBegin(GL_LINE_LOOP); + for (unsigned int i = 0; i < ScaleStepsCount; ++i) + { + float angle = (float)i * ScaleStepRad; + float x = ::cos(angle) * m_radius; + float y = ::sin(angle) * m_radius; + float z = 0.0f; + ::glVertex3f((GLfloat)x, (GLfloat)y, (GLfloat)z); + } + ::glEnd(); +} + +void GLGizmoRotate::render_scale() const +{ + float out_radius_long = m_snap_fine_out_radius; + float out_radius_short = m_radius * (1.0f + 0.5f * ScaleLongTooth); + + ::glBegin(GL_LINES); + for (unsigned int i = 0; i < ScaleStepsCount; ++i) + { + float angle = (float)i * ScaleStepRad; + float cosa = ::cos(angle); + float sina = ::sin(angle); + float in_x = cosa * m_radius; + float in_y = sina * m_radius; + float in_z = 0.0f; + float out_x = (i % ScaleLongEvery == 0) ? cosa * out_radius_long : cosa * out_radius_short; + float out_y = (i % ScaleLongEvery == 0) ? sina * out_radius_long : sina * out_radius_short; + float out_z = 0.0f; + ::glVertex3f((GLfloat)in_x, (GLfloat)in_y, (GLfloat)in_z); + ::glVertex3f((GLfloat)out_x, (GLfloat)out_y, (GLfloat)out_z); + } + ::glEnd(); +} + +void GLGizmoRotate::render_snap_radii() const +{ + float step = 2.0f * (float)PI / (float)SnapRegionsCount; + + float in_radius = m_radius / 3.0f; + float out_radius = 2.0f * in_radius; + + ::glBegin(GL_LINES); + for (unsigned int i = 0; i < SnapRegionsCount; ++i) + { + float angle = (float)i * step; + float cosa = ::cos(angle); + float sina = ::sin(angle); + float in_x = cosa * in_radius; + float in_y = sina * in_radius; + float in_z = 0.0f; + float out_x = cosa * out_radius; + float out_y = sina * out_radius; + float out_z = 0.0f; + ::glVertex3f((GLfloat)in_x, (GLfloat)in_y, (GLfloat)in_z); + ::glVertex3f((GLfloat)out_x, (GLfloat)out_y, (GLfloat)out_z); + } + ::glEnd(); +} + +void GLGizmoRotate::render_reference_radius() const +{ + ::glBegin(GL_LINES); + ::glVertex3f(0.0f, 0.0f, 0.0f); + ::glVertex3f((GLfloat)(m_radius * (1.0f + GrabberOffset)), 0.0f, 0.0f); + ::glEnd(); +} + +void GLGizmoRotate::render_angle() const +{ + float step_angle = (float)m_angle / AngleResolution; + float ex_radius = m_radius * (1.0f + GrabberOffset); + + ::glBegin(GL_LINE_STRIP); + for (unsigned int i = 0; i <= AngleResolution; ++i) + { + float angle = (float)i * step_angle; + float x = ::cos(angle) * ex_radius; + float y = ::sin(angle) * ex_radius; + float z = 0.0f; + ::glVertex3f((GLfloat)x, (GLfloat)y, (GLfloat)z); + } + ::glEnd(); +} + +void GLGizmoRotate::render_grabber(const BoundingBoxf3& box) const +{ + double grabber_radius = (double)m_radius * (1.0 + (double)GrabberOffset); + m_grabbers[0].center = Vec3d(::cos(m_angle) * grabber_radius, ::sin(m_angle) * grabber_radius, 0.0); + m_grabbers[0].angles(2) = m_angle; + + ::glColor3fv((m_hover_id != -1) ? m_drag_color : m_highlight_color); + + ::glBegin(GL_LINES); + ::glVertex3f(0.0f, 0.0f, 0.0f); + ::glVertex3dv(m_grabbers[0].center.data()); + ::glEnd(); + + ::memcpy((void*)m_grabbers[0].color, (const void*)m_highlight_color, 3 * sizeof(float)); + render_grabbers(box); +} + +void GLGizmoRotate::render_grabber_extension(const BoundingBoxf3& box, bool picking) const +{ + if (m_quadric == nullptr) + return; + + double size = m_dragging ? (double)m_grabbers[0].get_dragging_half_size((float)box.max_size()) : (double)m_grabbers[0].get_half_size((float)box.max_size()); + + float color[3]; + ::memcpy((void*)color, (const void*)m_grabbers[0].color, 3 * sizeof(float)); + if (!picking && (m_hover_id != -1)) + { + color[0] = 1.0f - color[0]; + color[1] = 1.0f - color[1]; + color[2] = 1.0f - color[2]; + } + + if (!picking) + ::glEnable(GL_LIGHTING); + + ::glColor3fv(color); + ::glPushMatrix(); + ::glTranslated(m_grabbers[0].center(0), m_grabbers[0].center(1), m_grabbers[0].center(2)); + ::glRotated(Geometry::rad2deg(m_angle), 0.0, 0.0, 1.0); + ::glRotated(90.0, 1.0, 0.0, 0.0); + ::glTranslated(0.0, 0.0, 2.0 * size); + ::gluQuadricOrientation(m_quadric, GLU_OUTSIDE); + ::gluCylinder(m_quadric, 0.75 * size, 0.0, 3.0 * size, 36, 1); + ::gluQuadricOrientation(m_quadric, GLU_INSIDE); + ::gluDisk(m_quadric, 0.0, 0.75 * size, 36, 1); + ::glPopMatrix(); + ::glPushMatrix(); + ::glTranslated(m_grabbers[0].center(0), m_grabbers[0].center(1), m_grabbers[0].center(2)); + ::glRotated(Geometry::rad2deg(m_angle), 0.0, 0.0, 1.0); + ::glRotated(-90.0, 1.0, 0.0, 0.0); + ::glTranslated(0.0, 0.0, 2.0 * size); + ::gluQuadricOrientation(m_quadric, GLU_OUTSIDE); + ::gluCylinder(m_quadric, 0.75 * size, 0.0, 3.0 * size, 36, 1); + ::gluQuadricOrientation(m_quadric, GLU_INSIDE); + ::gluDisk(m_quadric, 0.0, 0.75 * size, 36, 1); + ::glPopMatrix(); + + if (!picking) + ::glDisable(GL_LIGHTING); +} + +void GLGizmoRotate::transform_to_local(const GLCanvas3D::Selection& selection) const +{ + ::glTranslated(m_center(0), m_center(1), m_center(2)); + + if (selection.is_single_volume() || selection.is_single_modifier() || selection.requires_local_axes()) + { + Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); + ::glMultMatrixd(orient_matrix.data()); + } + + switch (m_axis) + { + case X: + { + ::glRotatef(90.0f, 0.0f, 1.0f, 0.0f); + ::glRotatef(-90.0f, 0.0f, 0.0f, 1.0f); + break; + } + case Y: + { + ::glRotatef(-90.0f, 0.0f, 0.0f, 1.0f); + ::glRotatef(-90.0f, 0.0f, 1.0f, 0.0f); + break; + } + default: + case Z: + { + // no rotation + break; + } + } +} + +Vec3d GLGizmoRotate::mouse_position_in_local_plane(const Linef3& mouse_ray, const GLCanvas3D::Selection& selection) const +{ + double half_pi = 0.5 * (double)PI; + + Transform3d m = Transform3d::Identity(); + + switch (m_axis) + { + case X: + { + m.rotate(Eigen::AngleAxisd(half_pi, Vec3d::UnitZ())); + m.rotate(Eigen::AngleAxisd(-half_pi, Vec3d::UnitY())); + break; + } + case Y: + { + m.rotate(Eigen::AngleAxisd(half_pi, Vec3d::UnitY())); + m.rotate(Eigen::AngleAxisd(half_pi, Vec3d::UnitZ())); + break; + } + default: + case Z: + { + // no rotation applied + break; + } + } + + if (selection.is_single_volume() || selection.is_single_modifier() || selection.requires_local_axes()) + m = m * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true).inverse(); + + m.translate(-m_center); + + return transform(mouse_ray, m).intersect_plane(0.0); +} + +#if ENABLE_SVG_ICONS +GLGizmoRotate3D::GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) + : GLGizmoBase(parent, icon_filename, sprite_id) +#else +GLGizmoRotate3D::GLGizmoRotate3D(GLCanvas3D& parent, unsigned int sprite_id) + : GLGizmoBase(parent, sprite_id) +#endif // ENABLE_SVG_ICONS +{ + m_gizmos.emplace_back(parent, GLGizmoRotate::X); + m_gizmos.emplace_back(parent, GLGizmoRotate::Y); + m_gizmos.emplace_back(parent, GLGizmoRotate::Z); + + for (unsigned int i = 0; i < 3; ++i) + { + m_gizmos[i].set_group_id(i); + } +} + +bool GLGizmoRotate3D::on_init() +{ + for (GLGizmoRotate& g : m_gizmos) + { + if (!g.init()) + return false; + } + + for (unsigned int i = 0; i < 3; ++i) + { + m_gizmos[i].set_highlight_color(AXES_COLOR[i]); + } + + m_shortcut_key = WXK_CONTROL_R; + + return true; +} + +std::string GLGizmoRotate3D::on_get_name() const +{ + return L("Rotate [R]"); +} + +void GLGizmoRotate3D::on_start_dragging(const GLCanvas3D::Selection& selection) +{ + if ((0 <= m_hover_id) && (m_hover_id < 3)) + m_gizmos[m_hover_id].start_dragging(selection); +} + +void GLGizmoRotate3D::on_stop_dragging() +{ + if ((0 <= m_hover_id) && (m_hover_id < 3)) + m_gizmos[m_hover_id].stop_dragging(); +} + +void GLGizmoRotate3D::on_render(const GLCanvas3D::Selection& selection) const +{ + ::glClear(GL_DEPTH_BUFFER_BIT); + + if ((m_hover_id == -1) || (m_hover_id == 0)) + m_gizmos[X].render(selection); + + if ((m_hover_id == -1) || (m_hover_id == 1)) + m_gizmos[Y].render(selection); + + if ((m_hover_id == -1) || (m_hover_id == 2)) + m_gizmos[Z].render(selection); +} + +void GLGizmoRotate3D::on_render_input_window(float x, float y, float bottom_limit, const GLCanvas3D::Selection& selection) +{ +#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI + Vec3d rotation(Geometry::rad2deg(m_gizmos[0].get_angle()), Geometry::rad2deg(m_gizmos[1].get_angle()), Geometry::rad2deg(m_gizmos[2].get_angle())); + wxString label = _(L("Rotation (deg)")); + + m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); + m_imgui->set_next_window_bg_alpha(0.5f); + m_imgui->begin(label, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + m_imgui->input_vec3("", rotation, 100.0f, "%.2f"); + m_imgui->end(); +#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI +} + + + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp new file mode 100644 index 000000000..aca64c94e --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -0,0 +1,142 @@ +#ifndef slic3r_GLGizmoRotate_hpp_ +#define slic3r_GLGizmoRotate_hpp_ + +#include "GLGizmoBase.hpp" + + +namespace Slic3r { +namespace GUI { + +class GLGizmoRotate : public GLGizmoBase +{ + static const float Offset; + static const unsigned int CircleResolution; + static const unsigned int AngleResolution; + static const unsigned int ScaleStepsCount; + static const float ScaleStepRad; + static const unsigned int ScaleLongEvery; + static const float ScaleLongTooth; + static const unsigned int SnapRegionsCount; + static const float GrabberOffset; + +public: + enum Axis : unsigned char + { + X, + Y, + Z + }; + +private: + Axis m_axis; + double m_angle; + + GLUquadricObj* m_quadric; + + mutable Vec3d m_center; + mutable float m_radius; + + mutable float m_snap_coarse_in_radius; + mutable float m_snap_coarse_out_radius; + mutable float m_snap_fine_in_radius; + mutable float m_snap_fine_out_radius; + +public: + GLGizmoRotate(GLCanvas3D& parent, Axis axis); + GLGizmoRotate(const GLGizmoRotate& other); + virtual ~GLGizmoRotate(); + + double get_angle() const { return m_angle; } + void set_angle(double angle); + +protected: + virtual bool on_init(); + virtual std::string on_get_name() const { return ""; } + virtual void on_start_dragging(const GLCanvas3D::Selection& selection); + virtual void on_update(const UpdateData& data, const GLCanvas3D::Selection& selection); + virtual void on_render(const GLCanvas3D::Selection& selection) const; + virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const; + +private: + void render_circle() const; + void render_scale() const; + void render_snap_radii() const; + void render_reference_radius() const; + void render_angle() const; + void render_grabber(const BoundingBoxf3& box) const; + void render_grabber_extension(const BoundingBoxf3& box, bool picking) const; + + void transform_to_local(const GLCanvas3D::Selection& selection) const; + // returns the intersection of the mouse ray with the plane perpendicular to the gizmo axis, in local coordinate + Vec3d mouse_position_in_local_plane(const Linef3& mouse_ray, const GLCanvas3D::Selection& selection) const; +}; + +class GLGizmoRotate3D : public GLGizmoBase +{ + std::vector<GLGizmoRotate> m_gizmos; + +public: +#if ENABLE_SVG_ICONS + GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); +#else + GLGizmoRotate3D(GLCanvas3D& parent, unsigned int sprite_id); +#endif // ENABLE_SVG_ICONS + + Vec3d get_rotation() const { return Vec3d(m_gizmos[X].get_angle(), m_gizmos[Y].get_angle(), m_gizmos[Z].get_angle()); } + void set_rotation(const Vec3d& rotation) { m_gizmos[X].set_angle(rotation(0)); m_gizmos[Y].set_angle(rotation(1)); m_gizmos[Z].set_angle(rotation(2)); } + +protected: + virtual bool on_init(); + virtual std::string on_get_name() const; + virtual void on_set_state() + { + for (GLGizmoRotate& g : m_gizmos) + { + g.set_state(m_state); + } + } + virtual void on_set_hover_id() + { + for (unsigned int i = 0; i < 3; ++i) + { + m_gizmos[i].set_hover_id((m_hover_id == i) ? 0 : -1); + } + } + virtual bool on_is_activable(const GLCanvas3D::Selection& selection) const { return !selection.is_wipe_tower(); } + virtual void on_enable_grabber(unsigned int id) + { + if ((0 <= id) && (id < 3)) + m_gizmos[id].enable_grabber(0); + } + virtual void on_disable_grabber(unsigned int id) + { + if ((0 <= id) && (id < 3)) + m_gizmos[id].disable_grabber(0); + } + virtual void on_start_dragging(const GLCanvas3D::Selection& selection); + virtual void on_stop_dragging(); + virtual void on_update(const UpdateData& data, const GLCanvas3D::Selection& selection) + { + for (GLGizmoRotate& g : m_gizmos) + { + g.update(data, selection); + } + } + virtual void on_render(const GLCanvas3D::Selection& selection) const; + virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const + { + for (const GLGizmoRotate& g : m_gizmos) + { + g.render_for_picking(selection); + } + } + + virtual void on_render_input_window(float x, float y, float bottom_limit, const GLCanvas3D::Selection& selection); +}; + + + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GLGizmoRotate_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp new file mode 100644 index 000000000..acc44e00d --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -0,0 +1,362 @@ + + +// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. +#include "GLGizmoScale.hpp" + +#include <GL/glew.h> + +namespace Slic3r { +namespace GUI { + + +const float GLGizmoScale3D::Offset = 5.0f; + +#if ENABLE_SVG_ICONS +GLGizmoScale3D::GLGizmoScale3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) + : GLGizmoBase(parent, icon_filename, sprite_id) +#else +GLGizmoScale3D::GLGizmoScale3D(GLCanvas3D& parent, unsigned int sprite_id) + : GLGizmoBase(parent, sprite_id) +#endif // ENABLE_SVG_ICONS + , m_scale(Vec3d::Ones()) + , m_snap_step(0.05) + , m_starting_scale(Vec3d::Ones()) +{ +} + +bool GLGizmoScale3D::on_init() +{ + for (int i = 0; i < 10; ++i) + { + m_grabbers.push_back(Grabber()); + } + + double half_pi = 0.5 * (double)PI; + + // x axis + m_grabbers[0].angles(1) = half_pi; + m_grabbers[1].angles(1) = half_pi; + + // y axis + m_grabbers[2].angles(0) = half_pi; + m_grabbers[3].angles(0) = half_pi; + + m_shortcut_key = WXK_CONTROL_S; + + return true; +} + +std::string GLGizmoScale3D::on_get_name() const +{ + return L("Scale [S]"); +} + +void GLGizmoScale3D::on_start_dragging(const GLCanvas3D::Selection& selection) +{ + if (m_hover_id != -1) + { + m_starting_drag_position = m_grabbers[m_hover_id].center; + m_starting_box = selection.get_bounding_box(); + } +} + +void GLGizmoScale3D::on_update(const UpdateData& data, const GLCanvas3D::Selection& selection) +{ + if ((m_hover_id == 0) || (m_hover_id == 1)) + do_scale_x(data); + else if ((m_hover_id == 2) || (m_hover_id == 3)) + do_scale_y(data); + else if ((m_hover_id == 4) || (m_hover_id == 5)) + do_scale_z(data); + else if (m_hover_id >= 6) + do_scale_uniform(data); +} + +void GLGizmoScale3D::on_render(const GLCanvas3D::Selection& selection) const +{ + bool single_instance = selection.is_single_full_instance(); + bool single_volume = selection.is_single_modifier() || selection.is_single_volume(); + bool single_selection = single_instance || single_volume; + + Vec3f scale = 100.0f * Vec3f::Ones(); + if (single_instance) + scale = 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor().cast<float>(); + else if (single_volume) + scale = 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_scaling_factor().cast<float>(); + + if ((single_selection && ((m_hover_id == 0) || (m_hover_id == 1))) || m_grabbers[0].dragging || m_grabbers[1].dragging) + set_tooltip("X: " + format(scale(0), 4) + "%"); + else if (!m_grabbers[0].dragging && !m_grabbers[1].dragging && ((m_hover_id == 0) || (m_hover_id == 1))) + set_tooltip("X"); + else if ((single_selection && ((m_hover_id == 2) || (m_hover_id == 3))) || m_grabbers[2].dragging || m_grabbers[3].dragging) + set_tooltip("Y: " + format(scale(1), 4) + "%"); + else if (!m_grabbers[2].dragging && !m_grabbers[3].dragging && ((m_hover_id == 2) || (m_hover_id == 3))) + set_tooltip("Y"); + else if ((single_selection && ((m_hover_id == 4) || (m_hover_id == 5))) || m_grabbers[4].dragging || m_grabbers[5].dragging) + set_tooltip("Z: " + format(scale(2), 4) + "%"); + else if (!m_grabbers[4].dragging && !m_grabbers[5].dragging && ((m_hover_id == 4) || (m_hover_id == 5))) + set_tooltip("Z"); + else if ((single_selection && ((m_hover_id == 6) || (m_hover_id == 7) || (m_hover_id == 8) || (m_hover_id == 9))) + || m_grabbers[6].dragging || m_grabbers[7].dragging || m_grabbers[8].dragging || m_grabbers[9].dragging) + { + std::string tooltip = "X: " + format(scale(0), 4) + "%\n"; + tooltip += "Y: " + format(scale(1), 4) + "%\n"; + tooltip += "Z: " + format(scale(2), 4) + "%"; + set_tooltip(tooltip); + } + else if (!m_grabbers[6].dragging && !m_grabbers[7].dragging && !m_grabbers[8].dragging && !m_grabbers[9].dragging && + ((m_hover_id == 6) || (m_hover_id == 7) || (m_hover_id == 8) || (m_hover_id == 9))) + set_tooltip("X/Y/Z"); + + ::glClear(GL_DEPTH_BUFFER_BIT); + ::glEnable(GL_DEPTH_TEST); + + BoundingBoxf3 box; + Transform3d transform = Transform3d::Identity(); + Vec3d angles = Vec3d::Zero(); + Transform3d offsets_transform = Transform3d::Identity(); + + Vec3d grabber_size = Vec3d::Zero(); + + if (single_instance) + { + // calculate bounding box in instance local reference system + const GLCanvas3D::Selection::IndicesList& idxs = selection.get_volume_idxs(); + for (unsigned int idx : idxs) + { + const GLVolume* vol = selection.get_volume(idx); + box.merge(vol->bounding_box.transformed(vol->get_volume_transformation().get_matrix())); + } + + // gets transform from first selected volume + const GLVolume* v = selection.get_volume(*idxs.begin()); + transform = v->get_instance_transformation().get_matrix(); + // gets angles from first selected volume + angles = v->get_instance_rotation(); + // consider rotation+mirror only components of the transform for offsets + offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_instance_mirror()); + grabber_size = v->get_instance_transformation().get_matrix(true, true, false, true) * box.size(); + } + else if (single_volume) + { + const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); + box = v->bounding_box; + transform = v->world_matrix(); + angles = Geometry::extract_euler_angles(transform); + // consider rotation+mirror only components of the transform for offsets + offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_instance_mirror()); + grabber_size = v->get_volume_transformation().get_matrix(true, true, false, true) * box.size(); + } + else + { + box = selection.get_bounding_box(); + grabber_size = box.size(); + } + + m_box = box; + + const Vec3d& center = m_box.center(); + Vec3d offset_x = offsets_transform * Vec3d((double)Offset, 0.0, 0.0); + Vec3d offset_y = offsets_transform * Vec3d(0.0, (double)Offset, 0.0); + Vec3d offset_z = offsets_transform * Vec3d(0.0, 0.0, (double)Offset); + + // x axis + m_grabbers[0].center = transform * Vec3d(m_box.min(0), center(1), center(2)) - offset_x; + m_grabbers[1].center = transform * Vec3d(m_box.max(0), center(1), center(2)) + offset_x; + ::memcpy((void*)m_grabbers[0].color, (const void*)&AXES_COLOR[0], 3 * sizeof(float)); + ::memcpy((void*)m_grabbers[1].color, (const void*)&AXES_COLOR[0], 3 * sizeof(float)); + + // y axis + m_grabbers[2].center = transform * Vec3d(center(0), m_box.min(1), center(2)) - offset_y; + m_grabbers[3].center = transform * Vec3d(center(0), m_box.max(1), center(2)) + offset_y; + ::memcpy((void*)m_grabbers[2].color, (const void*)&AXES_COLOR[1], 3 * sizeof(float)); + ::memcpy((void*)m_grabbers[3].color, (const void*)&AXES_COLOR[1], 3 * sizeof(float)); + + // z axis + m_grabbers[4].center = transform * Vec3d(center(0), center(1), m_box.min(2)) - offset_z; + m_grabbers[5].center = transform * Vec3d(center(0), center(1), m_box.max(2)) + offset_z; + ::memcpy((void*)m_grabbers[4].color, (const void*)&AXES_COLOR[2], 3 * sizeof(float)); + ::memcpy((void*)m_grabbers[5].color, (const void*)&AXES_COLOR[2], 3 * sizeof(float)); + + // uniform + m_grabbers[6].center = transform * Vec3d(m_box.min(0), m_box.min(1), center(2)) - offset_x - offset_y; + m_grabbers[7].center = transform * Vec3d(m_box.max(0), m_box.min(1), center(2)) + offset_x - offset_y; + m_grabbers[8].center = transform * Vec3d(m_box.max(0), m_box.max(1), center(2)) + offset_x + offset_y; + m_grabbers[9].center = transform * Vec3d(m_box.min(0), m_box.max(1), center(2)) - offset_x + offset_y; + for (int i = 6; i < 10; ++i) + { + ::memcpy((void*)m_grabbers[i].color, (const void*)m_highlight_color, 3 * sizeof(float)); + } + + // sets grabbers orientation + for (int i = 0; i < 10; ++i) + { + m_grabbers[i].angles = angles; + } + + ::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f); + + float grabber_mean_size = (float)(grabber_size(0) + grabber_size(1) + grabber_size(2)) / 3.0f; + + if (m_hover_id == -1) + { + // draw connections + if (m_grabbers[0].enabled && m_grabbers[1].enabled) + { + ::glColor3fv(m_grabbers[0].color); + render_grabbers_connection(0, 1); + } + if (m_grabbers[2].enabled && m_grabbers[3].enabled) + { + ::glColor3fv(m_grabbers[2].color); + render_grabbers_connection(2, 3); + } + if (m_grabbers[4].enabled && m_grabbers[5].enabled) + { + ::glColor3fv(m_grabbers[4].color); + render_grabbers_connection(4, 5); + } + ::glColor3fv(m_base_color); + render_grabbers_connection(6, 7); + render_grabbers_connection(7, 8); + render_grabbers_connection(8, 9); + render_grabbers_connection(9, 6); + // draw grabbers + render_grabbers(grabber_mean_size); + } + else if ((m_hover_id == 0) || (m_hover_id == 1)) + { + // draw connection + ::glColor3fv(m_grabbers[0].color); + render_grabbers_connection(0, 1); + // draw grabbers + m_grabbers[0].render(true, grabber_mean_size); + m_grabbers[1].render(true, grabber_mean_size); + } + else if ((m_hover_id == 2) || (m_hover_id == 3)) + { + // draw connection + ::glColor3fv(m_grabbers[2].color); + render_grabbers_connection(2, 3); + // draw grabbers + m_grabbers[2].render(true, grabber_mean_size); + m_grabbers[3].render(true, grabber_mean_size); + } + else if ((m_hover_id == 4) || (m_hover_id == 5)) + { + // draw connection + ::glColor3fv(m_grabbers[4].color); + render_grabbers_connection(4, 5); + // draw grabbers + m_grabbers[4].render(true, grabber_mean_size); + m_grabbers[5].render(true, grabber_mean_size); + } + else if (m_hover_id >= 6) + { + // draw connection + ::glColor3fv(m_drag_color); + render_grabbers_connection(6, 7); + render_grabbers_connection(7, 8); + render_grabbers_connection(8, 9); + render_grabbers_connection(9, 6); + // draw grabbers + for (int i = 6; i < 10; ++i) + { + m_grabbers[i].render(true, grabber_mean_size); + } + } +} + +void GLGizmoScale3D::on_render_for_picking(const GLCanvas3D::Selection& selection) const +{ + ::glDisable(GL_DEPTH_TEST); + + render_grabbers_for_picking(selection.get_bounding_box()); +} + +void GLGizmoScale3D::on_render_input_window(float x, float y, float bottom_limit, const GLCanvas3D::Selection& selection) +{ +#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI + bool single_instance = selection.is_single_full_instance(); + wxString label = _(L("Scale (%)")); + + m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); + m_imgui->set_next_window_bg_alpha(0.5f); + m_imgui->begin(label, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + m_imgui->input_vec3("", m_scale * 100.f, 100.0f, "%.2f"); + m_imgui->end(); +#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI +} + +void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int id_2) const +{ + unsigned int grabbers_count = (unsigned int)m_grabbers.size(); + if ((id_1 < grabbers_count) && (id_2 < grabbers_count)) + { + ::glBegin(GL_LINES); + ::glVertex3dv(m_grabbers[id_1].center.data()); + ::glVertex3dv(m_grabbers[id_2].center.data()); + ::glEnd(); + } +} + +void GLGizmoScale3D::do_scale_x(const UpdateData& data) +{ + double ratio = calc_ratio(data); + if (ratio > 0.0) + m_scale(0) = m_starting_scale(0) * ratio; +} + +void GLGizmoScale3D::do_scale_y(const UpdateData& data) +{ + double ratio = calc_ratio(data); + if (ratio > 0.0) + m_scale(1) = m_starting_scale(1) * ratio; +} + +void GLGizmoScale3D::do_scale_z(const UpdateData& data) +{ + double ratio = calc_ratio(data); + if (ratio > 0.0) + m_scale(2) = m_starting_scale(2) * ratio; +} + +void GLGizmoScale3D::do_scale_uniform(const UpdateData& data) +{ + double ratio = calc_ratio(data); + if (ratio > 0.0) + m_scale = m_starting_scale * ratio; +} + +double GLGizmoScale3D::calc_ratio(const UpdateData& data) const +{ + double ratio = 0.0; + + // vector from the center to the starting position + Vec3d starting_vec = m_starting_drag_position - m_starting_box.center(); + double len_starting_vec = starting_vec.norm(); + if (len_starting_vec != 0.0) + { + Vec3d mouse_dir = data.mouse_ray.unit_vector(); + // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position + // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form + // in our case plane normal and ray direction are the same (orthogonal view) + // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal + Vec3d inters = data.mouse_ray.a + (m_starting_drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; + // vector from the starting position to the found intersection + Vec3d inters_vec = inters - m_starting_drag_position; + + // finds projection of the vector along the staring direction + double proj = inters_vec.dot(starting_vec.normalized()); + + ratio = (len_starting_vec + proj) / len_starting_vec; + } + + if (data.shift_down) + ratio = m_snap_step * (double)std::round(ratio / m_snap_step); + + return ratio; +} + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp new file mode 100644 index 000000000..8554e57a7 --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -0,0 +1,62 @@ +#ifndef slic3r_GLGizmoScale_hpp_ +#define slic3r_GLGizmoScale_hpp_ + +#include "GLGizmoBase.hpp" + + +namespace Slic3r { +namespace GUI { + +class GLGizmoScale3D : public GLGizmoBase +{ + static const float Offset; + + mutable BoundingBoxf3 m_box; + + Vec3d m_scale; + + double m_snap_step; + + Vec3d m_starting_scale; + Vec3d m_starting_drag_position; + BoundingBoxf3 m_starting_box; + +public: +#if ENABLE_SVG_ICONS + GLGizmoScale3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); +#else + GLGizmoScale3D(GLCanvas3D& parent, unsigned int sprite_id); +#endif // ENABLE_SVG_ICONS + + double get_snap_step(double step) const { return m_snap_step; } + void set_snap_step(double step) { m_snap_step = step; } + + const Vec3d& get_scale() const { return m_scale; } + void set_scale(const Vec3d& scale) { m_starting_scale = scale; m_scale = scale; } + +protected: + virtual bool on_init(); + virtual std::string on_get_name() const; + virtual bool on_is_activable(const GLCanvas3D::Selection& selection) const { return !selection.is_wipe_tower(); } + virtual void on_start_dragging(const GLCanvas3D::Selection& selection); + virtual void on_update(const UpdateData& data, const GLCanvas3D::Selection& selection); + virtual void on_render(const GLCanvas3D::Selection& selection) const; + virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const; + virtual void on_render_input_window(float x, float y, float bottom_limit, const GLCanvas3D::Selection& selection); + +private: + void render_grabbers_connection(unsigned int id_1, unsigned int id_2) const; + + void do_scale_x(const UpdateData& data); + void do_scale_y(const UpdateData& data); + void do_scale_z(const UpdateData& data); + void do_scale_uniform(const UpdateData& data); + + double calc_ratio(const UpdateData& data) const; +}; + + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GLGizmoScale_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp new file mode 100644 index 000000000..695564971 --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -0,0 +1,874 @@ +// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. +#include "GLGizmoSlaSupports.hpp" + +#include <GL/glew.h> + +#include <wx/msgdlg.h> + +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/GUI_ObjectSettings.hpp" +#include "slic3r/GUI/GUI_ObjectList.hpp" +#include "slic3r/GUI/PresetBundle.hpp" + + +namespace Slic3r { +namespace GUI { + +#if ENABLE_SVG_ICONS +GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) + : GLGizmoBase(parent, icon_filename, sprite_id) +#else +GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, unsigned int sprite_id) + : GLGizmoBase(parent, sprite_id) +#endif // ENABLE_SVG_ICONS + , m_starting_center(Vec3d::Zero()), m_quadric(nullptr) +{ + m_quadric = ::gluNewQuadric(); + if (m_quadric != nullptr) + // using GLU_FILL does not work when the instance's transformation + // contains mirroring (normals are reverted) + ::gluQuadricDrawStyle(m_quadric, GLU_FILL); +} + +GLGizmoSlaSupports::~GLGizmoSlaSupports() +{ + if (m_quadric != nullptr) + ::gluDeleteQuadric(m_quadric); +} + +bool GLGizmoSlaSupports::on_init() +{ + m_shortcut_key = WXK_CONTROL_L; + return true; +} + +void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const GLCanvas3D::Selection& selection) +{ + m_starting_center = Vec3d::Zero(); + m_old_model_object = m_model_object; + m_model_object = model_object; + if (selection.is_empty()) + m_old_instance_id = -1; + + m_active_instance = selection.get_instance_idx(); + + if (model_object && selection.is_from_single_instance()) + { + if (is_mesh_update_necessary()) { + update_mesh(); + editing_mode_reload_cache(); + } + + if (m_model_object != m_old_model_object) + m_editing_mode = false; + + if (m_editing_mode_cache.empty() && m_model_object->sla_points_status != sla::PointsStatus::UserModified) + get_data_from_backend(); + + if (m_state == On) { + m_parent.toggle_model_objects_visibility(false); + m_parent.toggle_model_objects_visibility(true, m_model_object, m_active_instance); + } + } +} + +void GLGizmoSlaSupports::on_render(const GLCanvas3D::Selection& selection) const +{ + ::glEnable(GL_BLEND); + ::glEnable(GL_DEPTH_TEST); + + render_points(selection, false); + render_selection_rectangle(); + + ::glDisable(GL_BLEND); +} + +void GLGizmoSlaSupports::render_selection_rectangle() const +{ + if (!m_selection_rectangle_active) + return; + + ::glLineWidth(1.5f); + float render_color[3] = {1.f, 0.f, 0.f}; + ::glColor3fv(render_color); + + ::glPushAttrib(GL_TRANSFORM_BIT); // remember current MatrixMode + + ::glMatrixMode(GL_MODELVIEW); // cache modelview matrix and set to identity + ::glPushMatrix(); + ::glLoadIdentity(); + + ::glMatrixMode(GL_PROJECTION); // cache projection matrix and set to identity + ::glPushMatrix(); + ::glLoadIdentity(); + + ::glOrtho(0.f, m_canvas_width, m_canvas_height, 0.f, -1.f, 1.f); // set projection matrix so that world coords = window coords + + // render the selection rectangle (window coordinates): + ::glPushAttrib(GL_ENABLE_BIT); + ::glLineStipple(4, 0xAAAA); + ::glEnable(GL_LINE_STIPPLE); + + ::glBegin(GL_LINE_LOOP); + ::glVertex3f((GLfloat)m_selection_rectangle_start_corner(0), (GLfloat)m_selection_rectangle_start_corner(1), (GLfloat)0.5f); + ::glVertex3f((GLfloat)m_selection_rectangle_end_corner(0), (GLfloat)m_selection_rectangle_start_corner(1), (GLfloat)0.5f); + ::glVertex3f((GLfloat)m_selection_rectangle_end_corner(0), (GLfloat)m_selection_rectangle_end_corner(1), (GLfloat)0.5f); + ::glVertex3f((GLfloat)m_selection_rectangle_start_corner(0), (GLfloat)m_selection_rectangle_end_corner(1), (GLfloat)0.5f); + ::glEnd(); + ::glPopAttrib(); + + ::glPopMatrix(); // restore former projection matrix + ::glMatrixMode(GL_MODELVIEW); + ::glPopMatrix(); // restore former modelview matrix + ::glPopAttrib(); // restore former MatrixMode +} + +void GLGizmoSlaSupports::on_render_for_picking(const GLCanvas3D::Selection& selection) const +{ + ::glEnable(GL_DEPTH_TEST); + + render_points(selection, true); +} + +void GLGizmoSlaSupports::render_points(const GLCanvas3D::Selection& selection, bool picking) const +{ + if (m_quadric == nullptr || !selection.is_from_single_instance()) + return; + + if (!picking) + ::glEnable(GL_LIGHTING); + + const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin()); + double z_shift = vol->get_sla_shift_z(); + const Transform3d& instance_scaling_matrix_inverse = vol->get_instance_transformation().get_matrix(true, true, false, true).inverse(); + const Transform3d& instance_matrix = vol->get_instance_transformation().get_matrix(); + + ::glPushMatrix(); + ::glTranslated(0.0, 0.0, z_shift); + ::glMultMatrixd(instance_matrix.data()); + + float render_color[3]; + for (int i = 0; i < (int)m_editing_mode_cache.size(); ++i) + { + const sla::SupportPoint& support_point = m_editing_mode_cache[i].support_point; + const bool& point_selected = m_editing_mode_cache[i].selected; + + // First decide about the color of the point. + if (picking) { + std::array<float, 3> color = picking_color_component(i); + render_color[0] = color[0]; + render_color[1] = color[1]; + render_color[2] = color[2]; + } + else { + if ((m_hover_id == i && m_editing_mode)) { // ignore hover state unless editing mode is active + render_color[0] = 0.f; + render_color[1] = 1.0f; + render_color[2] = 1.0f; + } + else { // neigher hover nor picking + bool supports_new_island = m_lock_unique_islands && m_editing_mode_cache[i].support_point.is_new_island; + if (m_editing_mode) { + render_color[0] = point_selected ? 1.0f : (supports_new_island ? 0.3f : 0.7f); + render_color[1] = point_selected ? 0.3f : (supports_new_island ? 0.3f : 0.7f); + render_color[2] = point_selected ? 0.3f : (supports_new_island ? 1.0f : 0.7f); + } + else + for (unsigned char i=0; i<3; ++i) render_color[i] = 0.5f; + } + } + ::glColor3fv(render_color); + float render_color_emissive[4] = { 0.5f * render_color[0], 0.5f * render_color[1], 0.5f * render_color[2], 1.f}; + ::glMaterialfv(GL_FRONT, GL_EMISSION, render_color_emissive); + + // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. + ::glPushMatrix(); + ::glTranslated(support_point.pos(0), support_point.pos(1), support_point.pos(2)); + ::glMultMatrixd(instance_scaling_matrix_inverse.data()); + + // Matrices set, we can render the point mark now. + // If in editing mode, we'll also render a cone pointing to the sphere. + if (m_editing_mode) { + if (m_editing_mode_cache[i].normal == Vec3f::Zero()) + update_cache_entry_normal(i); // in case the normal is not yet cached, find and cache it + + Eigen::Quaterniond q; + q.setFromTwoVectors(Vec3d{0., 0., 1.}, instance_scaling_matrix_inverse * m_editing_mode_cache[i].normal.cast<double>()); + Eigen::AngleAxisd aa(q); + ::glRotated(aa.angle() * (180./M_PI), aa.axis()(0), aa.axis()(1), aa.axis()(2)); + + const float cone_radius = 0.25f; // mm + const float cone_height = 0.75f; + ::glPushMatrix(); + ::glTranslatef(0.f, 0.f, m_editing_mode_cache[i].support_point.head_front_radius * RenderPointScale); + ::gluCylinder(m_quadric, 0.f, cone_radius, cone_height, 36, 1); + ::glTranslatef(0.f, 0.f, cone_height); + ::gluDisk(m_quadric, 0.0, cone_radius, 36, 1); + ::glPopMatrix(); + } + ::gluSphere(m_quadric, m_editing_mode_cache[i].support_point.head_front_radius * RenderPointScale, 64, 36); + ::glPopMatrix(); + } + + { + // Reset emissive component to zero (the default value) + float render_color_emissive[4] = { 0.f, 0.f, 0.f, 1.f }; + ::glMaterialfv(GL_FRONT, GL_EMISSION, render_color_emissive); + } + + if (!picking) + ::glDisable(GL_LIGHTING); + + ::glPopMatrix(); +} + +bool GLGizmoSlaSupports::is_mesh_update_necessary() const +{ + return ((m_state == On) && (m_model_object != nullptr) && !m_model_object->instances.empty()) + && ((m_model_object != m_old_model_object) || m_V.size()==0); + + //if (m_state != On || !m_model_object || m_model_object->instances.empty() || ! m_instance_matrix.isApprox(m_source_data.matrix)) + // return false; +} + +void GLGizmoSlaSupports::update_mesh() +{ + wxBusyCursor wait; + Eigen::MatrixXf& V = m_V; + Eigen::MatrixXi& F = m_F; + // Composite mesh of all instances in the world coordinate system. + // This mesh does not account for the possible Z up SLA offset. + TriangleMesh mesh = m_model_object->raw_mesh(); + const stl_file& stl = mesh.stl; + V.resize(3 * stl.stats.number_of_facets, 3); + F.resize(stl.stats.number_of_facets, 3); + for (unsigned int i=0; i<stl.stats.number_of_facets; ++i) { + const stl_facet* facet = stl.facet_start+i; + V(3*i+0, 0) = facet->vertex[0](0); V(3*i+0, 1) = facet->vertex[0](1); V(3*i+0, 2) = facet->vertex[0](2); + V(3*i+1, 0) = facet->vertex[1](0); V(3*i+1, 1) = facet->vertex[1](1); V(3*i+1, 2) = facet->vertex[1](2); + V(3*i+2, 0) = facet->vertex[2](0); V(3*i+2, 1) = facet->vertex[2](1); V(3*i+2, 2) = facet->vertex[2](2); + F(i, 0) = 3*i+0; + F(i, 1) = 3*i+1; + F(i, 2) = 3*i+2; + } + + m_AABB = igl::AABB<Eigen::MatrixXf,3>(); + m_AABB.init(m_V, m_F); +} + +std::pair<Vec3f, Vec3f> GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos) +{ + // if the gizmo doesn't have the V, F structures for igl, calculate them first: + if (m_V.size() == 0) + update_mesh(); + + Eigen::Matrix<GLint, 4, 1, Eigen::DontAlign> viewport; + ::glGetIntegerv(GL_VIEWPORT, viewport.data()); + Eigen::Matrix<GLdouble, 4, 4, Eigen::DontAlign> modelview_matrix; + ::glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix.data()); + Eigen::Matrix<GLdouble, 4, 4, Eigen::DontAlign> projection_matrix; + ::glGetDoublev(GL_PROJECTION_MATRIX, projection_matrix.data()); + + Vec3d point1; + Vec3d point2; + ::gluUnProject(mouse_pos(0), viewport(3)-mouse_pos(1), 0.f, modelview_matrix.data(), projection_matrix.data(), viewport.data(), &point1(0), &point1(1), &point1(2)); + ::gluUnProject(mouse_pos(0), viewport(3)-mouse_pos(1), 1.f, modelview_matrix.data(), projection_matrix.data(), viewport.data(), &point2(0), &point2(1), &point2(2)); + + igl::Hit hit; + + const GLCanvas3D::Selection& selection = m_parent.get_selection(); + const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + double z_offset = volume->get_sla_shift_z(); + + point1(2) -= z_offset; + point2(2) -= z_offset; + + Transform3d inv = volume->get_instance_transformation().get_matrix().inverse(); + + point1 = inv * point1; + point2 = inv * point2; + + if (!m_AABB.intersect_ray(m_V, m_F, point1.cast<float>(), (point2-point1).cast<float>(), hit)) + throw std::invalid_argument("unproject_on_mesh(): No intersection found."); + + int fid = hit.id; // facet id + Vec3f bc(1-hit.u-hit.v, hit.u, hit.v); // barycentric coordinates of the hit + Vec3f a = (m_V.row(m_F(fid, 1)) - m_V.row(m_F(fid, 0))); + Vec3f b = (m_V.row(m_F(fid, 2)) - m_V.row(m_F(fid, 0))); + + // Calculate and return both the point and the facet normal. + return std::make_pair( + bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2)), + a.cross(b) + ); +} + +// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event. +// The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is +// aware that the event was reacted to and stops trying to make different sense of it. If the gizmo +// concludes that the event was not intended for it, it should return false. +bool GLGizmoSlaSupports::mouse_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down) +{ + if (m_editing_mode) { + + // left down - show the selection rectangle: + if (action == SLAGizmoEventType::LeftDown && shift_down) { + if (m_hover_id == -1) { + m_selection_rectangle_active = true; + m_selection_rectangle_start_corner = mouse_position; + m_selection_rectangle_end_corner = mouse_position; + m_canvas_width = m_parent.get_canvas_size().get_width(); + m_canvas_height = m_parent.get_canvas_size().get_height(); + } + else + select_point(m_hover_id); + + return true; + } + + // dragging the selection rectangle: + if (action == SLAGizmoEventType::Dragging && m_selection_rectangle_active) { + m_selection_rectangle_end_corner = mouse_position; + return true; + } + + // mouse up without selection rectangle - place point on the mesh: + if (action == SLAGizmoEventType::LeftUp && !m_selection_rectangle_active && !shift_down) { + if (m_ignore_up_event) { + m_ignore_up_event = false; + return false; + } + + int instance_id = m_parent.get_selection().get_instance_idx(); + if (m_old_instance_id != instance_id) + { + bool something_selected = (m_old_instance_id != -1); + m_old_instance_id = instance_id; + if (something_selected) + return false; + } + if (instance_id == -1) + return false; + + // If there is some selection, don't add new point and deselect everything instead. + if (m_selection_empty) { + try { + std::pair<Vec3f, Vec3f> pos_and_normal = unproject_on_mesh(mouse_position); // don't create anything if this throws + m_editing_mode_cache.emplace_back(sla::SupportPoint(pos_and_normal.first, m_new_point_head_diameter/2.f, false), false, pos_and_normal.second); + m_unsaved_changes = true; + } + catch (...) { // not clicked on object + return true; // prevents deselection of the gizmo by GLCanvas3D + } + } + else + select_point(NoPoints); + + return true; + } + + // left up with selection rectangle - select points inside the rectangle: + if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::ShiftUp) + && m_selection_rectangle_active) { + if (action == SLAGizmoEventType::ShiftUp) + m_ignore_up_event = true; + const Transform3d& instance_matrix = m_model_object->instances[m_active_instance]->get_transformation().get_matrix(); + GLint viewport[4]; + ::glGetIntegerv(GL_VIEWPORT, viewport); + GLdouble modelview_matrix[16]; + ::glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix); + GLdouble projection_matrix[16]; + ::glGetDoublev(GL_PROJECTION_MATRIX, projection_matrix); + + const GLCanvas3D::Selection& selection = m_parent.get_selection(); + const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + double z_offset = volume->get_sla_shift_z(); + + // bounding box created from the rectangle corners - will take care of order of the corners + BoundingBox rectangle(Points{Point(m_selection_rectangle_start_corner.cast<int>()), Point(m_selection_rectangle_end_corner.cast<int>())}); + + const Transform3d& instance_matrix_no_translation = volume->get_instance_transformation().get_matrix(true); + // we'll recover current look direction from the modelview matrix (in world coords)... + Vec3f direction_to_camera(modelview_matrix[2], modelview_matrix[6], modelview_matrix[10]); + // ...and transform it to model coords. + direction_to_camera = (instance_matrix_no_translation.inverse().cast<float>() * direction_to_camera).normalized().eval(); + + // Iterate over all points, check if they're in the rectangle and if so, check that they are not obscured by the mesh: + for (unsigned int i=0; i<m_editing_mode_cache.size(); ++i) { + const sla::SupportPoint &support_point = m_editing_mode_cache[i].support_point; + Vec3f pos = instance_matrix.cast<float>() * support_point.pos; + pos(2) += z_offset; + GLdouble out_x, out_y, out_z; + ::gluProject((GLdouble)pos(0), (GLdouble)pos(1), (GLdouble)pos(2), modelview_matrix, projection_matrix, viewport, &out_x, &out_y, &out_z); + out_y = m_canvas_height - out_y; + + if (rectangle.contains(Point(out_x, out_y))) { + bool is_obscured = false; + // Cast a ray in the direction of the camera and look for intersection with the mesh: + std::vector<igl::Hit> hits; + // Offset the start of the ray to the front of the ball + EPSILON to account for numerical inaccuracies. + if (m_AABB.intersect_ray(m_V, m_F, support_point.pos + direction_to_camera * (support_point.head_front_radius + EPSILON), direction_to_camera, hits)) + // FIXME: the intersection could in theory be behind the camera, but as of now we only have camera direction. + // Also, the threshold is in mesh coordinates, not in actual dimensions. + if (hits.size() > 1 || hits.front().t > 0.001f) + is_obscured = true; + + if (!is_obscured) + select_point(i); + } + } + m_selection_rectangle_active = false; + return true; + } + + if (action == SLAGizmoEventType::Delete) { + // delete key pressed + delete_selected_points(); + return true; + } + + if (action == SLAGizmoEventType::ApplyChanges) { + editing_mode_apply_changes(); + return true; + } + + if (action == SLAGizmoEventType::DiscardChanges) { + editing_mode_discard_changes(); + return true; + } + + if (action == SLAGizmoEventType::RightDown) { + if (m_hover_id != -1) { + select_point(NoPoints); + select_point(m_hover_id); + delete_selected_points(); + return true; + } + return false; + } + + if (action == SLAGizmoEventType::SelectAll) { + select_point(AllPoints); + return true; + } + } + + if (!m_editing_mode) { + if (action == SLAGizmoEventType::AutomaticGeneration) { + auto_generate(); + return true; + } + + if (action == SLAGizmoEventType::ManualEditing) { + switch_to_editing_mode(); + return true; + } + } + + return false; +} + +void GLGizmoSlaSupports::delete_selected_points(bool force) +{ + for (unsigned int idx=0; idx<m_editing_mode_cache.size(); ++idx) { + if (m_editing_mode_cache[idx].selected && (!m_editing_mode_cache[idx].support_point.is_new_island || !m_lock_unique_islands || force)) { + m_editing_mode_cache.erase(m_editing_mode_cache.begin() + (idx--)); + m_unsaved_changes = true; + } + // This should trigger the support generation + // wxGetApp().plater()->reslice_SLA_supports(*m_model_object); + } + + select_point(NoPoints); + + //m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); +} + +void GLGizmoSlaSupports::on_update(const UpdateData& data, const GLCanvas3D::Selection& selection) +{ + if (m_editing_mode && m_hover_id != -1 && data.mouse_pos && (!m_editing_mode_cache[m_hover_id].support_point.is_new_island || !m_lock_unique_islands)) { + std::pair<Vec3f, Vec3f> pos_and_normal; + try { + pos_and_normal = unproject_on_mesh(Vec2d((*data.mouse_pos)(0), (*data.mouse_pos)(1))); + } + catch (...) { return; } + m_editing_mode_cache[m_hover_id].support_point.pos = pos_and_normal.first; + m_editing_mode_cache[m_hover_id].support_point.is_new_island = false; + m_editing_mode_cache[m_hover_id].normal = pos_and_normal.second; + m_unsaved_changes = true; + // Do not update immediately, wait until the mouse is released. + // m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + } +} + +std::vector<const ConfigOption*> GLGizmoSlaSupports::get_config_options(const std::vector<std::string>& keys) const +{ + std::vector<const ConfigOption*> out; + + if (!m_model_object) + return out; + + const DynamicPrintConfig& object_cfg = m_model_object->config; + const DynamicPrintConfig& print_cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; + std::unique_ptr<DynamicPrintConfig> default_cfg = nullptr; + + for (const std::string& key : keys) { + if (object_cfg.has(key)) + out.push_back(object_cfg.option(key)); + else + if (print_cfg.has(key)) + out.push_back(print_cfg.option(key)); + else { // we must get it from defaults + if (default_cfg == nullptr) + default_cfg.reset(DynamicPrintConfig::new_from_defaults_keys(keys)); + out.push_back(default_cfg->option(key)); + } + } + + return out; +} + + +void GLGizmoSlaSupports::update_cache_entry_normal(unsigned int i) const +{ + int idx = 0; + Eigen::Matrix<float, 1, 3> pp = m_editing_mode_cache[i].support_point.pos; + Eigen::Matrix<float, 1, 3> cc; + m_AABB.squared_distance(m_V, m_F, pp, idx, cc); + Vec3f a = (m_V.row(m_F(idx, 1)) - m_V.row(m_F(idx, 0))); + Vec3f b = (m_V.row(m_F(idx, 2)) - m_V.row(m_F(idx, 0))); + m_editing_mode_cache[i].normal = a.cross(b); +} + + + +void GLGizmoSlaSupports::on_render_input_window(float x, float y, float bottom_limit, const GLCanvas3D::Selection& selection) +{ + if (!m_model_object) + return; + + bool first_run = true; // This is a hack to redraw the button when all points are removed, + // so it is not delayed until the background process finishes. +RENDER_AGAIN: + m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); + + const float scaling = m_imgui->get_style_scaling(); + const ImVec2 window_size(285.f * scaling, 300.f * scaling); + ImGui::SetNextWindowPos(ImVec2(x, y - std::max(0.f, y+window_size.y-bottom_limit) )); + ImGui::SetNextWindowSize(ImVec2(window_size)); + + m_imgui->set_next_window_bg_alpha(0.5f); + m_imgui->begin(on_get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + + ImGui::PushItemWidth(100.0f); + + bool force_refresh = false; + bool remove_selected = false; + bool remove_all = false; + + if (m_editing_mode) { + m_imgui->text(_(L("Left mouse click - add point"))); + m_imgui->text(_(L("Right mouse click - remove point"))); + m_imgui->text(_(L("Shift + Left (+ drag) - select point(s)"))); + m_imgui->text(" "); // vertical gap + + float diameter_upper_cap = static_cast<ConfigOptionFloat*>(wxGetApp().preset_bundle->sla_prints.get_edited_preset().config.option("support_pillar_diameter"))->value; + if (m_new_point_head_diameter > diameter_upper_cap) + m_new_point_head_diameter = diameter_upper_cap; + + m_imgui->text(_(L("Head diameter: "))); + ImGui::SameLine(); + if (ImGui::SliderFloat("", &m_new_point_head_diameter, 0.1f, diameter_upper_cap, "%.1f")) { + // value was changed + for (auto& cache_entry : m_editing_mode_cache) + if (cache_entry.selected) { + cache_entry.support_point.head_front_radius = m_new_point_head_diameter / 2.f; + m_unsaved_changes = true; + } + } + + bool changed = m_lock_unique_islands; + m_imgui->checkbox(_(L("Lock supports under new islands")), m_lock_unique_islands); + force_refresh |= changed != m_lock_unique_islands; + + m_imgui->disabled_begin(m_selection_empty); + remove_selected = m_imgui->button(_(L("Remove selected points"))); + m_imgui->disabled_end(); + + m_imgui->disabled_begin(m_editing_mode_cache.empty()); + remove_all = m_imgui->button(_(L("Remove all points"))); + m_imgui->disabled_end(); + + m_imgui->text(" "); // vertical gap + + if (m_imgui->button(_(L("Apply changes")))) { + editing_mode_apply_changes(); + force_refresh = true; + } + ImGui::SameLine(); + bool discard_changes = m_imgui->button(_(L("Discard changes"))); + if (discard_changes) { + editing_mode_discard_changes(); + force_refresh = true; + } + } + else { // not in editing mode: + ImGui::PushItemWidth(100.0f); + m_imgui->text(_(L("Minimal points distance: "))); + ImGui::SameLine(); + + std::vector<const ConfigOption*> opts = get_config_options({"support_points_density_relative", "support_points_minimal_distance"}); + float density = static_cast<const ConfigOptionInt*>(opts[0])->value; + float minimal_point_distance = static_cast<const ConfigOptionFloat*>(opts[1])->value; + + bool value_changed = ImGui::SliderFloat("", &minimal_point_distance, 0.f, 20.f, "%.f mm"); + if (value_changed) + m_model_object->config.opt<ConfigOptionFloat>("support_points_minimal_distance", true)->value = minimal_point_distance; + + m_imgui->text(_(L("Support points density: "))); + ImGui::SameLine(); + if (ImGui::SliderFloat(" ", &density, 0.f, 200.f, "%.f %%")) { + value_changed = true; + m_model_object->config.opt<ConfigOptionInt>("support_points_density_relative", true)->value = (int)density; + } + + if (value_changed) { // Update side panel + wxTheApp->CallAfter([]() { + wxGetApp().obj_settings()->UpdateAndShow(true); + wxGetApp().obj_list()->update_settings_items(); + }); + } + + bool generate = m_imgui->button(_(L("Auto-generate points [A]"))); + + if (generate) + auto_generate(); + + m_imgui->text(""); + if (m_imgui->button(_(L("Manual editing [M]")))) + switch_to_editing_mode(); + + m_imgui->disabled_begin(m_editing_mode_cache.empty()); + remove_all = m_imgui->button(_(L("Remove all points"))); + m_imgui->disabled_end(); + + m_imgui->text(""); + + m_imgui->text(m_model_object->sla_points_status == sla::PointsStatus::None ? "No points (will be autogenerated)" : + (m_model_object->sla_points_status == sla::PointsStatus::AutoGenerated ? "Autogenerated points (no modifications)" : + (m_model_object->sla_points_status == sla::PointsStatus::UserModified ? "User-modified points" : + (m_model_object->sla_points_status == sla::PointsStatus::Generating ? "Generation in progress..." : "UNKNOWN STATUS")))); + } + + m_imgui->end(); + + if (m_editing_mode != m_old_editing_state) { // user toggled between editing/non-editing mode + m_parent.toggle_sla_auxiliaries_visibility(!m_editing_mode); + force_refresh = true; + } + m_old_editing_state = m_editing_mode; + + if (remove_selected || remove_all) { + force_refresh = false; + m_parent.set_as_dirty(); + if (remove_all) + select_point(AllPoints); + delete_selected_points(remove_all); + if (remove_all && !m_editing_mode) + editing_mode_apply_changes(); + if (first_run) { + first_run = false; + goto RENDER_AGAIN; + } + } + + if (force_refresh) + m_parent.set_as_dirty(); +} + +bool GLGizmoSlaSupports::on_is_activable(const GLCanvas3D::Selection& selection) const +{ + if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA + || !selection.is_from_single_instance()) + return false; + + // Check that none of the selected volumes is outside. + const GLCanvas3D::Selection::IndicesList& list = selection.get_volume_idxs(); + for (const auto& idx : list) + if (selection.get_volume(idx)->is_outside) + return false; + + return true; +} + +bool GLGizmoSlaSupports::on_is_selectable() const +{ + return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA); +} + +std::string GLGizmoSlaSupports::on_get_name() const +{ + return L("SLA Support Points [L]"); +} + +void GLGizmoSlaSupports::on_set_state() +{ + if (m_state == On && m_old_state != On) { // the gizmo was just turned on + if (is_mesh_update_necessary()) + update_mesh(); + + // we'll now reload support points: + if (m_model_object) + editing_mode_reload_cache(); + + m_parent.toggle_model_objects_visibility(false); + if (m_model_object) + m_parent.toggle_model_objects_visibility(true, m_model_object, m_active_instance); + + // Set default head diameter from config. + const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; + m_new_point_head_diameter = static_cast<const ConfigOptionFloat*>(cfg.option("support_head_front_diameter"))->value; + } + if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off + if (m_model_object) { + if (m_unsaved_changes) { + wxMessageDialog dlg(GUI::wxGetApp().plater(), _(L("Do you want to save your manually edited support points ?\n")), + _(L("Save changes?")), wxICON_QUESTION | wxYES | wxNO); + if (dlg.ShowModal() == wxID_YES) + editing_mode_apply_changes(); + else + editing_mode_discard_changes(); + } + } + + m_parent.toggle_model_objects_visibility(true); + m_editing_mode = false; // so it is not active next time the gizmo opens + m_editing_mode_cache.clear(); + } + m_old_state = m_state; +} + + + +void GLGizmoSlaSupports::on_start_dragging(const GLCanvas3D::Selection& selection) +{ + if (m_hover_id != -1) { + select_point(NoPoints); + select_point(m_hover_id); + } +} + + + +void GLGizmoSlaSupports::select_point(int i) +{ + if (i == AllPoints || i == NoPoints) { + for (auto& point_and_selection : m_editing_mode_cache) + point_and_selection.selected = ( i == AllPoints ); + m_selection_empty = (i == NoPoints); + + if (i == AllPoints) + m_new_point_head_diameter = m_editing_mode_cache[0].support_point.head_front_radius * 2.f; + } + else { + m_editing_mode_cache[i].selected = true; + m_selection_empty = false; + m_new_point_head_diameter = m_editing_mode_cache[i].support_point.head_front_radius * 2.f; + } +} + + + +void GLGizmoSlaSupports::editing_mode_discard_changes() +{ + m_editing_mode_cache.clear(); + for (const sla::SupportPoint& point : m_model_object->sla_support_points) + m_editing_mode_cache.emplace_back(point, false); + m_editing_mode = false; + m_unsaved_changes = false; +} + + + +void GLGizmoSlaSupports::editing_mode_apply_changes() +{ + // If there are no changes, don't touch the front-end. The data in the cache could have been + // taken from the backend and copying them to ModelObject would needlessly invalidate them. + if (m_unsaved_changes) { + m_model_object->sla_points_status = sla::PointsStatus::UserModified; + m_model_object->sla_support_points.clear(); + for (const CacheEntry& cache_entry : m_editing_mode_cache) + m_model_object->sla_support_points.push_back(cache_entry.support_point); + + // Recalculate support structures once the editing mode is left. + // m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + // m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + wxGetApp().plater()->reslice_SLA_supports(*m_model_object); + } + m_editing_mode = false; + m_unsaved_changes = false; +} + + + +void GLGizmoSlaSupports::editing_mode_reload_cache() +{ + m_editing_mode_cache.clear(); + for (const sla::SupportPoint& point : m_model_object->sla_support_points) + m_editing_mode_cache.emplace_back(point, false); + + m_unsaved_changes = false; +} + + + +void GLGizmoSlaSupports::get_data_from_backend() +{ + for (const SLAPrintObject* po : m_parent.sla_print()->objects()) { + if (po->model_object()->id() == m_model_object->id() && po->is_step_done(slaposSupportPoints)) { + m_editing_mode_cache.clear(); + const std::vector<sla::SupportPoint>& points = po->get_support_points(); + auto mat = po->trafo().inverse().cast<float>(); + for (unsigned int i=0; i<points.size();++i) + m_editing_mode_cache.emplace_back(sla::SupportPoint(mat * points[i].pos, points[i].head_front_radius, points[i].is_new_island), false); + + if (m_model_object->sla_points_status != sla::PointsStatus::UserModified) + m_model_object->sla_points_status = sla::PointsStatus::AutoGenerated; + + break; + } + } + m_unsaved_changes = false; + + // We don't copy the data into ModelObject, as this would stop the background processing. +} + + + +void GLGizmoSlaSupports::auto_generate() +{ + wxMessageDialog dlg(GUI::wxGetApp().plater(), _(L( + "Autogeneration will erase all manually edited points.\n\n" + "Are you sure you want to do it?\n" + )), _(L("Warning")), wxICON_WARNING | wxYES | wxNO); + + if (m_model_object->sla_points_status != sla::PointsStatus::UserModified || m_editing_mode_cache.empty() || dlg.ShowModal() == wxID_YES) { + m_model_object->sla_support_points.clear(); + m_model_object->sla_points_status = sla::PointsStatus::Generating; + m_editing_mode_cache.clear(); + wxGetApp().plater()->reslice_SLA_supports(*m_model_object); + } +} + + + +void GLGizmoSlaSupports::switch_to_editing_mode() +{ + if (m_model_object->sla_points_status != sla::PointsStatus::AutoGenerated) + editing_mode_reload_cache(); + m_unsaved_changes = false; + m_editing_mode = true; +} + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp new file mode 100644 index 000000000..492bb85ca --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -0,0 +1,128 @@ +#ifndef slic3r_GLGizmoSlaSupports_hpp_ +#define slic3r_GLGizmoSlaSupports_hpp_ + +#include "GLGizmoBase.hpp" + +// There is an L function in igl that would be overridden by our localization macro - let's undefine it... +#undef L +#include <igl/AABB.h> +#include "slic3r/GUI/I18N.hpp" // ...and redefine again when we are done with the igl code + +#include "libslic3r/SLA/SLACommon.hpp" +#include "libslic3r/SLAPrint.hpp" + + +namespace Slic3r { +namespace GUI { + + +class GLGizmoSlaSupports : public GLGizmoBase +{ +private: + ModelObject* m_model_object = nullptr; + ModelObject* m_old_model_object = nullptr; + int m_active_instance = -1; + int m_old_instance_id = -1; + std::pair<Vec3f, Vec3f> unproject_on_mesh(const Vec2d& mouse_pos); + + const float RenderPointScale = 1.f; + + GLUquadricObj* m_quadric; + Eigen::MatrixXf m_V; // vertices + Eigen::MatrixXi m_F; // facets indices + igl::AABB<Eigen::MatrixXf,3> m_AABB; + + struct SourceDataSummary { + Geometry::Transformation transformation; + }; + + class CacheEntry { + public: + CacheEntry(const sla::SupportPoint& point, bool sel, const Vec3f& norm = Vec3f::Zero()) : + support_point(point), selected(sel), normal(norm) {} + + sla::SupportPoint support_point; + bool selected; // whether the point is selected + Vec3f normal; + }; + + // This holds information to decide whether recalculation is necessary: + SourceDataSummary m_source_data; + + mutable Vec3d m_starting_center; + +public: +#if ENABLE_SVG_ICONS + GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); +#else + GLGizmoSlaSupports(GLCanvas3D& parent, unsigned int sprite_id); +#endif // ENABLE_SVG_ICONS + virtual ~GLGizmoSlaSupports(); + void set_sla_support_data(ModelObject* model_object, const GLCanvas3D::Selection& selection); + bool mouse_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down); + void delete_selected_points(bool force = false); + std::pair<float, float> get_sla_clipping_plane() const; + +private: + bool on_init(); + void on_update(const UpdateData& data, const GLCanvas3D::Selection& selection); + virtual void on_render(const GLCanvas3D::Selection& selection) const; + virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const; + + void render_selection_rectangle() const; + void render_points(const GLCanvas3D::Selection& selection, bool picking = false) const; + bool is_mesh_update_necessary() const; + void update_mesh(); + void update_cache_entry_normal(unsigned int i) const; + + bool m_lock_unique_islands = false; + bool m_editing_mode = false; // Is editing mode active? + bool m_old_editing_state = false; // To keep track of whether the user toggled between the modes (needed for imgui refreshes). + float m_new_point_head_diameter; // Size of a new point. + float m_minimal_point_distance = 20.f; + float m_density = 100.f; + mutable std::vector<CacheEntry> m_editing_mode_cache; // a support point and whether it is currently selected + float m_clipping_plane_distance = 0.f; + + bool m_selection_rectangle_active = false; + Vec2d m_selection_rectangle_start_corner; + Vec2d m_selection_rectangle_end_corner; + bool m_ignore_up_event = false; + bool m_combo_box_open = false; // To ensure proper rendering of the imgui combobox. + bool m_unsaved_changes = false; // Are there unsaved changes in manual mode? + bool m_selection_empty = true; + EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state) + int m_canvas_width; + int m_canvas_height; + + std::vector<const ConfigOption*> get_config_options(const std::vector<std::string>& keys) const; + + // Methods that do the model_object and editing cache synchronization, + // editing mode selection, etc: + enum { + AllPoints = -2, + NoPoints, + }; + void select_point(int i); + void editing_mode_apply_changes(); + void editing_mode_discard_changes(); + void editing_mode_reload_cache(); + void get_data_from_backend(); + void auto_generate(); + void switch_to_editing_mode(); + +protected: + void on_set_state() override; + void on_start_dragging(const GLCanvas3D::Selection& selection) override; + virtual void on_render_input_window(float x, float y, float bottom_limit, const GLCanvas3D::Selection& selection) override; + + virtual std::string on_get_name() const; + virtual bool on_is_activable(const GLCanvas3D::Selection& selection) const; + virtual bool on_is_selectable() const; +}; + + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GLGizmoSlaSupports_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmos.hpp b/src/slic3r/GUI/Gizmos/GLGizmos.hpp new file mode 100644 index 000000000..8c5e25669 --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmos.hpp @@ -0,0 +1,11 @@ +#ifndef slic3r_GLGizmos_hpp_ +#define slic3r_GLGizmos_hpp_ + +#include "slic3r/GUI/Gizmos/GLGizmoMove.hpp" +#include "slic3r/GUI/Gizmos/GLGizmoScale.hpp" +#include "slic3r/GUI/Gizmos/GLGizmoRotate.hpp" +#include "slic3r/GUI/Gizmos/GLGizmoFlatten.hpp" +#include "slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp" +#include "slic3r/GUI/Gizmos/GLGizmoCut.hpp" + +#endif //slic3r_GLGizmos_hpp_ diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 9e75eb2ff..0050b6463 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -107,11 +107,11 @@ bool ImGuiWrapper::update_mouse_data(wxMouseEvent& evt) ImGuiIO& io = ImGui::GetIO(); io.MousePos = ImVec2((float)evt.GetX(), (float)evt.GetY()); - io.MouseDown[0] = evt.LeftDown(); - io.MouseDown[1] = evt.RightDown(); - io.MouseDown[2] = evt.MiddleDown(); + io.MouseDown[0] = evt.LeftIsDown(); + io.MouseDown[1] = evt.RightIsDown(); + io.MouseDown[2] = evt.MiddleIsDown(); - unsigned buttons = (evt.LeftDown() ? 1 : 0) | (evt.RightDown() ? 2 : 0) | (evt.MiddleDown() ? 4 : 0); + unsigned buttons = (evt.LeftIsDown() ? 1 : 0) | (evt.RightIsDown() ? 2 : 0) | (evt.MiddleIsDown() ? 4 : 0); m_mouse_buttons = buttons; new_frame(); @@ -441,6 +441,10 @@ void ImGuiWrapper::init_style() set_color(ImGuiCol_Header, COL_ORANGE_DARK); set_color(ImGuiCol_HeaderHovered, COL_ORANGE_LIGHT); set_color(ImGuiCol_HeaderActive, COL_ORANGE_LIGHT); + + // Slider + set_color(ImGuiCol_SliderGrab, COL_ORANGE_DARK); + set_color(ImGuiCol_SliderGrabActive, COL_ORANGE_LIGHT); } void ImGuiWrapper::render_draw_data(ImDrawData *draw_data) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index cddf081eb..d69480b28 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -56,7 +56,8 @@ wxFrame(NULL, wxID_ANY, SLIC3R_BUILD, wxDefaultPosition, wxDefaultSize, wxDEFAUL // initialize default width_unit according to the width of the one symbol ("x") of the current system font const wxSize size = GetTextExtent("m"); - wxGetApp().set_em_unit(size.x-1); +// wxGetApp().set_em_unit(size.x-1); + wxGetApp().set_em_unit(std::max<size_t>(10, size.x - 1)); // initialize tabpanel and menubar init_tabpanel(); @@ -76,12 +77,14 @@ wxFrame(NULL, wxID_ANY, SLIC3R_BUILD, wxDefaultPosition, wxDefaultSize, wxDEFAUL sizer->SetSizeHints(this); SetSizer(sizer); Fit(); + + const wxSize min_size = wxSize(76*wxGetApp().em_unit(), 49*wxGetApp().em_unit()); #ifdef __APPLE__ // Using SetMinSize() on Mac messes up the window position in some cases // cf. https://groups.google.com/forum/#!topic/wx-users/yUKPBBfXWO0 - SetSize(wxSize(760, 490)); + SetSize(min_size/*wxSize(760, 490)*/); #else - SetMinSize(wxSize(760, 490)); + SetMinSize(min_size/*wxSize(760, 490)*/); SetSize(GetMinSize()); #endif Layout(); @@ -93,6 +96,12 @@ wxFrame(NULL, wxID_ANY, SLIC3R_BUILD, wxDefaultPosition, wxDefaultSize, wxDEFAUL return; } + // Weird things happen as the Paint messages are floating around the windows being destructed. + // Avoid the Paint messages by hiding the main window. + // Also the application closes much faster without these unnecessary screen refreshes. + // In addition, there were some crashes due to the Paint events sent to already destructed windows. + this->Show(false); + // Save the slic3r.ini.Usually the ini file is saved from "on idle" callback, // but in rare cases it may not have been called yet. wxGetApp().app_config->save(); @@ -124,7 +133,9 @@ wxFrame(NULL, wxID_ANY, SLIC3R_BUILD, wxDefaultPosition, wxDefaultSize, wxDEFAUL void MainFrame::init_tabpanel() { - m_tabpanel = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL); + // wxNB_NOPAGETHEME: Disable Windows Vista theme for the Notebook background. The theme performance is terrible on Windows 10 + // with multiple high resolution displays connected. + m_tabpanel = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME); m_tabpanel->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, [this](wxEvent&) { auto panel = m_tabpanel->GetCurrentPage(); @@ -881,9 +892,10 @@ void MainFrame::on_config_changed(DynamicPrintConfig* config) const // Update the UI based on the current preferences. void MainFrame::update_ui_from_settings() { - bool bp_on = wxGetApp().app_config->get("background_processing") == "1"; + const bool bp_on = wxGetApp().app_config->get("background_processing") == "1"; // m_menu_item_reslice_now->Enable(!bp_on); m_plater->sidebar().show_reslice(!bp_on); + m_plater->sidebar().show_export(bp_on); m_plater->sidebar().Layout(); if (m_plater) m_plater->update_ui_from_settings(); diff --git a/src/slic3r/GUI/MsgDialog.cpp b/src/slic3r/GUI/MsgDialog.cpp index 176d19fb4..961888455 100644 --- a/src/slic3r/GUI/MsgDialog.cpp +++ b/src/slic3r/GUI/MsgDialog.cpp @@ -24,12 +24,11 @@ namespace GUI { MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, wxWindowID button_id) : -// MsgDialog(parent, title, headline, wxBitmap(from_u8(Slic3r::var("Slic3r_192px.png")), wxBITMAP_TYPE_PNG), button_id) MsgDialog(parent, title, headline, create_scaled_bitmap("Slic3r_192px.png"), button_id) {} MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, wxBitmap bitmap, wxWindowID button_id) : - wxDialog(parent, wxID_ANY, title), + wxDialog(parent, wxID_ANY, title, wxDefaultPosition, wxDefaultSize, wxRESIZE_BORDER), boldfont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)), content_sizer(new wxBoxSizer(wxVERTICAL)), btn_sizer(new wxBoxSizer(wxHORIZONTAL)) @@ -70,7 +69,6 @@ MsgDialog::~MsgDialog() {} ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &msg) : MsgDialog(parent, _(L("Slic3r error")), _(L("Slic3r has encountered an error")), -// wxBitmap(from_u8(Slic3r::var("Slic3r_192px_grayscale.png")), wxBITMAP_TYPE_PNG), create_scaled_bitmap("Slic3r_192px_grayscale.png"), wxID_NONE) , msg(msg) diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 0e9fcd75e..5e9c9ac30 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -23,18 +23,18 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co // is the normal type. if (opt.gui_type.compare("select") == 0) { } else if (opt.gui_type.compare("select_open") == 0) { - m_fields.emplace(id, std::move(Choice::Create<Choice>(parent(), opt, id))); + m_fields.emplace(id, std::move(Choice::Create<Choice>(this->ctrl_parent(), opt, id))); } else if (opt.gui_type.compare("color") == 0) { - m_fields.emplace(id, std::move(ColourPicker::Create<ColourPicker>(parent(), opt, id))); + m_fields.emplace(id, std::move(ColourPicker::Create<ColourPicker>(this->ctrl_parent(), opt, id))); } else if (opt.gui_type.compare("f_enum_open") == 0 || opt.gui_type.compare("i_enum_open") == 0 || opt.gui_type.compare("i_enum_closed") == 0) { - m_fields.emplace(id, std::move(Choice::Create<Choice>(parent(), opt, id))); + m_fields.emplace(id, std::move(Choice::Create<Choice>(this->ctrl_parent(), opt, id))); } else if (opt.gui_type.compare("slider") == 0) { - m_fields.emplace(id, std::move(SliderCtrl::Create<SliderCtrl>(parent(), opt, id))); + m_fields.emplace(id, std::move(SliderCtrl::Create<SliderCtrl>(this->ctrl_parent(), opt, id))); } else if (opt.gui_type.compare("i_spin") == 0) { // Spinctrl } else if (opt.gui_type.compare("legend") == 0) { // StaticText - m_fields.emplace(id, std::move(StaticText::Create<StaticText>(parent(), opt, id))); + m_fields.emplace(id, std::move(StaticText::Create<StaticText>(this->ctrl_parent(), opt, id))); } else { switch (opt.type) { case coFloatOrPercent: @@ -44,21 +44,21 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co case coPercents: case coString: case coStrings: - m_fields.emplace(id, std::move(TextCtrl::Create<TextCtrl>(parent(), opt, id))); + m_fields.emplace(id, std::move(TextCtrl::Create<TextCtrl>(this->ctrl_parent(), opt, id))); break; case coBool: case coBools: - m_fields.emplace(id, std::move(CheckBox::Create<CheckBox>(parent(), opt, id))); + m_fields.emplace(id, std::move(CheckBox::Create<CheckBox>(this->ctrl_parent(), opt, id))); break; case coInt: case coInts: - m_fields.emplace(id, std::move(SpinCtrl::Create<SpinCtrl>(parent(), opt, id))); + m_fields.emplace(id, std::move(SpinCtrl::Create<SpinCtrl>(this->ctrl_parent(), opt, id))); break; case coEnum: - m_fields.emplace(id, std::move(Choice::Create<Choice>(parent(), opt, id))); + m_fields.emplace(id, std::move(Choice::Create<Choice>(this->ctrl_parent(), opt, id))); break; case coPoints: - m_fields.emplace(id, std::move(PointCtrl::Create<PointCtrl>(parent(), opt, id))); + m_fields.emplace(id, std::move(PointCtrl::Create<PointCtrl>(this->ctrl_parent(), opt, id))); break; case coNone: break; default: @@ -119,7 +119,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n return; } if (line.widget != nullptr) { - sizer->Add(line.widget(m_parent), 0, wxEXPAND | wxALL, wxOSX ? 0 : 15); + sizer->Add(line.widget(this->ctrl_parent()), 0, wxEXPAND | wxALL, wxOSX ? 0 : 15); return; } } @@ -167,7 +167,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n // if we have an extra column, build it if (extra_column) - grid_sizer->Add(extra_column(parent(), line), 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 3); + grid_sizer->Add(extra_column(this->ctrl_parent(), line), 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 3); // Build a label if we have it wxStaticText* label=nullptr; @@ -179,18 +179,21 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n // Text is properly aligned only when Ellipsize is checked. label_style |= staticbox ? 0 : wxST_ELLIPSIZE_END; #endif /* __WXGTK__ */ - label = new wxStaticText(parent(), wxID_ANY, line.label + (line.label.IsEmpty() ? "" : ": "), + label = new wxStaticText(this->ctrl_parent(), wxID_ANY, line.label + (line.label.IsEmpty() ? "" : ": "), wxDefaultPosition, wxSize(label_width, -1), label_style); + label->SetBackgroundStyle(wxBG_STYLE_PAINT); label->SetFont(label_font); label->Wrap(label_width); // avoid a Linux/GTK bug if (!line.near_label_widget) grid_sizer->Add(label, 0, (staticbox ? 0 : wxALIGN_RIGHT | wxRIGHT) | wxALIGN_CENTER_VERTICAL, line.label.IsEmpty() ? 0 : 5); + else if (line.near_label_widget && line.label.IsEmpty()) + grid_sizer->Add(line.near_label_widget(this->ctrl_parent()), 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 7); else { // If we're here, we have some widget near the label // so we need a horizontal sizer to arrange these things auto sizer = new wxBoxSizer(wxHORIZONTAL); grid_sizer->Add(sizer, 0, wxEXPAND | (staticbox ? wxALL : wxBOTTOM | wxTOP | wxLEFT), staticbox ? 0 : 1); - sizer->Add(line.near_label_widget(parent()), 0, wxRIGHT, 7); + sizer->Add(line.near_label_widget(this->ctrl_parent()), 0, wxRIGHT, 7); sizer->Add(label, 0, (staticbox ? 0 : wxALIGN_RIGHT | wxRIGHT) | wxALIGN_CENTER_VERTICAL, 5); } if (line.label_tooltip.compare("") != 0) @@ -201,7 +204,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n *full_Label = label; // Initiate the pointer to the control of the full label, if we need this one. // If there's a widget, build it and add the result to the sizer. if (line.widget != nullptr) { - auto wgt = line.widget(parent()); + auto wgt = line.widget(this->ctrl_parent()); // If widget doesn't have label, don't use border grid_sizer->Add(wgt, 0, wxEXPAND | wxBOTTOM | wxTOP, (wxOSX || line.label.IsEmpty()) ? 0 : 5); return; @@ -237,7 +240,8 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n wxString str_label = (option.label == "Top" || option.label == "Bottom") ? _CTX(option.label, "Layers") : _(option.label); - label = new wxStaticText(parent(), wxID_ANY, str_label + ": ", wxDefaultPosition, wxDefaultSize); + label = new wxStaticText(this->ctrl_parent(), wxID_ANY, str_label + ": ", wxDefaultPosition, wxDefaultSize); + label->SetBackgroundStyle(wxBG_STYLE_PAINT); label->SetFont(label_font); sizer_tmp->Add(label, 0, /*wxALIGN_RIGHT |*/ wxALIGN_CENTER_VERTICAL, 0); } @@ -262,8 +266,9 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n // add sidetext if any if (option.sidetext != "") { - auto sidetext = new wxStaticText( parent(), wxID_ANY, _(option.sidetext), wxDefaultPosition, + auto sidetext = new wxStaticText( this->ctrl_parent(), wxID_ANY, _(option.sidetext), wxDefaultPosition, wxSize(sidetext_width, -1)/*wxDefaultSize*/, wxALIGN_LEFT); + sidetext->SetBackgroundStyle(wxBG_STYLE_PAINT); sidetext->SetFont(sidetext_font); sizer_tmp->Add(sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 4); field->set_side_text_ptr(sidetext); @@ -271,7 +276,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n // add side widget if any if (opt.side_widget != nullptr) { - sizer_tmp->Add(opt.side_widget(parent())/*!.target<wxWindow>()*/, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 1); //! requires verification + sizer_tmp->Add(opt.side_widget(this->ctrl_parent())/*!.target<wxWindow>()*/, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 1); //! requires verification } if (opt.opt_id != option_set.back().opt_id) //! istead of (opt != option_set.back()) @@ -287,11 +292,11 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n // extra widget for non-staticbox option group (like for the frequently used parameters on the sidebar) should be wxALIGN_RIGHT const auto v_sizer = new wxBoxSizer(wxVERTICAL); sizer->Add(v_sizer, 1, wxEXPAND); - v_sizer->Add(extra_widget(parent()), 0, wxALIGN_RIGHT); + v_sizer->Add(extra_widget(this->ctrl_parent()), 0, wxALIGN_RIGHT); return; } - sizer->Add(extra_widget(parent())/*!.target<wxWindow>()*/, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 4); //! requires verification + sizer->Add(extra_widget(this->ctrl_parent())/*!.target<wxWindow>()*/, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 4); //! requires verification } } diff --git a/src/slic3r/GUI/OptionsGroup.hpp b/src/slic3r/GUI/OptionsGroup.hpp index d9ff6a01a..661aadbd7 100644 --- a/src/slic3r/GUI/OptionsGroup.hpp +++ b/src/slic3r/GUI/OptionsGroup.hpp @@ -112,6 +112,10 @@ public: } #endif /* __WXGTK__ */ + wxWindow* ctrl_parent() const { + return this->stb ? (wxWindow*)this->stb : this->parent(); + } + void append_line(const Line& line, wxStaticText** full_Label = nullptr); Line create_single_option_line(const Option& option) const; void append_single_option_line(const Option& option) { append_line(create_single_option_line(option)); } @@ -161,8 +165,10 @@ public: staticbox(title!=""), extra_column(extra_clmn) { if (staticbox) { stb = new wxStaticBox(_parent, wxID_ANY, title); + stb->SetBackgroundStyle(wxBG_STYLE_PAINT); stb->SetFont(wxGetApp().bold_font()); - } + } else + stb = nullptr; sizer = (staticbox ? new wxStaticBoxSizer(stb, wxVERTICAL) : new wxBoxSizer(wxVERTICAL)); auto num_columns = 1U; if (label_width != 0) num_columns++; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 40f18598f..56dc323b3 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -50,6 +50,7 @@ #include "GLToolbar.hpp" #include "GUI_Preview.hpp" #include "3DBed.hpp" +#include "Camera.hpp" #include "Tab.hpp" #include "PresetBundle.hpp" #include "BackgroundSlicingProcess.hpp" @@ -364,20 +365,20 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : Line line = Line { "", "" }; - ConfigOptionDef def; - def.label = L("Supports"); - def.type = coStrings; - def.gui_type = "select_open"; - def.tooltip = L("Select what kind of support do you need"); - def.enum_labels.push_back(L("None")); - def.enum_labels.push_back(L("Support on build plate only")); - def.enum_labels.push_back(L("Everywhere")); - const std::string selection = !config->opt_bool("support_material") ? - "None" : config->opt_bool("support_material_buildplate_only") ? - "Support on build plate only" : - "Everywhere"; - def.default_value = new ConfigOptionStrings{ selection }; - Option option = Option(def, "support"); + ConfigOptionDef support_def; + support_def.label = L("Supports"); + support_def.type = coStrings; + support_def.gui_type = "select_open"; + support_def.tooltip = L("Select what kind of support do you need"); + support_def.enum_labels.push_back(L("None")); + support_def.enum_labels.push_back(L("Support on build plate only")); + support_def.enum_labels.push_back(L("Everywhere")); + std::string selection = !config->opt_bool("support_material") ? + "None" : config->opt_bool("support_material_buildplate_only") ? + "Support on build plate only" : + "Everywhere"; + support_def.default_value = new ConfigOptionStrings{ selection }; + Option option = Option(support_def, "support"); option.opt.full_width = true; line.append_option(option); m_og->append_line(line); @@ -392,6 +393,7 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : line.append_option(option); m_brim_width = config->opt_float("brim_width"); + ConfigOptionDef def; def.label = L("Brim"); def.type = coBool; def.tooltip = L("This flag enables the brim that will be printed around each object on the first layer."); @@ -427,6 +429,7 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : m_og->append_line(line); + // Frequently changed parameters for SLA_technology m_og_sla = std::make_shared<ConfigOptionsGroup>(parent, ""); DynamicPrintConfig* config_sla = &wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; @@ -437,20 +440,43 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : Tab* tab = wxGetApp().get_tab(Preset::TYPE_SLA_PRINT); if (!tab) return; - tab->set_value(opt_key, value); + if (opt_key == "pad_enable") { + tab->set_value(opt_key, value); + tab->update(); + } + else //(opt_key == "support") + { + DynamicPrintConfig new_conf = *config_sla; + const wxString& selection = boost::any_cast<wxString>(value); - DynamicPrintConfig new_conf = *config_sla; - new_conf.set_key_value(opt_key, new ConfigOptionBool(boost::any_cast<bool>(value))); - tab->load_config(new_conf); - tab->update_dirty(); - }; + const bool supports_enable = selection == _("None") ? false : true; + new_conf.set_key_value("supports_enable", new ConfigOptionBool(supports_enable)); + if (selection == _("Everywhere")) + new_conf.set_key_value("support_buildplate_only", new ConfigOptionBool(false)); + else if (selection == _("Support on build plate only")) + new_conf.set_key_value("support_buildplate_only", new ConfigOptionBool(true)); + + tab->load_config(new_conf); + } + + tab->update_dirty(); + }; line = Line{ "", "" }; - option = m_og_sla->get_option("supports_enable"); - option.opt.sidetext = " "; + selection = !config_sla->opt_bool("supports_enable") ? + "None" : config_sla->opt_bool("support_buildplate_only") ? + "Support on build plate only" : + "Everywhere"; + support_def.default_value = new ConfigOptionStrings{ selection }; + option = Option(support_def, "support"); + option.opt.full_width = true; line.append_option(option); + m_og_sla->append_line(line); + + + line = Line{ "", "" }; option = m_og_sla->get_option("pad_enable"); option.opt.sidetext = " "; @@ -487,11 +513,18 @@ ConfigOptionsGroup* FreqChangedParams::get_og(const bool is_fff) // Sidebar / private +enum class ActionButtonType : int { + abReslice, + abExport, + abSendGCode +}; + struct Sidebar::priv { Plater *plater; wxScrolledWindow *scrolled; + wxPanel* presets_panel; // Used for MSW better layouts PrusaModeSizer *mode_sizer; wxFlexGridSizer *sizer_presets; @@ -523,8 +556,6 @@ void Sidebar::priv::show_preset_comboboxes() { const bool showSLA = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA; - wxWindowUpdateLocker noUpdates_scrolled(scrolled->GetParent()); - for (size_t i = 0; i < 4; ++i) sizer_presets->Show(i, !showSLA); @@ -548,6 +579,7 @@ Sidebar::Sidebar(Plater *parent) p->scrolled = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxSize(40 * wxGetApp().em_unit(), -1)); p->scrolled->SetScrollbars(0, 20, 1, 2); + // Sizer in the scrolled area auto *scrolled_sizer = new wxBoxSizer(wxVERTICAL); p->scrolled->SetSizer(scrolled_sizer); @@ -559,12 +591,25 @@ Sidebar::Sidebar(Plater *parent) p->sizer_presets = new wxFlexGridSizer(10, 1, 1, 2); p->sizer_presets->AddGrowableCol(0, 1); p->sizer_presets->SetFlexibleDirection(wxBOTH); + + bool is_msw = false; +#ifdef __WINDOWS__ + p->scrolled->SetDoubleBuffered(true); + + p->presets_panel = new wxPanel(p->scrolled, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + p->presets_panel->SetSizer(p->sizer_presets); + + is_msw = true; +#else + p->presets_panel = p->scrolled; +#endif //__WINDOWS__ + p->sizer_filaments = new wxBoxSizer(wxVERTICAL); auto init_combo = [this](PresetComboBox **combo, wxString label, Preset::Type preset_type, bool filament) { - auto *text = new wxStaticText(p->scrolled, wxID_ANY, label+" :"); + auto *text = new wxStaticText(p->presets_panel, wxID_ANY, label + " :"); text->SetFont(wxGetApp().small_font()); - *combo = new PresetComboBox(p->scrolled, preset_type); + *combo = new PresetComboBox(p->presets_panel, preset_type); auto *sizer_presets = this->p->sizer_presets; auto *sizer_filaments = this->p->sizer_filaments; @@ -613,9 +658,7 @@ Sidebar::Sidebar(Plater *parent) p->object_settings->Hide(); p->sizer_params->Add(p->object_settings->get_sizer(), 0, wxEXPAND | wxTOP, margin_5); - wxBitmap arrow_up(GUI::from_u8(Slic3r::var("brick_go.png")), wxBITMAP_TYPE_PNG); p->btn_send_gcode = new wxButton(this, wxID_ANY, _(L("Send to printer"))); - p->btn_send_gcode->SetBitmap(arrow_up); p->btn_send_gcode->SetFont(wxGetApp().bold_font()); p->btn_send_gcode->Hide(); @@ -625,7 +668,9 @@ Sidebar::Sidebar(Plater *parent) // Sizer in the scrolled area scrolled_sizer->Add(p->mode_sizer, 0, wxALIGN_CENTER_HORIZONTAL/*RIGHT | wxBOTTOM | wxRIGHT, 5*/); - scrolled_sizer->Add(p->sizer_presets, 0, wxEXPAND | wxLEFT, margin_5); + is_msw ? + scrolled_sizer->Add(p->presets_panel, 0, wxEXPAND | wxLEFT, margin_5) : + scrolled_sizer->Add(p->sizer_presets, 0, wxEXPAND | wxLEFT, margin_5); scrolled_sizer->Add(p->sizer_params, 1, wxEXPAND | wxLEFT, margin_5); scrolled_sizer->Add(p->object_info, 0, wxEXPAND | wxTOP | wxLEFT, margin_5); scrolled_sizer->Add(p->sliced_info, 0, wxEXPAND | wxTOP | wxLEFT, margin_5); @@ -649,14 +694,21 @@ Sidebar::Sidebar(Plater *parent) // Events p->btn_export_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->export_gcode(); }); - p->btn_reslice->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->reslice(); }); + p->btn_reslice->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) + { + const bool export_gcode_after_slicing = wxGetKeyState(WXK_SHIFT); + if (export_gcode_after_slicing) + p->plater->export_gcode(); + else + p->plater->reslice(); + }); p->btn_send_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->send_gcode(); }); } Sidebar::~Sidebar() {} void Sidebar::init_filament_combo(PresetComboBox **combo, const int extr_idx) { - *combo = new PresetComboBox(p->scrolled, Slic3r::Preset::TYPE_FILAMENT); + *combo = new PresetComboBox(p->presets_panel, Slic3r::Preset::TYPE_FILAMENT); // # copy icons from first choice // $choice->SetItemBitmap($_, $choices->[0]->GetItemBitmap($_)) for 0..$#presets; @@ -718,6 +770,8 @@ void Sidebar::update_presets(Preset::Type preset_type) case Preset::TYPE_PRINTER: { +// wxWindowUpdateLocker noUpdates_scrolled(p->scrolled); + // Update the print choosers to only contain the compatible presets, update the dirty flags. if (print_tech == ptFFF) preset_bundle.prints.update_platter_ui(p->combo_print); @@ -747,9 +801,15 @@ void Sidebar::update_presets(Preset::Type preset_type) wxGetApp().preset_bundle->export_selections(*wxGetApp().app_config); } -void Sidebar::update_mode_sizer(const Slic3r::ConfigOptionMode& mode) +void Sidebar::update_mode_sizer() const +{ + p->mode_sizer->SetMode(m_mode); +} + +void Sidebar::update_reslice_btn_tooltip() const { - p->mode_sizer->SetMode(mode); + const wxString tooltip = m_mode == comSimple ? wxString("") : _(L("Hold Shift to Slice & Export G-code")); + p->btn_reslice->SetToolTip(tooltip); } ObjectManipulation* Sidebar::obj_manipul() @@ -772,6 +832,11 @@ wxScrolledWindow* Sidebar::scrolled_panel() return p->scrolled; } +wxPanel* Sidebar::presets_panel() +{ + return p->presets_panel; +} + ConfigOptionsGroup* Sidebar::og_freq_chng_params(const bool is_fff) { return p->frequently_changed_parameters->get_og(is_fff); @@ -942,8 +1007,9 @@ void Sidebar::enable_buttons(bool enable) p->btn_send_gcode->Enable(enable); } -void Sidebar::show_reslice(bool show) { p->btn_reslice->Show(show); } -void Sidebar::show_send(bool show) { p->btn_send_gcode->Show(show); } +void Sidebar::show_reslice(bool show) const { p->btn_reslice->Show(show); } +void Sidebar::show_export(bool show) const { p->btn_export_gcode->Show(show); } +void Sidebar::show_send(bool show) const { p->btn_send_gcode->Show(show); } bool Sidebar::is_multifilament() { @@ -951,6 +1017,24 @@ bool Sidebar::is_multifilament() } +void Sidebar::update_mode() +{ + m_mode = wxGetApp().get_mode(); + + update_reslice_btn_tooltip(); + update_mode_sizer(); + + wxWindowUpdateLocker noUpdates(this); + + p->object_list->get_sizer()->Show(m_mode > comSimple); + + p->object_list->unselect_objects(); + p->object_list->update_selections(); + p->object_list->update_object_menu(); + + Layout(); +} + std::vector<PresetComboBox*>& Sidebar::combos_filament() { return p->combos_filament; @@ -1019,6 +1103,7 @@ struct Plater::priv std::vector<wxPanel*> panels; Sidebar *sidebar; Bed3D bed; + Camera camera; View3D* view3D; GLToolbar view_toolbar; Preview *preview; @@ -1033,6 +1118,9 @@ struct Plater::priv wxTimer background_process_timer; + std::string label_btn_export; + std::string label_btn_send; + static const std::regex pattern_bundle; static const std::regex pattern_3mf; static const std::regex pattern_zip_amf; @@ -1115,13 +1203,13 @@ struct Plater::priv void on_action_layersediting(SimpleEvent&); void on_object_select(SimpleEvent&); - void on_viewport_changed(SimpleEvent&); void on_right_click(Vec2dEvent&); void on_wipetower_moved(Vec3dEvent&); void on_update_geometry(Vec3dsEvent<2>&); void on_3dcanvas_mouse_dragging_finished(SimpleEvent&); void update_object_menu(); + void show_action_buttons(const bool is_ready_to_slice) const; // Set the bed shape to a single closed 2D polygon(array of two element arrays), // triangulate the bed and store the triangles into m_bed.m_triangles, @@ -1202,11 +1290,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) sla_print.set_status_callback(statuscb); this->q->Bind(EVT_SLICING_UPDATE, &priv::on_slicing_update, this); - view3D = new View3D(q, &model, config, &background_process); - preview = new Preview(q, config, &background_process, &gcode_preview_data, [this](){ schedule_background_process(); }); - - view3D->set_bed(&bed); - preview->set_bed(&bed); + view3D = new View3D(q, bed, camera, view_toolbar, &model, config, &background_process); + preview = new Preview(q, bed, camera, view_toolbar, config, &background_process, &gcode_preview_data, [this](){ schedule_background_process(); }); panels.push_back(view3D); panels.push_back(preview); @@ -1238,7 +1323,6 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) // 3DScene events: view3D_canvas->Bind(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, [this](SimpleEvent&) { this->schedule_background_process(); }); view3D_canvas->Bind(EVT_GLCANVAS_OBJECT_SELECT, &priv::on_object_select, this); - view3D_canvas->Bind(EVT_GLCANVAS_VIEWPORT_CHANGED, &priv::on_viewport_changed, this); view3D_canvas->Bind(EVT_GLCANVAS_RIGHT_CLICK, &priv::on_right_click, this); view3D_canvas->Bind(EVT_GLCANVAS_REMOVE_OBJECT, [q](SimpleEvent&) { q->remove_selected(); }); view3D_canvas->Bind(EVT_GLCANVAS_ARRANGE, [this](SimpleEvent&) { arrange(); }); @@ -1268,7 +1352,6 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&) { set_bed_shape(config->option<ConfigOptionPoints>("bed_shape")->values); }); // Preview events: - preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_VIEWPORT_CHANGED, &priv::on_viewport_changed, this); preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_QUESTION_MARK, [this](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); }); preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&) { set_bed_shape(config->option<ConfigOptionPoints>("bed_shape")->values); }); preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_TAB, [this](SimpleEvent&) { select_next_view_3D(); }); @@ -2089,13 +2172,36 @@ unsigned int Plater::priv::update_background_process(bool force_validation) wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt.Clone()); } - //FIXME update "Slice Now / Schedule background process" - //background_process.is_export_scheduled() - byl zavolan "Export G-code", background processing ma jmeno export souboru - //background_process.is_upload_scheduled() - byl zavolan "Send to OctoPrint", jeste nebylo doslajsovano (pak se preda upload fronte a background process zapomene) - //background_process.empty() - prazdna plocha - // pokud (return_state & UPDATE_BACKGROUND_PROCESS_INVALID) != 0 -> doslo k chybe (gray out "Slice now") mozna "Invalid data"??? - // jinak background_process.running() -> Zobraz "Slicing ..." - // jinak pokud ! background_process.empty() && ! background_process.finished() -> je neco ke slajsovani (povol tlacitko) "Slice Now" + if ((return_state & UPDATE_BACKGROUND_PROCESS_INVALID) != 0) + { + // Validation of the background data failed. + const wxString invalid_str = _(L("Invalid data")); + for (auto btn : {ActionButtonType::abReslice, ActionButtonType::abSendGCode, ActionButtonType::abExport}) + sidebar->set_btn_label(btn, invalid_str); + } + else + { + // Background data is valid. + if ((return_state & UPDATE_BACKGROUND_PROCESS_RESTART) != 0 || + (return_state & UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE) != 0 ) + this->statusbar()->set_status_text(L("Ready to slice")); + + sidebar->set_btn_label(ActionButtonType::abExport, _(label_btn_export)); + sidebar->set_btn_label(ActionButtonType::abSendGCode, _(label_btn_send)); + + const wxString slice_string = background_process.running() && wxGetApp().get_mode() == comSimple ? + _(L("Slicing")) + dots : _(L("Slice now")); + sidebar->set_btn_label(ActionButtonType::abReslice, slice_string); + + if (background_process.finished()) + show_action_buttons(false); + else if (!background_process.empty() && + !background_process.running()) /* Do not update buttons if background process is running + * This condition is important for SLA mode especially, + * when this function is called several times during calculations + * */ + show_action_buttons(true); + } return return_state; } @@ -2226,6 +2332,10 @@ void Plater::priv::set_current_panel(wxPanel* panel) if (std::find(panels.begin(), panels.end(), panel) == panels.end()) return; +#ifdef __WXMAC__ + bool force_render = (current_panel != nullptr); +#endif // __WXMAC__ + if (current_panel == panel) return; @@ -2234,7 +2344,19 @@ void Plater::priv::set_current_panel(wxPanel* panel) for (wxPanel* p : panels) { if (p == current_panel) + { +#ifdef __WXMAC__ + // On Mac we need also to force a render to avoid flickering when changing view + if (force_render) + { + if (p == view3D) + dynamic_cast<View3D*>(p)->get_canvas3d()->render(); + else if (p == preview) + dynamic_cast<Preview*>(p)->get_canvas3d()->render(); + } +#endif // __WXMAC__ p->Show(); + } } // then set to invisible the other for (wxPanel* p : panels) @@ -2266,7 +2388,7 @@ void Plater::priv::set_current_panel(wxPanel* panel) { this->q->reslice(); // keeps current gcode preview, if any - preview->reload_print(false, true); + preview->reload_print(true); preview->set_canvas_as_dirty(); view_toolbar.select_item("Preview"); } @@ -2288,7 +2410,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) //! instead of //! combo->GetStringSelection().ToUTF8().data()); - std::string selected_string = combo->GetString(combo->GetSelection()).ToUTF8().data(); + const std::string& selected_string = combo->GetString(combo->GetSelection()).ToUTF8().data(); if (preset_type == Preset::TYPE_FILAMENT) { wxGetApp().preset_bundle->set_filament_preset(idx, selected_string); @@ -2300,12 +2422,8 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) wxGetApp().preset_bundle->update_platter_filament_ui(idx, combo); } else { - for (Tab* tab : wxGetApp().tabs_list) { - if (tab->type() == preset_type) { - tab->select_preset(selected_string); - break; - } - } + wxWindowUpdateLocker noUpdates(sidebar->presets_panel()); + wxGetApp().get_tab(preset_type)->select_preset(selected_string); } // update plater with new config @@ -2316,8 +2434,10 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) { - this->statusbar()->set_progress(evt.status.percent); - this->statusbar()->set_status_text(_(L(evt.status.text)) + wxString::FromUTF8("…")); + if (evt.status.percent >= -1) { + this->statusbar()->set_progress(evt.status.percent); + this->statusbar()->set_status_text(_(L(evt.status.text)) + wxString::FromUTF8("…")); + } if (evt.status.flags & PrintBase::SlicingStatus::RELOAD_SCENE) { switch (this->printer_technology) { case ptFFF: @@ -2335,6 +2455,10 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) // Update SLA gizmo (reload_scene calls update_gizmos_data) q->canvas3D()->reload_scene(true); } + if (evt.status.flags & PrintBase::SlicingStatus::RELOAD_SLA_PREVIEW) { + // Update the SLA preview + this->preview->reload_print(); + } } void Plater::priv::on_slicing_completed(wxCommandEvent &) @@ -2361,14 +2485,17 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt) this->statusbar()->reset_cancel_callback(); this->statusbar()->stop_busy(); - bool canceled = evt.GetInt() < 0; - bool success = evt.GetInt() > 0; + const bool canceled = evt.GetInt() < 0; + const bool error = evt.GetInt() == 0; + const bool success = evt.GetInt() > 0; // Reset the "export G-code path" name, so that the automatic background processing will be enabled again. this->background_process.reset_export(); - if (! success) { + + if (error) { wxString message = evt.GetString(); if (message.IsEmpty()) message = _(L("Export failed")); + show_error(q, message); this->statusbar()->set_status_text(message); } if (canceled) @@ -2393,6 +2520,14 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt) this->update_sla_scene(); break; } + + if (canceled) { + if (wxGetApp().get_mode() == comSimple) + sidebar->set_btn_label(ActionButtonType::abReslice, "Slice now"); + show_action_buttons(true); + } + else if (wxGetApp().get_mode() == comSimple) + show_action_buttons(false); } void Plater::priv::on_layer_editing_toggled(bool enable) @@ -2435,15 +2570,6 @@ void Plater::priv::on_object_select(SimpleEvent& evt) selection_changed(); } -void Plater::priv::on_viewport_changed(SimpleEvent& evt) -{ - wxObject* o = evt.GetEventObject(); - if (o == preview->get_wxglcanvas()) - preview->set_viewport_into_scene(view3D->get_canvas3d()); - else if (o == view3D->get_wxglcanvas()) - preview->set_viewport_from_scene(view3D->get_canvas3d()); -} - void Plater::priv::on_right_click(Vec2dEvent& evt) { int obj_idx = get_selected_object_idx(); @@ -2506,15 +2632,20 @@ bool Plater::priv::init_object_menu() bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/) { - wxMenuItem* item_delete = append_menu_item(menu, wxID_ANY, _(L("Delete")) + "\tDel", _(L("Remove the selected object")), - [this](wxCommandEvent&) { q->remove_selected(); }, "brick_delete.png"); - if (!is_part){ + wxMenuItem* item_delete = nullptr; + if (is_part) { + item_delete = append_menu_item(menu, wxID_ANY, _(L("Delete")) + "\tDel", _(L("Remove the selected object")), + [this](wxCommandEvent&) { q->remove_selected(); }, "brick_delete.png"); + } else { wxMenuItem* item_increase = append_menu_item(menu, wxID_ANY, _(L("Increase copies")) + "\t+", _(L("Place one more copy of the selected object")), [this](wxCommandEvent&) { q->increase_instances(); }, "add.png"); wxMenuItem* item_decrease = append_menu_item(menu, wxID_ANY, _(L("Decrease copies")) + "\t-", _(L("Remove one copy of the selected object")), [this](wxCommandEvent&) { q->decrease_instances(); }, "delete.png"); wxMenuItem* item_set_number_of_copies = append_menu_item(menu, wxID_ANY, _(L("Set number of copies")) + dots, _(L("Change the number of copies of the selected object")), [this](wxCommandEvent&) { q->set_number_of_copies(); }, "textfield.png"); + // Delete menu was moved to be after +/- instace to make it more difficult to be selected by mistake. + item_delete = append_menu_item(menu, wxID_ANY, _(L("Delete")) + "\tDel", _(L("Remove the selected object")), + [this](wxCommandEvent&) { q->remove_selected(); }, "brick_delete.png"); menu->AppendSeparator(); wxMenuItem* item_instance_to_object = sidebar->obj_list()->append_menu_item_instance_to_object(menu); @@ -2551,7 +2682,7 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/ wxMenuItem* item_mirror = append_submenu(menu, mirror_menu, wxID_ANY, _(L("Mirror")), _(L("Mirror the selected object"))); - // ui updates needs to be binded to the parent panel + // ui updates needs to be bound to the parent panel if (q != nullptr) { q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_mirror()); }, item_mirror->GetId()); @@ -2681,9 +2812,6 @@ void Plater::priv::init_view_toolbar() view_toolbar.select_item("3D"); view_toolbar.set_enabled(true); - - view3D->set_view_toolbar(&view_toolbar); - preview->set_view_toolbar(&view_toolbar); } bool Plater::priv::can_delete_object() const @@ -2768,6 +2896,38 @@ void Plater::priv::update_object_menu() view3D->update_toolbar_items_visibility(); } +void Plater::priv::show_action_buttons(const bool is_ready_to_slice) const +{ + wxWindowUpdateLocker noUpdater(sidebar); + const auto prin_host_opt = config->option<ConfigOptionString>("print_host"); + const bool send_gcode_shown = prin_host_opt != nullptr && !prin_host_opt->value.empty(); + + // when a background processing is ON, export_btn and/or send_btn are showing + if (wxGetApp().app_config->get("background_processing") == "1") + { + sidebar->show_reslice(false); + sidebar->show_export(true); + sidebar->show_send(send_gcode_shown); + } + else + { + sidebar->show_reslice(is_ready_to_slice); + sidebar->show_export(!is_ready_to_slice); + sidebar->show_send(send_gcode_shown && !is_ready_to_slice); + } + sidebar->Layout(); +} + +void Sidebar::set_btn_label(const ActionButtonType btn_type, const wxString& label) const +{ + switch (btn_type) + { + case ActionButtonType::abReslice: p->btn_reslice->SetLabelText(label); break; + case ActionButtonType::abExport: p->btn_export_gcode->SetLabelText(label); break; + case ActionButtonType::abSendGCode: p->btn_send_gcode->SetLabelText(label); break; + } +} + // Plater / Public Plater::Plater(wxWindow *parent, MainFrame *main_frame) @@ -3094,6 +3254,22 @@ void Plater::reslice() this->p->background_process.set_task(PrintBase::TaskParams()); // Only restarts if the state is valid. this->p->restart_background_process(state | priv::UPDATE_BACKGROUND_PROCESS_FORCE_RESTART); + + if ((state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) != 0) + return; + + if (p->background_process.running()) + { + if (wxGetApp().get_mode() == comSimple) + p->sidebar->set_btn_label(ActionButtonType::abReslice, _(L("Slicing")) + dots); + else + { + p->sidebar->set_btn_label(ActionButtonType::abReslice, _(L("Slice now"))); + p->show_action_buttons(false); + } + } + else if (!p->background_process.empty() && !p->background_process.idle()) + p->show_action_buttons(true); } void Plater::reslice_SLA_supports(const ModelObject &object) @@ -3157,8 +3333,10 @@ void Plater::on_extruders_change(int num_extruders) { auto& choices = sidebar().combos_filament(); + if (num_extruders == choices.size()) + return; + wxWindowUpdateLocker noUpdates_scrolled_panel(&sidebar()/*.scrolled_panel()*/); -// sidebar().scrolled_panel()->Freeze(); int i = choices.size(); while ( i < num_extruders ) @@ -3292,6 +3470,9 @@ void Plater::set_printer_technology(PrinterTechnology printer_technology) } //FIXME for SLA synchronize //p->background_process.apply(Model)! + + p->label_btn_export = printer_technology == ptFFF ? L("Export G-code") : L("Export"); + p->label_btn_send = printer_technology == ptFFF ? L("Send G-code") : L("Send to printer"); } void Plater::changed_object(int obj_idx) diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 4d489c82a..ecce63805 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -38,6 +38,7 @@ class GLCanvas3D; using t_optgroups = std::vector <std::shared_ptr<ConfigOptionsGroup>>; class Plater; +enum class ActionButtonType : int; class PresetComboBox : public wxBitmapComboBox { @@ -61,7 +62,7 @@ private: class Sidebar : public wxPanel { - /*ConfigOptionMode*/int m_mode; + ConfigOptionMode m_mode; public: Sidebar(Plater *parent); Sidebar(Sidebar &&) = delete; @@ -73,12 +74,14 @@ public: void init_filament_combo(PresetComboBox **combo, const int extr_idx); void remove_unused_filament_combos(const int current_extruder_count); void update_presets(Slic3r::Preset::Type preset_type); - void update_mode_sizer(const Slic3r::ConfigOptionMode& mode); + void update_mode_sizer() const; + void update_reslice_btn_tooltip() const; ObjectManipulation* obj_manipul(); ObjectList* obj_list(); ObjectSettings* obj_settings(); wxScrolledWindow* scrolled_panel(); + wxPanel* presets_panel(); ConfigOptionsGroup* og_freq_chng_params(const bool is_fff); wxButton* get_wiping_dialog_button(); @@ -86,10 +89,12 @@ public: void show_info_sizer(); void show_sliced_info_sizer(const bool show); void enable_buttons(bool enable); - void show_reslice(bool show); - void show_send(bool show); + void set_btn_label(const ActionButtonType btn_type, const wxString& label) const; + void show_reslice(bool show) const; + void show_export(bool show) const; + void show_send(bool show) const; bool is_multifilament(); - void set_mode_value(const /*ConfigOptionMode*/int mode) { m_mode = mode; } + void update_mode(); std::vector<PresetComboBox*>& combos_filament(); private: diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index 37b495a36..656658b0b 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -457,6 +457,7 @@ const std::vector<std::string>& Preset::sla_print_options() "support_base_height", "support_critical_angle", "support_max_bridge_length", + "support_max_pillar_link_distance", "support_object_elevation", "support_points_density_relative", "support_points_minimal_distance", @@ -880,6 +881,7 @@ void PresetCollection::update_platter_ui(GUI::PresetComboBox *ui) { if (ui == nullptr) return; + // Otherwise fill in the list from scratch. ui->Freeze(); ui->Clear(); diff --git a/src/slic3r/GUI/PresetBundle.cpp b/src/slic3r/GUI/PresetBundle.cpp index 0ee7a5c6c..f0bb4de01 100644 --- a/src/slic3r/GUI/PresetBundle.cpp +++ b/src/slic3r/GUI/PresetBundle.cpp @@ -1289,7 +1289,7 @@ void PresetBundle::update_compatible(bool select_other_if_incompatible) { const Preset &printer_preset = this->printers.get_edited_preset(); - switch (printers.get_edited_preset().printer_technology()) { + switch (printer_preset.printer_technology()) { case ptFFF: { assert(printer_preset.config.has("default_print_profile")); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index b3580a285..d54d01572 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -58,6 +58,13 @@ Tab::Tab(wxNotebook* parent, const wxString& title, const char* name) : wxGetApp().tabs_list.push_back(this); m_em_unit = wxGetApp().em_unit(); + + Bind(wxEVT_SIZE, ([this](wxSizeEvent &evt) { + for (auto page : m_pages) + if (! page.get()->IsShown()) + page->layout_valid = false; + evt.Skip(); + })); } void Tab::set_type() @@ -73,6 +80,10 @@ void Tab::set_type() // sub new void Tab::create_preset_tab() { +#ifdef __WINDOWS__ + SetDoubleBuffered(true); +#endif //__WINDOWS__ + m_preset_bundle = wxGetApp().preset_bundle; // Vertical sizer to hold the choice menu and the rest of the page. @@ -290,6 +301,11 @@ Slic3r::GUI::PageShp Tab::add_options_page(const wxString& title, const std::str auto panel = this; #endif PageShp page(new Page(panel, title, icon_idx)); +// page->SetBackgroundStyle(wxBG_STYLE_SYSTEM); +#ifdef __WINDOWS__ +// page->SetDoubleBuffered(true); +#endif //__WINDOWS__ + page->SetScrollbars(1, 20, 1, 2); page->Hide(); m_hsizer->Add(page.get(), 1, wxEXPAND | wxLEFT, 5); @@ -315,7 +331,7 @@ void Tab::OnActivate() void Tab::update_labels_colour() { - Freeze(); +// Freeze(); //update options "decoration" for (const auto opt : m_options_list) { @@ -342,7 +358,7 @@ void Tab::update_labels_colour() if (field == nullptr) continue; field->set_label_colour_force(color); } - Thaw(); +// Thaw(); auto cur_item = m_treectrl->GetFirstVisibleItem(); while (cur_item) { @@ -386,7 +402,7 @@ void Tab::update_changed_ui() for (auto opt_key : dirty_options) m_options_list[opt_key] &= ~osInitValue; for (auto opt_key : nonsys_options) m_options_list[opt_key] &= ~osSystemValue; - Freeze(); +// Freeze(); //update options "decoration" for (const auto opt : m_options_list) { @@ -436,7 +452,7 @@ void Tab::update_changed_ui() field->set_undo_to_sys_tooltip(sys_tt); field->set_label_colour(color); } - Thaw(); +// Thaw(); wxTheApp->CallAfter([this]() { update_changed_tree_ui(); @@ -683,16 +699,16 @@ void Tab::load_config(const DynamicPrintConfig& config) // Reload current $self->{config} (aka $self->{presets}->edited_preset->config) into the UI fields. void Tab::reload_config() { - Freeze(); +// Freeze(); for (auto page : m_pages) page->reload_config(); - Thaw(); +// Thaw(); } void Tab::update_visibility() { const ConfigOptionMode mode = wxGetApp().get_mode(); - Freeze(); +// Freeze(); for (auto page : m_pages) page->update_visibility(mode); @@ -702,7 +718,7 @@ void Tab::update_visibility() m_mode_sizer->SetMode(mode); Layout(); - Thaw(); +// Thaw(); // to update tree items color // wxTheApp->CallAfter([this]() { @@ -752,21 +768,29 @@ void Tab::load_key_value(const std::string& opt_key, const boost::any& value, bo void Tab::on_value_change(const std::string& opt_key, const boost::any& value) { - ConfigOptionsGroup* og_freq_chng_params = wxGetApp().sidebar().og_freq_chng_params(supports_printer_technology(ptFFF)); - if (opt_key == "fill_density" || opt_key == "supports_enable" || opt_key == "pad_enable") + if (wxGetApp().plater() == nullptr) { + return; + } + + const bool is_fff = supports_printer_technology(ptFFF); + ConfigOptionsGroup* og_freq_chng_params = wxGetApp().sidebar().og_freq_chng_params(is_fff); + if (opt_key == "fill_density" || opt_key == "pad_enable") { boost::any val = og_freq_chng_params->get_config_value(*m_config, opt_key); og_freq_chng_params->set_value(opt_key, val); } - if (opt_key == "support_material" || opt_key == "support_material_buildplate_only") + + if ( is_fff && (opt_key == "support_material" || opt_key == "support_material_buildplate_only") || + !is_fff && (opt_key == "supports_enable" || opt_key == "support_buildplate_only")) { - wxString new_selection = !m_config->opt_bool("support_material") ? - _("None") : - m_config->opt_bool("support_material_buildplate_only") ? - _("Support on build plate only") : - _("Everywhere"); + const std::string support = is_fff ? "support_material" : "supports_enable"; + const std::string buildplate_only = is_fff ? "support_material_buildplate_only" : "support_buildplate_only"; + wxString new_selection = !m_config->opt_bool(support) ? _("None") : + m_config->opt_bool(buildplate_only) ? _("Support on build plate only") : + _("Everywhere"); og_freq_chng_params->set_value("support", new_selection); } + if (opt_key == "brim_width") { bool val = m_config->opt_float("brim_width") > 0.0 ? true : false; @@ -780,25 +804,6 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) wxGetApp().plater()->on_extruders_change(boost::any_cast<size_t>(value)); update(); - - // #ys_FIXME_to_delete - // Post event to the Plater after updating of the all dirty options - // It helps to avoid needless schedule_background_processing -// if (update_completed()) -// if (m_update_stack.empty()) -// { -// // wxCommandEvent event(EVT_TAB_VALUE_CHANGED); -// // event.SetEventObject(this); -// // event.SetString(opt_key); -// // if (opt_key == "extruders_count") -// // { -// // const int val = boost::any_cast<size_t>(value); -// // event.SetInt(val); -// // } -// // -// // wxPostEvent(this, event); -// wxGetApp().mainframe->on_value_changed(m_config); -// } } // Show/hide the 'purging volumes' button @@ -821,9 +826,17 @@ void Tab::update_wiping_button_visibility() { // To update the content of the selection boxes, // to update the filament colors of the selection boxes, // to update the "dirty" flags of the selection boxes, -// to uddate number of "filament" selection boxes when the number of extruders change. +// to update number of "filament" selection boxes when the number of extruders change. void Tab::on_presets_changed() { + if (wxGetApp().plater() == nullptr) { + return; + } + + // Instead of PostEvent (EVT_TAB_PRESETS_CHANGED) just call update_presets + wxGetApp().plater()->sidebar().update_presets(m_type); + update_preset_description_line(); + // Printer selected at the Printer tab, update "compatible" marks at the print and filament selectors. for (auto t: m_dependent_tabs) { @@ -834,16 +847,6 @@ void Tab::on_presets_changed() // clear m_dependent_tabs after first update from select_preset() // to avoid needless preset loading from update() function m_dependent_tabs.clear(); - - // #ys_FIXME_to_delete -// wxCommandEvent event(EVT_TAB_PRESETS_CHANGED); -// event.SetEventObject(this); -// wxPostEvent(this, event); - - // Instead of PostEvent (EVT_TAB_PRESETS_CHANGED) just call update_presets - wxGetApp().plater()->sidebar().update_presets(m_type); - - update_preset_description_line(); } void Tab::update_preset_description_line() @@ -1190,7 +1193,7 @@ void TabPrint::update() // return; // ! TODO Let delete this part of code after a common aplication testing m_update_cnt++; - Freeze(); +// Freeze(); double fill_density = m_config->option<ConfigOptionPercent>("fill_density")->value; @@ -1401,7 +1404,7 @@ void TabPrint::update() m_recommended_thin_wall_thickness_description_line->SetText( from_u8(PresetHints::recommended_thin_wall_thickness(*m_preset_bundle))); - Thaw(); +// Thaw(); m_update_cnt--; if (m_update_cnt==0) @@ -1496,6 +1499,7 @@ void TabFilament::build() line = optgroup->create_single_option_line("filament_ramming_parameters");// { _(L("Ramming")), "" }; line.widget = [this](wxWindow* parent) { auto ramming_dialog_btn = new wxButton(parent, wxID_ANY, _(L("Ramming settings"))+dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); + ramming_dialog_btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(ramming_dialog_btn); @@ -1576,7 +1580,7 @@ void TabFilament::update() return; // ys_FIXME m_update_cnt++; - Freeze(); +// Freeze(); wxString text = from_u8(PresetHints::cooling_description(m_presets->get_edited_preset())); m_cooling_description_line->SetText(text); text = from_u8(PresetHints::maximum_volumetric_flow_description(*m_preset_bundle)); @@ -1590,7 +1594,7 @@ void TabFilament::update() for (auto el : { "min_fan_speed", "disable_fan_first_layers" }) get_field(el)->toggle(fan_always_on); - Thaw(); +// Thaw(); m_update_cnt--; if (m_update_cnt == 0) @@ -1622,25 +1626,22 @@ bool Tab::current_preset_is_dirty() void TabPrinter::build_printhost(ConfigOptionsGroup *optgroup) { - const bool sla = m_presets->get_selected_preset().printer_technology() == ptSLA; + const PrinterTechnology tech = m_presets->get_selected_preset().printer_technology(); // Only offer the host type selection for FFF, for SLA it's always the SL1 printer (at the moment) - if (! sla) { + if (tech == ptFFF) { optgroup->append_single_option_line("host_type"); } - auto printhost_browse = [this, optgroup] (wxWindow* parent) { - - // TODO: SLA Bonjour - + auto printhost_browse = [=](wxWindow* parent) { auto btn = m_printhost_browse_btn = new wxButton(parent, wxID_ANY, _(L(" Browse "))+dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT); -// btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("zoom.png")), wxBITMAP_TYPE_PNG)); + btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); btn->SetBitmap(create_scaled_bitmap("zoom.png")); auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(btn); - btn->Bind(wxEVT_BUTTON, [this, parent, optgroup](wxCommandEvent &e) { - BonjourDialog dialog(parent); + btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) { + BonjourDialog dialog(parent, tech); if (dialog.show_and_lookup()) { optgroup->set_value("print_host", std::move(dialog.get_selected()), true); optgroup->get_field("print_host")->field_changed(); @@ -1653,7 +1654,7 @@ void TabPrinter::build_printhost(ConfigOptionsGroup *optgroup) auto print_host_test = [this](wxWindow* parent) { auto btn = m_print_host_test_btn = new wxButton(parent, wxID_ANY, _(L("Test")), wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); -// btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("wrench.png")), wxBITMAP_TYPE_PNG)); + btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); btn->SetBitmap(create_scaled_bitmap("wrench.png")); auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(btn); @@ -1691,6 +1692,7 @@ void TabPrinter::build_printhost(ConfigOptionsGroup *optgroup) auto printhost_cafile_browse = [this, optgroup] (wxWindow* parent) { auto btn = new wxButton(parent, wxID_ANY, _(L(" Browse "))+dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT); // btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("zoom.png")), wxBITMAP_TYPE_PNG)); + btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); btn->SetBitmap(create_scaled_bitmap("zoom.png")); auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(btn); @@ -1729,6 +1731,7 @@ void TabPrinter::build_printhost(ConfigOptionsGroup *optgroup) \tOn this system, Slic3r uses HTTPS certificates from the system Certificate Store or Keychain.\n\ \tTo use a custom CA file, please import your CA file into Certificate Store / Keychain.")), ca_file_hint)); + txt->SetFont(Slic3r::GUI::wxGetApp().normal_font()); auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(txt); return sizer; @@ -1969,7 +1972,7 @@ void TabPrinter::build_sla() Line line = optgroup->create_single_option_line("bed_shape");//{ _(L("Bed shape")), "" }; line.widget = [this](wxWindow* parent) { auto btn = new wxButton(parent, wxID_ANY, _(L(" Set ")) + dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); - // btn->SetFont(Slic3r::GUI::small_font); + btn->SetFont(wxGetApp().small_font()); // btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("printer_empty.png")), wxBITMAP_TYPE_PNG)); btn->SetBitmap(create_scaled_bitmap("printer_empty.png")); @@ -2276,7 +2279,7 @@ void TabPrinter::update() void TabPrinter::update_fff() { - Freeze(); +// Freeze(); bool en; auto serial_speed = get_field("serial_speed"); @@ -2375,7 +2378,7 @@ void TabPrinter::update_fff() (have_multiple_extruders && toolchange_retraction); } - Thaw(); +// Thaw(); } void TabPrinter::update_sla() @@ -2469,7 +2472,7 @@ void Tab::load_current_preset() //Regerenerate content of the page tree. void Tab::rebuild_page_tree(bool tree_sel_change_event /*= false*/) { - Freeze(); +// Freeze(); // get label of the currently selected item const auto sel_item = m_treectrl->GetSelection(); @@ -2495,7 +2498,7 @@ void Tab::rebuild_page_tree(bool tree_sel_change_event /*= false*/) // this is triggered on first load, so we don't disable the sel change event m_treectrl->SelectItem(m_treectrl->GetFirstVisibleItem());//! (treectrl->GetFirstChild(rootItem)); } - Thaw(); +// Thaw(); } void Tab::update_page_tree_visibility() @@ -2609,7 +2612,7 @@ void Tab::select_preset(std::string preset_name) } else { if (current_dirty) m_presets->discard_current_changes(); - m_presets->select_preset_by_name(preset_name, false); + const bool is_selected = m_presets->select_preset_by_name(preset_name, false); // Mark the print & filament enabled if they are compatible with the currently selected preset. // The following method should not discard changes of current print or filament presets on change of a printer profile, // if they are compatible with the current printer. @@ -2618,6 +2621,28 @@ void Tab::select_preset(std::string preset_name) // Initialize the UI from the current preset. if (printer_tab) static_cast<TabPrinter*>(this)->update_pages(); + + if (!is_selected && printer_tab) + { + /* There is a case, when : + * after Config Wizard applying we try to select previously selected preset, but + * in a current configuration this one: + * 1. doesn't exist now, + * 2. have another printer_technology + * So, it is necessary to update list of dependent tabs + * to the corresponding printer_technology + */ + const PrinterTechnology printer_technology = m_presets->get_edited_preset().printer_technology(); + if (printer_technology == ptFFF && m_dependent_tabs.front() != Preset::Type::TYPE_PRINT || + printer_technology == ptSLA && m_dependent_tabs.front() != Preset::Type::TYPE_SLA_PRINT ) + { + m_dependent_tabs.clear(); + if (printer_technology == ptFFF) + m_dependent_tabs = { Preset::Type::TYPE_PRINT, Preset::Type::TYPE_FILAMENT }; + else + m_dependent_tabs = { Preset::Type::TYPE_SLA_PRINT, Preset::Type::TYPE_SLA_MATERIAL }; + } + } load_current_preset(); } } @@ -2689,7 +2714,7 @@ void Tab::OnTreeSelChange(wxTreeEvent& event) #ifdef __linux__ std::unique_ptr<wxWindowUpdateLocker> no_updates(new wxWindowUpdateLocker(this)); #else - wxWindowUpdateLocker noUpdates(this); +// wxWindowUpdateLocker noUpdates(this); #endif if (m_pages.empty()) @@ -2709,17 +2734,22 @@ void Tab::OnTreeSelChange(wxTreeEvent& event) if (page == nullptr) return; for (auto& el : m_pages) - el.get()->Hide(); - -#ifdef __linux__ - no_updates.reset(nullptr); -#endif +// if (el.get()->IsShown()) { + el.get()->Hide(); +// break; +// } - page->Show(); - m_hsizer->Layout(); - Refresh(); + #ifdef __linux__ + no_updates.reset(nullptr); + #endif update_undo_buttons(); + page->Show(); +// if (! page->layout_valid) { + page->layout_valid = true; + m_hsizer->Layout(); + Refresh(); +// } } void Tab::OnKeyDown(wxKeyEvent& event) @@ -2859,7 +2889,9 @@ void Tab::update_ui_from_settings() wxSizer* Tab::compatible_widget_create(wxWindow* parent, PresetDependencies &deps) { deps.checkbox = new wxCheckBox(parent, wxID_ANY, _(L("All"))); + deps.checkbox->SetFont(Slic3r::GUI::wxGetApp().normal_font()); deps.btn = new wxButton(parent, wxID_ANY, _(L(" Set "))+dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); + deps.btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); // deps.btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("printer_empty.png")), wxBITMAP_TYPE_PNG)); deps.btn->SetBitmap(create_scaled_bitmap("printer_empty.png")); @@ -3055,6 +3087,7 @@ ConfigOptionsGroupShp Page::new_optgroup(const wxString& title, int noncommon_la } // auto bmp = new wxStaticBitmap(parent, wxID_ANY, bmp_name.empty() ? wxNullBitmap : wxBitmap(from_u8(var(bmp_name)), wxBITMAP_TYPE_PNG)); auto bmp = new wxStaticBitmap(parent, wxID_ANY, bmp_name.empty() ? wxNullBitmap : create_scaled_bitmap(bmp_name)); + bmp->SetBackgroundStyle(wxBG_STYLE_PAINT); return bmp; }; @@ -3270,7 +3303,8 @@ void TabSLAPrint::build() optgroup->append_single_option_line("support_pillar_diameter"); optgroup->append_single_option_line("support_pillar_connection_mode"); optgroup->append_single_option_line("support_buildplate_only"); - optgroup->append_single_option_line("support_pillar_widening_factor"); + // TODO: This parameter is not used at the moment. + // optgroup->append_single_option_line("support_pillar_widening_factor"); optgroup->append_single_option_line("support_base_diameter"); optgroup->append_single_option_line("support_base_height"); optgroup->append_single_option_line("support_object_elevation"); @@ -3278,6 +3312,7 @@ void TabSLAPrint::build() optgroup = page->new_optgroup(_(L("Connection of the support sticks and junctions"))); optgroup->append_single_option_line("support_critical_angle"); optgroup->append_single_option_line("support_max_bridge_length"); + optgroup->append_single_option_line("support_max_pillar_link_distance"); optgroup = page->new_optgroup(_(L("Automatic generation"))); optgroup->append_single_option_line("support_points_density_relative"); @@ -3336,11 +3371,37 @@ void TabSLAPrint::update() return; // #ys_FIXME // #ys_FIXME -// m_update_cnt++; -// ! something to update -// m_update_cnt--; -// -// if (m_update_cnt == 0) + m_update_cnt++; + + double head_penetration = m_config->opt_float("support_head_penetration"); + double head_width = m_config->opt_float("support_head_width"); + if(head_penetration > head_width) { + wxString msg_text = _(L("Head penetration should not be greater than the head width.")); + auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Invalid Head penetration")), wxICON_WARNING | wxOK); + DynamicPrintConfig new_conf = *m_config; + if (dialog->ShowModal() == wxID_OK) { + new_conf.set_key_value("support_head_penetration", new ConfigOptionFloat(head_width)); + } + + load_config(new_conf); + } + + double pinhead_d = m_config->opt_float("support_head_front_diameter"); + double pillar_d = m_config->opt_float("support_pillar_diameter"); + if(pinhead_d > pillar_d) { + wxString msg_text = _(L("Pinhead diameter should be smaller than the pillar diameter.")); + auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Invalid pinhead diameter")), wxICON_WARNING | wxOK); + DynamicPrintConfig new_conf = *m_config; + if (dialog->ShowModal() == wxID_OK) { + new_conf.set_key_value("support_head_front_diameter", new ConfigOptionFloat(pillar_d / 2.0)); + } + + load_config(new_conf); + } + + m_update_cnt--; + + if (m_update_cnt == 0) wxGetApp().mainframe->on_config_changed(m_config); } diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 2f8cec1de..81a79c58c 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -65,6 +65,9 @@ public: bool m_is_modified_values{ false }; bool m_is_nonsys_values{ true }; + // Delayed layout after resizing the main window. + bool layout_valid = false; + public: std::vector <ConfigOptionsGroupShp> m_optgroups; DynamicPrintConfig* m_config; diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index e83101d22..dee18ad43 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -1958,21 +1958,8 @@ int PrusaDoubleSlider::get_value_from_position(const wxCoord x, const wxCoord y) return int(m_min_value + double(height - SLIDER_MARGIN - y) / step + 0.5); } -void PrusaDoubleSlider::detect_selected_slider(const wxPoint& pt, const bool is_mouse_wheel /*= false*/) +void PrusaDoubleSlider::detect_selected_slider(const wxPoint& pt) { - if (is_mouse_wheel) - { - if (is_horizontal()) { - m_selection = pt.x <= m_rect_lower_thumb.GetRight() ? ssLower : - pt.x >= m_rect_higher_thumb.GetLeft() ? ssHigher : ssUndef; - } - else { - m_selection = pt.y >= m_rect_lower_thumb.GetTop() ? ssLower : - pt.y <= m_rect_higher_thumb.GetBottom() ? ssHigher : ssUndef; - } - return; - } - m_selection = is_point_in_rect(pt, m_rect_lower_thumb) ? ssLower : is_point_in_rect(pt, m_rect_higher_thumb) ? ssHigher : ssUndef; } @@ -2192,12 +2179,20 @@ void PrusaDoubleSlider::action_tick(const TicksAction action) void PrusaDoubleSlider::OnWheel(wxMouseEvent& event) { - wxClientDC dc(this); - wxPoint pos = event.GetLogicalPosition(dc); - detect_selected_slider(pos, true); - - if (m_selection == ssUndef) - return; + // Set nearest to the mouse thumb as a selected, if there is not selected thumb + if (m_selection == ssUndef) + { + const wxPoint& pt = event.GetLogicalPosition(wxClientDC(this)); + + if (is_horizontal()) + m_selection = abs(pt.x - m_rect_lower_thumb.GetRight()) <= + abs(pt.x - m_rect_higher_thumb.GetLeft()) ? + ssLower : ssHigher; + else + m_selection = abs(pt.y - m_rect_lower_thumb.GetTop()) <= + abs(pt.y - m_rect_higher_thumb.GetBottom()) ? + ssLower : ssHigher; + } move_current_thumb(event.GetWheelRotation() > 0); } diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index e0bd5d091..a15bee256 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -771,7 +771,7 @@ protected: void draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) const; void update_thumb_rect(const wxCoord& begin_x, const wxCoord& begin_y, const SelectedSlider& selection); - void detect_selected_slider(const wxPoint& pt, const bool is_mouse_wheel = false); + void detect_selected_slider(const wxPoint& pt); void correct_lower_value(); void correct_higher_value(); void move_current_thumb(const bool condition); diff --git a/src/slic3r/Utils/Bonjour.cpp b/src/slic3r/Utils/Bonjour.cpp index 4953cfc64..28b3b2228 100644 --- a/src/slic3r/Utils/Bonjour.cpp +++ b/src/slic3r/Utils/Bonjour.cpp @@ -5,6 +5,7 @@ #include <array> #include <vector> #include <string> +#include <map> #include <thread> #include <boost/optional.hpp> #include <boost/system/error_code.hpp> @@ -33,7 +34,9 @@ namespace Slic3r { // the implementations has been tested with AFL. -// Relevant RFC: https://www.ietf.org/rfc/rfc6762.txt +// Relevant RFCs: +// https://tools.ietf.org/html/rfc6762.txt +// https://tools.ietf.org/html/rfc6763.txt struct DnsName: public std::string @@ -156,9 +159,9 @@ struct DnsQuestion uint16_t type; uint16_t qclass; - DnsQuestion() : - type(0), - qclass(0) + DnsQuestion() + : type(0) + , qclass(0) {} static optional<DnsQuestion> decode(const std::vector<char> &buffer, size_t &offset) @@ -187,10 +190,10 @@ struct DnsResource uint32_t ttl; std::vector<char> data; - DnsResource() : - type(0), - rclass(0), - ttl(0) + DnsResource() + : type(0) + , rclass(0) + , ttl(0) {} static optional<DnsResource> decode(const std::vector<char> &buffer, size_t &offset, size_t &dataoffset) @@ -310,9 +313,9 @@ struct DnsRR_TXT TAG = 0x10, }; - std::vector<std::string> values; + BonjourReply::TxtData data; - static optional<DnsRR_TXT> decode(const DnsResource &rr) + static optional<DnsRR_TXT> decode(const DnsResource &rr, const Bonjour::TxtKeys &txt_keys) { const size_t size = rr.data.size(); if (size < 2) { @@ -328,11 +331,21 @@ struct DnsRR_TXT } ++it; - std::string value(val_size, ' '); - std::copy(it, it + val_size, value.begin()); - res.values.push_back(std::move(value)); + const auto it_end = it + val_size; + const auto it_eq = std::find(it, it_end, '='); + if (it_eq > it && it_eq < it_end - 1) { + std::string key(it_eq - it, ' '); + std::copy(it, it_eq, key.begin()); + + if (txt_keys.find(key) != txt_keys.end() || key == "path") { + // This key-value has been requested for + std::string value(it_end - it_eq - 1, ' '); + std::copy(it_eq + 1, it_end, value.begin()); + res.data.insert(std::make_pair(std::move(key), std::move(value))); + } + } - it += val_size; + it = it_end; } return std::move(res); @@ -389,7 +402,7 @@ struct DnsMessage DnsSDMap sdmap; - static optional<DnsMessage> decode(const std::vector<char> &buffer) + static optional<DnsMessage> decode(const std::vector<char> &buffer, const Bonjour::TxtKeys &txt_keys) { const auto size = buffer.size(); if (size < DnsHeader::SIZE + DnsQuestion::MIN_SIZE || size > MAX_SIZE) { @@ -414,14 +427,15 @@ struct DnsMessage if (!rr) { return boost::none; } else { - res.parse_rr(buffer, std::move(*rr), dataoffset); + res.parse_rr(buffer, std::move(*rr), dataoffset, txt_keys); } } return std::move(res); } + private: - void parse_rr(const std::vector<char> &buffer, DnsResource &&rr, size_t dataoffset) + void parse_rr(const std::vector<char> &buffer, DnsResource &&rr, size_t dataoffset, const Bonjour::TxtKeys &txt_keys) { switch (rr.type) { case DnsRR_A::TAG: DnsRR_A::decode(this->rr_a, rr); break; @@ -432,7 +446,7 @@ private: break; } case DnsRR_TXT::TAG: { - auto txt = DnsRR_TXT::decode(rr); + auto txt = DnsRR_TXT::decode(rr, txt_keys); if (txt) { this->sdmap.insert_txt(std::move(rr.name), std::move(*txt)); } break; } @@ -442,26 +456,28 @@ private: std::ostream& operator<<(std::ostream &os, const DnsMessage &msg) { - os << "DnsMessage(ID: " << msg.header.id << ", " - << "Q: " << (msg.question ? msg.question->name.c_str() : "none") << ", " - << "A: " << (msg.rr_a ? msg.rr_a->ip.to_string() : "none") << ", " - << "AAAA: " << (msg.rr_aaaa ? msg.rr_aaaa->ip.to_string() : "none") << ", " - << "services: ["; - - enum { SRV_PRINT_MAX = 3 }; - unsigned i = 0; - for (const auto &sdpair : msg.sdmap) { - os << sdpair.first << ", "; - - if (++i >= SRV_PRINT_MAX) { - os << "..."; - break; - } + os << boost::format("DnsMessage(ID: %1%, Q: %2%, A: %3%, AAAA: %4%, services: [") + % msg.header.id + % (msg.question ? msg.question->name.c_str() : "none") + % (msg.rr_a ? msg.rr_a->ip.to_string() : "none") + % (msg.rr_aaaa ? msg.rr_aaaa->ip.to_string() : "none"); + + enum { SRV_PRINT_MAX = 3 }; + unsigned i = 0; + for (const auto &sdpair : msg.sdmap) { + if (i > 0) { os << ", "; } + + if (i < SRV_PRINT_MAX) { + os << sdpair.first; + } else { + os << "..."; + break; } - os << "])"; + i++; + } - return os; + return os << "])"; } @@ -525,8 +541,9 @@ optional<BonjourRequest> BonjourRequest::make(const std::string &service, const struct Bonjour::priv { const std::string service; - const std::string protocol; - const std::string service_dn; + std::string protocol; + std::string service_dn; + TxtKeys txt_keys; unsigned timeout; unsigned retries; @@ -535,19 +552,18 @@ struct Bonjour::priv Bonjour::ReplyFn replyfn; Bonjour::CompleteFn completefn; - priv(std::string service, std::string protocol); + priv(std::string &&service); std::string strip_service_dn(const std::string &service_name) const; void udp_receive(udp::endpoint from, size_t bytes); void lookup_perform(); }; -Bonjour::priv::priv(std::string service, std::string protocol) : - service(std::move(service)), - protocol(std::move(protocol)), - service_dn((boost::format("_%1%._%2%.local") % this->service % this->protocol).str()), - timeout(10), - retries(1) +Bonjour::priv::priv(std::string &&service) + : service(std::move(service)) + , protocol("tcp") + , timeout(10) + , retries(1) { buffer.resize(DnsMessage::MAX_SIZE); } @@ -573,13 +589,13 @@ void Bonjour::priv::udp_receive(udp::endpoint from, size_t bytes) } buffer.resize(bytes); - const auto dns_msg = DnsMessage::decode(buffer); + auto dns_msg = DnsMessage::decode(buffer, txt_keys); if (dns_msg) { asio::ip::address ip = from.address(); if (dns_msg->rr_a) { ip = dns_msg->rr_a->ip; } else if (dns_msg->rr_aaaa) { ip = dns_msg->rr_aaaa->ip; } - for (const auto &sdpair : dns_msg->sdmap) { + for (auto &sdpair : dns_msg->sdmap) { if (! sdpair.second.srv) { continue; } @@ -590,20 +606,12 @@ void Bonjour::priv::udp_receive(udp::endpoint from, size_t bytes) std::string path; std::string version; + BonjourReply::TxtData txt_data; if (sdpair.second.txt) { - static const std::string tag_path = "path="; - static const std::string tag_version = "version="; - - for (const auto &value : sdpair.second.txt->values) { - if (value.size() > tag_path.size() && value.compare(0, tag_path.size(), tag_path) == 0) { - path = std::move(value.substr(tag_path.size())); - } else if (value.size() > tag_version.size() && value.compare(0, tag_version.size(), tag_version) == 0) { - version = std::move(value.substr(tag_version.size())); - } - } + txt_data = std::move(sdpair.second.txt->data); } - BonjourReply reply(ip, srv.port, std::move(service_name), srv.hostname, std::move(path), std::move(version)); + BonjourReply reply(ip, srv.port, std::move(service_name), srv.hostname, std::move(txt_data)); replyfn(std::move(reply)); } } @@ -611,6 +619,8 @@ void Bonjour::priv::udp_receive(udp::endpoint from, size_t bytes) void Bonjour::priv::lookup_perform() { + service_dn = (boost::format("_%1%._%2%.local") % service % protocol).str(); + const auto brq = BonjourRequest::make(service, protocol); if (!brq) { return; @@ -671,21 +681,29 @@ void Bonjour::priv::lookup_perform() // API - public part -BonjourReply::BonjourReply(boost::asio::ip::address ip, uint16_t port, std::string service_name, std::string hostname, std::string path, std::string version) : - ip(std::move(ip)), - port(port), - service_name(std::move(service_name)), - hostname(std::move(hostname)), - path(path.empty() ? std::move(std::string("/")) : std::move(path)), - version(version.empty() ? std::move(std::string("Unknown")) : std::move(version)) +BonjourReply::BonjourReply(boost::asio::ip::address ip, uint16_t port, std::string service_name, std::string hostname, BonjourReply::TxtData txt_data) + : ip(std::move(ip)) + , port(port) + , service_name(std::move(service_name)) + , hostname(std::move(hostname)) + , txt_data(std::move(txt_data)) { std::string proto; std::string port_suffix; if (port == 443) { proto = "https://"; } if (port != 443 && port != 80) { port_suffix = std::to_string(port).insert(0, 1, ':'); } - if (this->path[0] != '/') { this->path.insert(0, 1, '/'); } + + std::string path = this->path(); + if (path[0] != '/') { path.insert(0, 1, '/'); } full_address = proto + ip.to_string() + port_suffix; - if (this->path != "/") { full_address += path; } + if (path != "/") { full_address += path; } + txt_data["path"] = std::move(path); +} + +std::string BonjourReply::path() const +{ + const auto it = txt_data.find("path"); + return it != txt_data.end() ? it->second : std::string("/"); } bool BonjourReply::operator==(const BonjourReply &other) const @@ -707,14 +725,22 @@ bool BonjourReply::operator<(const BonjourReply &other) const std::ostream& operator<<(std::ostream &os, const BonjourReply &reply) { - os << "BonjourReply(" << reply.ip.to_string() << ", " << reply.service_name << ", " - << reply.hostname << ", " << reply.path << ", " << reply.version << ")"; - return os; + os << boost::format("BonjourReply(%1%, %2%, %3%, %4%") + % reply.ip.to_string() + % reply.service_name + % reply.hostname + % reply.full_address; + + for (const auto &kv : reply.txt_data) { + os << boost::format(", %1%=%2%") % kv.first % kv.second; + } + + return os << ')'; } -Bonjour::Bonjour(std::string service, std::string protocol) : - p(new priv(std::move(service), std::move(protocol))) +Bonjour::Bonjour(std::string service) + : p(new priv(std::move(service))) {} Bonjour::Bonjour(Bonjour &&other) : p(std::move(other.p)) {} @@ -726,6 +752,18 @@ Bonjour::~Bonjour() } } +Bonjour& Bonjour::set_protocol(std::string protocol) +{ + if (p) { p->protocol = std::move(protocol); } + return *this; +} + +Bonjour& Bonjour::set_txt_keys(TxtKeys txt_keys) +{ + if (p) { p->txt_keys = std::move(txt_keys); } + return *this; +} + Bonjour& Bonjour::set_timeout(unsigned timeout) { if (p) { p->timeout = timeout; } diff --git a/src/slic3r/Utils/Bonjour.hpp b/src/slic3r/Utils/Bonjour.hpp index 63f34638c..e61cd1833 100644 --- a/src/slic3r/Utils/Bonjour.hpp +++ b/src/slic3r/Utils/Bonjour.hpp @@ -4,6 +4,8 @@ #include <cstdint> #include <memory> #include <string> +#include <set> +#include <unordered_map> #include <functional> #include <boost/asio/ip/address.hpp> @@ -13,16 +15,24 @@ namespace Slic3r { struct BonjourReply { + typedef std::unordered_map<std::string, std::string> TxtData; + boost::asio::ip::address ip; uint16_t port; std::string service_name; std::string hostname; std::string full_address; - std::string path; - std::string version; + + TxtData txt_data; BonjourReply() = delete; - BonjourReply(boost::asio::ip::address ip, uint16_t port, std::string service_name, std::string hostname, std::string path, std::string version); + BonjourReply(boost::asio::ip::address ip, + uint16_t port, + std::string service_name, + std::string hostname, + TxtData txt_data); + + std::string path() const; bool operator==(const BonjourReply &other) const; bool operator<(const BonjourReply &other) const; @@ -39,11 +49,17 @@ public: typedef std::shared_ptr<Bonjour> Ptr; typedef std::function<void(BonjourReply &&)> ReplyFn; typedef std::function<void()> CompleteFn; + typedef std::set<std::string> TxtKeys; - Bonjour(std::string service, std::string protocol = "tcp"); + Bonjour(std::string service); Bonjour(Bonjour &&other); ~Bonjour(); + // Set requested service protocol, "tcp" by default + Bonjour& set_protocol(std::string protocol); + // Set which TXT key-values should be collected + // Note that "path" is always collected + Bonjour& set_txt_keys(TxtKeys txt_keys); Bonjour& set_timeout(unsigned timeout); Bonjour& set_retries(unsigned retries); // ^ Note: By default there is 1 retry (meaning 1 broadcast is sent). |