Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/prusa3d/PrusaSlicer.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYuSanka <yusanka@gmail.com>2019-03-19 10:52:58 +0300
committerYuSanka <yusanka@gmail.com>2019-03-19 10:52:58 +0300
commit8be8b604f5f91f7cacc7b4dc9150a34a6f4ca10e (patch)
tree23a92a43a708ad1c93f9b4c498eb8f830003275c /src/slic3r
parentb382ad1ffbc1bae64734fcc8e9b70befed5c03c2 (diff)
parent20be57dc582b53014cca5e51b73a585ebdcff8c1 (diff)
Merge remote-tracking branch 'origin/vb_faster_tabs' into ys_comboboxes
Diffstat (limited to 'src/slic3r')
-rw-r--r--src/slic3r/CMakeLists.txt18
-rw-r--r--src/slic3r/GUI/3DScene.cpp11
-rw-r--r--src/slic3r/GUI/3DScene.hpp9
-rw-r--r--src/slic3r/GUI/BackgroundSlicingProcess.hpp2
-rw-r--r--src/slic3r/GUI/BonjourDialog.cpp70
-rw-r--r--src/slic3r/GUI/BonjourDialog.hpp5
-rw-r--r--src/slic3r/GUI/Camera.cpp62
-rw-r--r--src/slic3r/GUI/Camera.hpp50
-rw-r--r--src/slic3r/GUI/ConfigSnapshotDialog.cpp2
-rw-r--r--src/slic3r/GUI/Field.cpp38
-rw-r--r--src/slic3r/GUI/GLCanvas3D.cpp566
-rw-r--r--src/slic3r/GUI/GLCanvas3D.hpp75
-rw-r--r--src/slic3r/GUI/GLCanvas3DManager.cpp4
-rw-r--r--src/slic3r/GUI/GLCanvas3DManager.hpp5
-rw-r--r--src/slic3r/GUI/GLGizmo.cpp2862
-rw-r--r--src/slic3r/GUI/GLGizmo.hpp629
-rw-r--r--src/slic3r/GUI/GLTexture.hpp1
-rw-r--r--src/slic3r/GUI/GUI.cpp3
-rw-r--r--src/slic3r/GUI/GUI_App.cpp52
-rw-r--r--src/slic3r/GUI/GUI_App.hpp9
-rw-r--r--src/slic3r/GUI/GUI_ObjectList.cpp201
-rw-r--r--src/slic3r/GUI/GUI_ObjectList.hpp5
-rw-r--r--src/slic3r/GUI/GUI_Preview.cpp102
-rw-r--r--src/slic3r/GUI/GUI_Preview.hpp31
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoBase.cpp281
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoBase.hpp182
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoCut.cpp257
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoCut.hpp53
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp357
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp67
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoMove.cpp255
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoMove.hpp57
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp503
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp142
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoScale.cpp362
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoScale.hpp62
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp874
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp128
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmos.hpp11
-rw-r--r--src/slic3r/GUI/ImGuiWrapper.cpp12
-rw-r--r--src/slic3r/GUI/MainFrame.cpp22
-rw-r--r--src/slic3r/GUI/MsgDialog.cpp4
-rw-r--r--src/slic3r/GUI/OptionsGroup.cpp45
-rw-r--r--src/slic3r/GUI/OptionsGroup.hpp8
-rw-r--r--src/slic3r/GUI/Plater.cpp341
-rw-r--r--src/slic3r/GUI/Plater.hpp15
-rw-r--r--src/slic3r/GUI/Preset.cpp2
-rw-r--r--src/slic3r/GUI/PresetBundle.cpp2
-rw-r--r--src/slic3r/GUI/Tab.cpp223
-rw-r--r--src/slic3r/GUI/Tab.hpp3
-rw-r--r--src/slic3r/GUI/wxExtensions.cpp35
-rw-r--r--src/slic3r/GUI/wxExtensions.hpp2
-rw-r--r--src/slic3r/Utils/Bonjour.cpp180
-rw-r--r--src/slic3r/Utils/Bonjour.hpp24
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).