1 // Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro.
2 #include "GLGizmoCut.hpp"
3 #include "slic3r/GUI/GLCanvas3D.hpp"
4 
5 #include <GL/glew.h>
6 
7 #include <wx/button.h>
8 #include <wx/checkbox.h>
9 #include <wx/stattext.h>
10 #include <wx/sizer.h>
11 
12 #include <algorithm>
13 
14 #include "slic3r/GUI/GUI_App.hpp"
15 #include "slic3r/GUI/Plater.hpp"
16 #include "slic3r/GUI/GUI_ObjectManipulation.hpp"
17 #include "libslic3r/AppConfig.hpp"
18 
19 
20 namespace Slic3r {
21 namespace GUI {
22 
23 const double GLGizmoCut::Offset = 10.0;
24 const double GLGizmoCut::Margin = 20.0;
25 const std::array<float, 4> GLGizmoCut::GrabberColor = { 1.0, 0.5, 0.0, 1.0 };
26 
GLGizmoCut(GLCanvas3D & parent,const std::string & icon_filename,unsigned int sprite_id)27 GLGizmoCut::GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
28     : GLGizmoBase(parent, icon_filename, sprite_id)
29     , m_cut_z(0.0)
30     , m_max_z(0.0)
31     , m_keep_upper(true)
32     , m_keep_lower(true)
33     , m_rotate_lower(false)
34 {}
35 
get_tooltip() const36 std::string GLGizmoCut::get_tooltip() const
37 {
38     double cut_z = m_cut_z;
39     if (wxGetApp().app_config->get("use_inches") == "1")
40         cut_z *= ObjectManipulation::mm_to_in;
41 
42     return (m_hover_id == 0 || m_grabbers[0].dragging) ? "Z: " + format(cut_z, 2) : "";
43 }
44 
on_init()45 bool GLGizmoCut::on_init()
46 {
47     m_grabbers.emplace_back();
48     m_shortcut_key = WXK_CONTROL_C;
49     return true;
50 }
51 
on_get_name() const52 std::string GLGizmoCut::on_get_name() const
53 {
54     return (_(L("Cut")) + " [C]").ToUTF8().data();
55 }
56 
on_set_state()57 void GLGizmoCut::on_set_state()
58 {
59     // Reset m_cut_z on gizmo activation
60     if (get_state() == On) {
61         m_cut_z = m_parent.get_selection().get_bounding_box().size()(2) / 2.0;
62     }
63 }
64 
on_is_activable() const65 bool GLGizmoCut::on_is_activable() const
66 {
67     const Selection& selection = m_parent.get_selection();
68     return selection.is_single_full_instance() && !selection.is_wipe_tower();
69 }
70 
on_start_dragging()71 void GLGizmoCut::on_start_dragging()
72 {
73     if (m_hover_id == -1)
74         return;
75 
76     const Selection& selection = m_parent.get_selection();
77     const BoundingBoxf3& box = selection.get_bounding_box();
78     m_start_z = m_cut_z;
79     update_max_z(selection);
80     m_drag_pos = m_grabbers[m_hover_id].center;
81     m_drag_center = box.center();
82     m_drag_center(2) = m_cut_z;
83 }
84 
on_update(const UpdateData & data)85 void GLGizmoCut::on_update(const UpdateData& data)
86 {
87     if (m_hover_id != -1)
88         set_cut_z(m_start_z + calc_projection(data.mouse_ray));
89 }
90 
on_render() const91 void GLGizmoCut::on_render() const
92 {
93     const Selection& selection = m_parent.get_selection();
94 
95     update_max_z(selection);
96 
97     const BoundingBoxf3& box = selection.get_bounding_box();
98     Vec3d plane_center = box.center();
99     plane_center(2) = m_cut_z;
100 
101     const float min_x = box.min(0) - Margin;
102     const float max_x = box.max(0) + Margin;
103     const float min_y = box.min(1) - Margin;
104     const float max_y = box.max(1) + Margin;
105     glsafe(::glEnable(GL_DEPTH_TEST));
106     glsafe(::glDisable(GL_CULL_FACE));
107     glsafe(::glEnable(GL_BLEND));
108     glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
109 
110     // Draw the cutting plane
111     ::glBegin(GL_QUADS);
112     ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f);
113     ::glVertex3f(min_x, min_y, plane_center(2));
114     ::glVertex3f(max_x, min_y, plane_center(2));
115     ::glVertex3f(max_x, max_y, plane_center(2));
116     ::glVertex3f(min_x, max_y, plane_center(2));
117     glsafe(::glEnd());
118 
119     glsafe(::glEnable(GL_CULL_FACE));
120     glsafe(::glDisable(GL_BLEND));
121 
122     // TODO: draw cut part contour?
123 
124     // Draw the grabber and the connecting line
125     m_grabbers[0].center = plane_center;
126     m_grabbers[0].center(2) = plane_center(2) + Offset;
127 
128     glsafe(::glDisable(GL_DEPTH_TEST));
129     glsafe(::glLineWidth(m_hover_id != -1 ? 2.0f : 1.5f));
130     glsafe(::glColor3f(1.0, 1.0, 0.0));
131     ::glBegin(GL_LINES);
132     ::glVertex3dv(plane_center.data());
133     ::glVertex3dv(m_grabbers[0].center.data());
134     glsafe(::glEnd());
135 
136     std::copy(std::begin(GrabberColor), std::end(GrabberColor), m_grabbers[0].color);
137     m_grabbers[0].render(m_hover_id == 0, (float)((box.size()(0) + box.size()(1) + box.size()(2)) / 3.0));
138 }
139 
on_render_for_picking() const140 void GLGizmoCut::on_render_for_picking() const
141 {
142     glsafe(::glDisable(GL_DEPTH_TEST));
143     render_grabbers_for_picking(m_parent.get_selection().get_bounding_box());
144 }
145 
on_render_input_window(float x,float y,float bottom_limit)146 void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit)
147 {
148     static float last_y = 0.0f;
149     static float last_h = 0.0f;
150 
151     m_imgui->begin(_L("Cut"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
152 
153     bool imperial_units = wxGetApp().app_config->get("use_inches") == "1";
154 
155     // adjust window position to avoid overlap the view toolbar
156     float win_h = ImGui::GetWindowHeight();
157     y = std::min(y, bottom_limit - win_h);
158     ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always);
159     if (last_h != win_h || last_y != y) {
160         // ask canvas for another frame to render the window in the correct position
161         m_parent.request_extra_frame();
162         if (last_h != win_h)
163             last_h = win_h;
164         if (last_y != y)
165             last_y = y;
166     }
167 
168     ImGui::AlignTextToFramePadding();
169     m_imgui->text("Z");
170     ImGui::SameLine();
171     ImGui::PushItemWidth(m_imgui->get_style_scaling() * 150.0f);
172 
173     double cut_z = m_cut_z;
174     if (imperial_units)
175         cut_z *= ObjectManipulation::mm_to_in;
176     ImGui::InputDouble("", &cut_z, 0.0f, 0.0f, "%.2f");
177 
178     ImGui::SameLine();
179     m_imgui->text(imperial_units ? _L("in") : _L("mm"));
180 
181     m_cut_z = cut_z * (imperial_units ? ObjectManipulation::in_to_mm : 1.0);
182 
183     ImGui::Separator();
184 
185     m_imgui->checkbox(_L("Keep upper part"), m_keep_upper);
186     m_imgui->checkbox(_L("Keep lower part"), m_keep_lower);
187     m_imgui->checkbox(_L("Rotate lower part upwards"), m_rotate_lower);
188 
189     ImGui::Separator();
190 
191     m_imgui->disabled_begin((!m_keep_upper && !m_keep_lower) || m_cut_z <= 0.0 || m_max_z < m_cut_z);
192     const bool cut_clicked = m_imgui->button(_L("Perform cut"));
193     m_imgui->disabled_end();
194 
195     m_imgui->end();
196 
197     if (cut_clicked && (m_keep_upper || m_keep_lower))
198         perform_cut(m_parent.get_selection());
199 }
200 
update_max_z(const Selection & selection) const201 void GLGizmoCut::update_max_z(const Selection& selection) const
202 {
203     m_max_z = selection.get_bounding_box().size()(2);
204     set_cut_z(m_cut_z);
205 }
206 
set_cut_z(double cut_z) const207 void GLGizmoCut::set_cut_z(double cut_z) const
208 {
209     // Clamp the plane to the object's bounding box
210     m_cut_z = std::clamp(cut_z, 0.0, m_max_z);
211 }
212 
perform_cut(const Selection & selection)213 void GLGizmoCut::perform_cut(const Selection& selection)
214 {
215     const int instance_idx = selection.get_instance_idx();
216     const int object_idx = selection.get_object_idx();
217 
218     wxCHECK_RET(instance_idx >= 0 && object_idx >= 0, "GLGizmoCut: Invalid object selection");
219 
220     // m_cut_z is the distance from the bed. Subtract possible SLA elevation.
221     const GLVolume* first_glvolume = selection.get_volume(*selection.get_volume_idxs().begin());
222     coordf_t object_cut_z = m_cut_z - first_glvolume->get_sla_shift_z();
223 
224     if (object_cut_z > 0.)
225         wxGetApp().plater()->cut(object_idx, instance_idx, object_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower);
226     else {
227         // the object is SLA-elevated and the plane is under it.
228     }
229 }
230 
calc_projection(const Linef3 & mouse_ray) const231 double GLGizmoCut::calc_projection(const Linef3& mouse_ray) const
232 {
233     double projection = 0.0;
234 
235     const Vec3d starting_vec = m_drag_pos - m_drag_center;
236     const double len_starting_vec = starting_vec.norm();
237     if (len_starting_vec != 0.0)
238     {
239         Vec3d mouse_dir = mouse_ray.unit_vector();
240         // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position
241         // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form
242         // in our case plane normal and ray direction are the same (orthogonal view)
243         // 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
244         Vec3d inters = mouse_ray.a + (m_drag_pos - mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir;
245         // vector from the starting position to the found intersection
246         Vec3d inters_vec = inters - m_drag_pos;
247 
248         // finds projection of the vector along the staring direction
249         projection = inters_vec.dot(starting_vec.normalized());
250     }
251     return projection;
252 }
253 
254 
255 } // namespace GUI
256 } // namespace Slic3r
257