diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index c9a097aea3..27ffef636e 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -951,25 +951,54 @@ bool ModelObject::needed_repair() const return false; } -void ModelObject::cut(coordf_t z, Model* model) const +template static void cut_reset_transform(T *thing) { + const Vec3d offset = thing->get_offset(); + thing->set_transformation(Geometry::Transformation()); + thing->set_offset(offset); +} + +ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z) { - // clone this one to duplicate instances, materials etc. - ModelObject* upper = model->add_object(*this); - ModelObject* lower = model->add_object(*this); + // Clone the object to duplicate instances, materials etc. + ModelObject* upper = ModelObject::new_clone(*this); + ModelObject* lower = ModelObject::new_clone(*this); + upper->set_model(nullptr); + lower->set_model(nullptr); upper->sla_support_points.clear(); lower->sla_support_points.clear(); upper->clear_volumes(); lower->clear_volumes(); upper->input_file = ""; lower->input_file = ""; - - for (ModelVolume *volume : this->volumes) { + + const auto instance_matrix = instances[instance]->get_matrix(true); + + // Because transformations are going to be applied to meshes directly, + // we reset transformation of all instances and volumes, + // _except_ for translation, which is preserved in the transformation matrix + // and not applied to the mesh transform. + // TODO: Do the same for Z-rotation as well? + + // Convert z from relative to bb's base to object coordinates + // FIXME: doesn't work well for rotated objects + const auto bb = instance_bounding_box(instance, true); + z -= bb.min(2); + + for (auto *instance : upper->instances) { cut_reset_transform(instance); } + for (auto *instance : lower->instances) { cut_reset_transform(instance); } + + for (ModelVolume *volume : volumes) { if (! volume->is_model_part()) { // don't cut modifiers upper->add_volume(*volume); lower->add_volume(*volume); } else { TriangleMesh upper_mesh, lower_mesh; + + // Transform the mesh by the object transformation matrix + volume->mesh.transform(instance_matrix * volume->get_matrix(true)); + cut_reset_transform(volume); + TriangleMeshSlicer tms(&volume->mesh); tms.cut(z, &upper_mesh, &lower_mesh); @@ -977,7 +1006,7 @@ void ModelObject::cut(coordf_t z, Model* model) const lower_mesh.repair(); upper_mesh.reset_repair_stats(); lower_mesh.reset_repair_stats(); - + if (upper_mesh.facets_count() > 0) { ModelVolume* vol = upper->add_volume(upper_mesh); vol->name = volume->name; @@ -992,6 +1021,15 @@ void ModelObject::cut(coordf_t z, Model* model) const } } } + + upper->invalidate_bounding_box(); + lower->invalidate_bounding_box(); + + ModelObjectPtrs res; + if (upper->volumes.size() > 0) { res.push_back(upper); } + if (lower->volumes.size() > 0) { res.push_back(lower); } + + return res; } void ModelObject::split(ModelObjectPtrs* new_objects) @@ -1011,7 +1049,8 @@ void ModelObject::split(ModelObjectPtrs* new_objects) mesh->repair(); - ModelObject* new_object = m_model->add_object(); + // XXX: this seems to be the only real usage of m_model, maybe refactor this so that it's not needed? + ModelObject* new_object = m_model->add_object(); new_object->name = this->name; new_object->config = this->config; new_object->instances.reserve(this->instances.size()); diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 354d71dc46..8e3ee3f306 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -237,7 +237,7 @@ public: size_t materials_count() const; size_t facets_count() const; bool needed_repair() const; - void cut(coordf_t z, Model* model) const; + ModelObjectPtrs cut(size_t instance, coordf_t z); void split(ModelObjectPtrs* new_objects); void repair(); @@ -607,7 +607,7 @@ public: bool delete_object(ModelID id); bool delete_object(ModelObject* object); void clear_objects(); - + ModelMaterial* add_material(t_model_material_id material_id); ModelMaterial* add_material(t_model_material_id material_id, const ModelMaterial &other); ModelMaterial* get_material(t_model_material_id material_id) { diff --git a/src/slic3r.cpp b/src/slic3r.cpp index e9fa54ce03..376ade95a4 100644 --- a/src/slic3r.cpp +++ b/src/slic3r.cpp @@ -192,20 +192,21 @@ int main(int argc, char **argv) model.repair(); model.translate(0, 0, - model.bounding_box().min(2)); if (! model.objects.empty()) { - Model out; - model.objects.front()->cut(cli_config.cut, &out); - ModelObject &upper = *out.objects[0]; - ModelObject &lower = *out.objects[1]; - // Use the input name and trim off the extension. - std::string outfile = cli_config.output.value; - if (outfile.empty()) - outfile = model.objects.front()->input_file; - outfile = outfile.substr(0, outfile.find_last_of('.')); - std::cerr << outfile << "\n"; - if (upper.facets_count() > 0) - upper.mesh().write_binary((outfile + "_upper.stl").c_str()); - if (lower.facets_count() > 0) - lower.mesh().write_binary((outfile + "_lower.stl").c_str()); + // XXX + // Model out; + // model.objects.front()->cut(cli_config.cut, &out); + // ModelObject &upper = *out.objects[0]; + // ModelObject &lower = *out.objects[1]; + // // Use the input name and trim off the extension. + // std::string outfile = cli_config.output.value; + // if (outfile.empty()) + // outfile = model.objects.front()->input_file; + // outfile = outfile.substr(0, outfile.find_last_of('.')); + // std::cerr << outfile << "\n"; + // if (upper.facets_count() > 0) + // upper.mesh().write_binary((outfile + "_upper.stl").c_str()); + // if (lower.facets_count() > 0) + // lower.mesh().write_binary((outfile + "_lower.stl").c_str()); } } else if (cli_config.slice) { std::string outfile = cli_config.output.value; diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 45c700482e..2350cdffcc 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -1915,11 +1915,6 @@ void _3DScene::set_bed_shape(wxGLCanvas* canvas, const Pointfs& shape) s_canvas_mgr.set_bed_shape(canvas, shape); } -void _3DScene::set_cutting_plane(wxGLCanvas* canvas, float z, const ExPolygons& polygons) -{ - s_canvas_mgr.set_cutting_plane(canvas, z, polygons); -} - void _3DScene::set_color_by(wxGLCanvas* canvas, const std::string& value) { s_canvas_mgr.set_color_by(canvas, value); diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 5d81b57bbd..16523af686 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -577,8 +577,6 @@ public: static void set_bed_shape(wxGLCanvas* canvas, const Pointfs& shape); - static void set_cutting_plane(wxGLCanvas* canvas, float z, const ExPolygons& polygons); - static void set_color_by(wxGLCanvas* canvas, const std::string& value); static bool is_layers_editing_enabled(wxGLCanvas* canvas); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 1f8ca9aaee..6e47c08673 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -641,70 +641,6 @@ void GLCanvas3D::Axes::render(bool depth_test) const ::glEnd(); } -GLCanvas3D::CuttingPlane::CuttingPlane() - : m_z(-1.0f) -{ -} - -bool GLCanvas3D::CuttingPlane::set(float z, const ExPolygons& polygons) -{ - m_z = z; - - // grow slices in order to display them better - ExPolygons expolygons = offset_ex(polygons, (float)scale_(0.1)); - Lines lines = to_lines(expolygons); - return m_lines.set_from_lines(lines, m_z); -} - -void GLCanvas3D::CuttingPlane::render(const BoundingBoxf3& bb) const -{ - _render_plane(bb); - _render_contour(); -} - -void GLCanvas3D::CuttingPlane::_render_plane(const BoundingBoxf3& bb) const -{ - if (m_z >= 0.0f) - { - ::glDisable(GL_CULL_FACE); - ::glEnable(GL_BLEND); - ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - float margin = 20.0f; - float min_x = bb.min(0) - margin; - float max_x = bb.max(0) + margin; - float min_y = bb.min(1) - margin; - float max_y = bb.max(1) + margin; - - ::glBegin(GL_QUADS); - ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f); - ::glVertex3f(min_x, min_y, m_z); - ::glVertex3f(max_x, min_y, m_z); - ::glVertex3f(max_x, max_y, m_z); - ::glVertex3f(min_x, max_y, m_z); - ::glEnd(); - - ::glEnable(GL_CULL_FACE); - ::glDisable(GL_BLEND); - } -} - -void GLCanvas3D::CuttingPlane::_render_contour() const -{ - ::glEnableClientState(GL_VERTEX_ARRAY); - - if (m_z >= 0.0f) - { - unsigned int lines_vcount = m_lines.get_vertices_count(); - - ::glLineWidth(2.0f); - ::glColor3f(0.0f, 0.0f, 0.0f); - ::glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)m_lines.get_vertices()); - ::glDrawArrays(GL_LINES, 0, (GLsizei)lines_vcount); - } - - ::glDisableClientState(GL_VERTEX_ARRAY); -} GLCanvas3D::Shader::Shader() : m_shader(nullptr) @@ -2380,6 +2316,13 @@ bool GLCanvas3D::Gizmos::init(GLCanvas3D& parent) m_gizmos.insert(GizmosMap::value_type(Flatten, gizmo)); + gizmo = new GLGizmoCut(parent); + if (! gizmo->init()) { + return false; + } + + m_gizmos.insert({ Cut, gizmo }); + gizmo = new GLGizmoSlaSupports(parent); if (gizmo == nullptr) return false; @@ -2391,7 +2334,6 @@ bool GLCanvas3D::Gizmos::init(GLCanvas3D& parent) m_gizmos.insert(GizmosMap::value_type(SlaSupports, gizmo)); - return true; } @@ -2764,6 +2706,13 @@ void GLCanvas3D::Gizmos::render_overlay(const GLCanvas3D& canvas) const ::glPopMatrix(); } +void GLCanvas3D::Gizmos::create_external_gizmo_widgets(wxWindow *parent) +{ + for (auto &entry : m_gizmos) { + entry.second->create_external_gizmo_widgets(parent); + } +} + void GLCanvas3D::Gizmos::_reset() { for (GizmosMap::value_type& gizmo : m_gizmos) @@ -3166,6 +3115,7 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas) , m_regenerate_volumes(true) , m_color_by("volume") , m_reload_delayed(false) + , m_external_gizmo_widgets_parent(nullptr) { if (m_canvas != nullptr) { @@ -3267,8 +3217,16 @@ bool GLCanvas3D::init(bool useVBOs, bool use_legacy_opengl) if (!m_volumes.empty()) m_volumes.finalize_geometry(m_use_VBOs); - if (m_gizmos.is_enabled() && !m_gizmos.init(*this)) - return false; + if (m_gizmos.is_enabled()) { + if (! m_gizmos.init(*this)) { + return false; + } + + if (m_external_gizmo_widgets_parent != nullptr) { + m_gizmos.create_external_gizmo_widgets(m_external_gizmo_widgets_parent); + m_canvas->GetParent()->Layout(); + } + } if (!_init_toolbar()) return false; @@ -3371,11 +3329,6 @@ void GLCanvas3D::set_axes_length(float length) m_axes.length = length; } -void GLCanvas3D::set_cutting_plane(float z, const ExPolygons& polygons) -{ - m_cutting_plane.set(z, polygons); -} - void GLCanvas3D::set_color_by(const std::string& value) { m_color_by = value; @@ -3629,7 +3582,6 @@ void GLCanvas3D::render() #endif // ENABLE_GIZMOS_ON_TOP _render_current_gizmo(); - _render_cutting_plane(); #if ENABLE_SHOW_CAMERA_TARGET _render_camera_target(); #endif // ENABLE_SHOW_CAMERA_TARGET @@ -4524,6 +4476,11 @@ void GLCanvas3D::set_tooltip(const std::string& tooltip) const } } +void GLCanvas3D::set_external_gizmo_widgets_parent(wxWindow *parent) +{ + m_external_gizmo_widgets_parent = parent; +} + bool GLCanvas3D::_is_shown_on_screen() const { return (m_canvas != nullptr) ? m_canvas->IsShownOnScreen() : false; @@ -4624,14 +4581,6 @@ bool GLCanvas3D::_init_toolbar() if (!m_toolbar.add_item(item)) return false; - item.name = "cut"; - item.tooltip = GUI::L_str("Cut..."); - item.sprite_id = 7; - item.is_toggable = false; - item.action_event = EVT_GLTOOLBAR_CUT; - if (!m_toolbar.add_item(item)) - return false; - if (!m_toolbar.add_separator()) return false; @@ -5030,11 +4979,6 @@ void GLCanvas3D::_render_selection() const m_selection.render(); } -void GLCanvas3D::_render_cutting_plane() const -{ - m_cutting_plane.render(volumes_bounding_box()); -} - void GLCanvas3D::_render_warning_texture() const { if (!m_warning_texture_enabled) diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 3f76f5b4e7..cf7c840854 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -16,6 +16,7 @@ class wxKeyEvent; class wxMouseEvent; class wxTimerEvent; class wxPaintEvent; +class wxGLCanvas; namespace Slic3r { @@ -219,23 +220,6 @@ class GLCanvas3D void render(bool depth_test) const; }; - class CuttingPlane - { - float m_z; - GeometryBuffer m_lines; - - public: - CuttingPlane(); - - bool set(float z, const ExPolygons& polygons); - - void render(const BoundingBoxf3& bb) const; - - private: - void _render_plane(const BoundingBoxf3& bb) const; - void _render_contour() const; - }; - class Shader { GLShader* m_shader; @@ -479,6 +463,8 @@ public: Selection(); void set_volumes(GLVolumePtrs* volumes); + + Model* get_model() const { return m_model; } void set_model(Model* model); EMode get_mode() const { return m_mode; } @@ -580,6 +566,7 @@ private: Rotate, Flatten, SlaSupports, + Cut, Num_Types }; @@ -642,6 +629,8 @@ private: void render_overlay(const GLCanvas3D& canvas) const; + void create_external_gizmo_widgets(wxWindow *parent); + private: void _reset(); @@ -698,7 +687,6 @@ private: Camera m_camera; Bed m_bed; Axes m_axes; - CuttingPlane m_cutting_plane; LayersEditing m_layers_editing; Shader m_shader; Mouse m_mouse; @@ -734,6 +722,8 @@ private: GCodePreviewVolumeIndex m_gcode_preview_volume_index; + wxWindow *m_external_gizmo_widgets_parent; + void post_event(wxEvent &&event); void viewport_changed(); @@ -775,8 +765,6 @@ public: void set_axes_length(float length); - void set_cutting_plane(float z, const ExPolygons& polygons); - void set_color_by(const std::string& value); float get_camera_zoom() const; @@ -855,6 +843,8 @@ public: void set_tooltip(const std::string& tooltip) const; + void set_external_gizmo_widgets_parent(wxWindow *parent); + private: bool _is_shown_on_screen() const; void _force_zoom_to_bed(); @@ -881,7 +871,6 @@ private: void _render_axes(bool depth_test) const; void _render_objects() const; void _render_selection() const; - void _render_cutting_plane() const; void _render_warning_texture() const; void _render_legend_texture() const; void _render_layer_editing_overlay() const; diff --git a/src/slic3r/GUI/GLCanvas3DManager.cpp b/src/slic3r/GUI/GLCanvas3DManager.cpp index 7172195200..abf7834605 100644 --- a/src/slic3r/GUI/GLCanvas3DManager.cpp +++ b/src/slic3r/GUI/GLCanvas3DManager.cpp @@ -293,13 +293,6 @@ void GLCanvas3DManager::set_bed_shape(wxGLCanvas* canvas, const Pointfs& shape) it->second->set_bed_shape(shape); } -void GLCanvas3DManager::set_cutting_plane(wxGLCanvas* canvas, float z, const ExPolygons& polygons) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->set_cutting_plane(z, polygons); -} - void GLCanvas3DManager::set_color_by(wxGLCanvas* canvas, const std::string& value) { CanvasesMap::iterator it = _get_canvas(canvas); diff --git a/src/slic3r/GUI/GLCanvas3DManager.hpp b/src/slic3r/GUI/GLCanvas3DManager.hpp index 237d3558c4..097d3a118f 100644 --- a/src/slic3r/GUI/GLCanvas3DManager.hpp +++ b/src/slic3r/GUI/GLCanvas3DManager.hpp @@ -99,8 +99,6 @@ public: void set_bed_shape(wxGLCanvas* canvas, const Pointfs& shape); - void set_cutting_plane(wxGLCanvas* canvas, float z, const ExPolygons& polygons); - void set_color_by(wxGLCanvas* canvas, const std::string& value); bool is_layers_editing_enabled(wxGLCanvas* canvas) const; diff --git a/src/slic3r/GUI/GLGizmo.cpp b/src/slic3r/GUI/GLGizmo.cpp index 8cbb850685..b8988077e7 100644 --- a/src/slic3r/GUI/GLGizmo.cpp +++ b/src/slic3r/GUI/GLGizmo.cpp @@ -1,4 +1,4 @@ -#include "../../libslic3r/libslic3r.h" +#include "libslic3r/libslic3r.h" #include "GLGizmo.hpp" #include "GUI.hpp" @@ -8,15 +8,29 @@ #include "PresetBundle.hpp" #include -#include "../../libslic3r/Geometry.hpp" +#include "libslic3r/Geometry.hpp" #include #include #include -#include +#include #include +#include + +#include +#include +#include +#include +#include +#include + +#include "GUI.hpp" +#include "GUI_Utils.hpp" +#include "GUI_App.hpp" + +// 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 }; @@ -252,6 +266,8 @@ void GLGizmoBase::render_grabbers_for_picking(const BoundingBoxf3& box) const } } +void GLGizmoBase::create_external_gizmo_widgets(wxWindow *parent) {} + void GLGizmoBase::set_tooltip(const std::string& tooltip) const { m_parent.set_tooltip(tooltip); @@ -259,9 +275,10 @@ void GLGizmoBase::set_tooltip(const std::string& tooltip) const std::string GLGizmoBase::format(float value, unsigned int decimals) const { - char buf[1024]; - ::sprintf(buf, "%.*f", decimals, value); - return buf; + size_t needed_size = std::snprintf(nullptr, 0, "%.*f", decimals, value); + std::string res(needed_size, '\0'); + std::snprintf(&res.front(), res.size(), "%.*f", decimals, value); + return res; } const float GLGizmoRotate::Offset = 5.0f; @@ -1770,5 +1787,237 @@ std::string GLGizmoSlaSupports::on_get_name() const return L("SLA Support Points"); } + + +// 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 GLGizmoCut::GrabberColor = { 1.0, 0.5, 0.0 }; + +GLGizmoCut::GLGizmoCut(GLCanvas3D& parent) + : GLGizmoBase(parent) + , m_cut_z(0.0) + , m_panel(nullptr) +{} + +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(); + }, wxID_OK); +} + +bool GLGizmoCut::on_init() +{ + // TODO: icon + + std::string path = resources_dir() + "/icons/overlay/"; + + if (!m_textures[Off].load_from_file(path + "cut_off.png", false)) { + return false; + } + + if (!m_textures[Hover].load_from_file(path + "cut_hover.png", false)) { + return false; + } + + if (!m_textures[On].load_from_file(path + "cut_on.png", false)) { + return false; + } + + m_grabbers.emplace_back(); + + return true; +} + +std::string GLGizmoCut::on_get_name() const +{ + return L("Cut"); +} + +void GLGizmoCut::on_set_state() +{ + // Reset m_cut_z on gizmo activation + if (get_state() == On) { + m_cut_z = 0.0; + } + + // Display or hide the extra panel + if (m_panel != nullptr) { + m_panel->display(get_state() == On); + } +} + +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; + m_max_z = box.size()(2) / 2.0; + 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) +{ + if (m_hover_id != -1) { + // Clamp the plane to the object's bounding box + const double new_z = m_start_z + calc_projection(data.mouse_ray); + m_cut_z = std::max(-m_max_z, std::min(m_max_z, new_z)); + } +} + +void GLGizmoCut::on_render(const GLCanvas3D::Selection& selection) const +{ + if (m_grabbers[0].dragging) { + set_tooltip("Z: " + format(m_cut_z, 2)); + } + + 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::perform_cut() +{ + const auto &selection = m_parent.get_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); +} + +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 index eac9693bcb..376c286904 100644 --- a/src/slic3r/GUI/GLGizmo.hpp +++ b/src/slic3r/GUI/GLGizmo.hpp @@ -6,7 +6,13 @@ #include "../../libslic3r/Point.hpp" #include "../../libslic3r/BoundingBox.hpp" +#include #include +#include + + +class wxWindow; + namespace Slic3r { @@ -120,6 +126,8 @@ public: void render(const GLCanvas3D::Selection& selection) const { on_render(selection); } void render_for_picking(const GLCanvas3D::Selection& selection) const { on_render_for_picking(selection); } + virtual void create_external_gizmo_widgets(wxWindow *parent); + protected: virtual bool on_init() = 0; virtual std::string on_get_name() const = 0; @@ -450,6 +458,43 @@ protected: bool on_is_activable(const GLCanvas3D::Selection& selection) const override; }; + +class GLGizmoCutPanel; + +class GLGizmoCut : public GLGizmoBase +{ + static const double Offset; + static const double Margin; + static const std::array GrabberColor; + + double m_cut_z; + double m_start_z; + double m_max_z; + Vec3d m_drag_pos; + Vec3d m_drag_center; + GLGizmoCutPanel *m_panel; + +public: + explicit GLGizmoCut(GLCanvas3D& parent); + + virtual void create_external_gizmo_widgets(wxWindow *parent); + +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); + virtual void on_render(const GLCanvas3D::Selection& selection) const; + virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const; + +private: + void perform_cut(); + double calc_projection(const Linef3& mouse_ray) const; +}; + + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index 26ca4a4dca..0e99f88ff8 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -24,7 +24,6 @@ wxDEFINE_EVENT(EVT_GLTOOLBAR_MORE, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_FEWER, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_SPLIT_OBJECTS, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_SPLIT_VOLUMES, SimpleEvent); -wxDEFINE_EVENT(EVT_GLTOOLBAR_CUT, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_LAYERSEDITING, SimpleEvent); diff --git a/src/slic3r/GUI/GLToolbar.hpp b/src/slic3r/GUI/GLToolbar.hpp index 6669bc9664..dde62c6452 100644 --- a/src/slic3r/GUI/GLToolbar.hpp +++ b/src/slic3r/GUI/GLToolbar.hpp @@ -24,7 +24,6 @@ wxDECLARE_EVENT(EVT_GLTOOLBAR_MORE, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_FEWER, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_SPLIT_OBJECTS, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_SPLIT_VOLUMES, SimpleEvent); -wxDECLARE_EVENT(EVT_GLTOOLBAR_CUT, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_LAYERSEDITING, SimpleEvent); class GLToolbarItem diff --git a/src/slic3r/GUI/GUI_Utils.cpp b/src/slic3r/GUI/GUI_Utils.cpp index c9fa96f4c6..43b3c38f71 100644 --- a/src/slic3r/GUI/GUI_Utils.cpp +++ b/src/slic3r/GUI/GUI_Utils.cpp @@ -15,6 +15,18 @@ namespace Slic3r { namespace GUI { +wxTopLevelWindow* find_toplevel_parent(wxWindow *window) +{ + for (; window != nullptr; window = window->GetParent()) { + if (window->IsTopLevel()) { + return dynamic_cast(window); + } + } + + return nullptr; +} + + CheckboxFileDialog::ExtraPanel::ExtraPanel(wxWindow *parent) : wxPanel(parent, wxID_ANY) { diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp index fe96e5a1b8..9cee986b0c 100644 --- a/src/slic3r/GUI/GUI_Utils.hpp +++ b/src/slic3r/GUI/GUI_Utils.hpp @@ -6,6 +6,7 @@ #include +#include #include #include #include @@ -19,6 +20,48 @@ namespace Slic3r { namespace GUI { +wxTopLevelWindow* find_toplevel_parent(wxWindow *window); + + +class EventGuard +{ +public: + EventGuard() {} + EventGuard(const EventGuard&) = delete; + EventGuard(EventGuard &&other) : unbinder(std::move(other.unbinder)) {} + + ~EventGuard() { + if (unbinder) { + unbinder(false); + } + } + + template void bind(wxEvtHandler *emitter, const EvTag &type, Fun fun) + { + // This is a way to type-erase both the event type as well as the handler: + + unbinder = std::move([=](bool bind) { + if (bind) { + emitter->Bind(type, fun); + } else { + emitter->Unbind(type, fun); + } + }); + + unbinder(true); + } + + EventGuard& operator=(const EventGuard&) = delete; + EventGuard& operator=(EventGuard &&other) + { + unbinder.swap(other.unbinder); + return *this; + } +private: + std::function unbinder; +}; + + class CheckboxFileDialog : public wxFileDialog { public: diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index c5883dcf52..85ac442f71 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include "Tab.hpp" @@ -100,7 +101,6 @@ wxFrame(NULL, wxID_ANY, SLIC3R_BUILD, wxDefaultPosition, wxDefaultSize, wxDEFAUL }); update_ui_from_settings(); - return; } void MainFrame::init_tabpanel() diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 7722d0b65f..b324586c45 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -81,6 +81,7 @@ public: MainFrame(const bool no_plater, const bool loaded); ~MainFrame() {} + Plater* plater() { return m_plater; } void init_tabpanel(); const std::map& options_tabs() const { return m_options_tabs; } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 7768df7dd5..cb0e04e37c 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -20,7 +20,8 @@ #include #include #include -#include +#include +#include #include "libslic3r/libslic3r.h" #include "libslic3r/PrintConfig.hpp" @@ -881,6 +882,7 @@ struct Plater::priv // GUI elements wxNotebook *notebook; Sidebar *sidebar; + wxWindow *panel3d; wxGLCanvas *canvas3D; // TODO: Use GLCanvas3D when we can Preview *preview; @@ -959,7 +961,6 @@ struct Plater::priv void on_action_add(SimpleEvent&); void on_action_split_objects(SimpleEvent&); void on_action_split_volumes(SimpleEvent&); - void on_action_cut(SimpleEvent&); void on_action_layersediting(SimpleEvent&); void on_object_select(SimpleEvent&); @@ -978,7 +979,6 @@ private: bool can_decrease_instances() const; bool can_split_to_objects() const; bool can_split_to_volumes() const; - bool can_cut_object() const; bool layers_height_allowed() const; bool can_delete_all() const; bool can_arrange() const; @@ -988,19 +988,20 @@ private: const std::regex Plater::priv::pattern_bundle(".*[.](amf|amf[.]xml|zip[.]amf|3mf|prusa)", std::regex::icase); const std::regex Plater::priv::pattern_3mf(".*3mf", std::regex::icase); const std::regex Plater::priv::pattern_zip_amf(".*[.]zip[.]amf", std::regex::icase); -Plater::priv::priv(Plater *q, MainFrame *main_frame) : - q(q), - main_frame(main_frame), - config(Slic3r::DynamicPrintConfig::new_from_defaults_keys({ +Plater::priv::priv(Plater *q, MainFrame *main_frame) + : q(q) + , main_frame(main_frame) + , config(Slic3r::DynamicPrintConfig::new_from_defaults_keys({ "bed_shape", "complete_objects", "extruder_clearance_radius", "skirts", "skirt_distance", "brim_width", "variable_layer_height", "serial_port", "serial_speed", "host_type", "print_host", "printhost_apikey", "printhost_cafile", "nozzle_diameter", "single_extruder_multi_material", "wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", - "extruder_colour", "filament_colour", "max_print_height", "printer_model", "printer_technology" - })), - notebook(new wxNotebook(q, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_BOTTOM)), - sidebar(new Sidebar(q)), - canvas3D(GLCanvas3DManager::create_wxglcanvas(notebook)) + "extruder_colour", "filament_colour", "max_print_height", "printer_model", "printer_technology" + })) + , notebook(new wxNotebook(q, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_BOTTOM)) + , sidebar(new Sidebar(q)) + , panel3d(new wxWindow(notebook, wxID_ANY)) + , canvas3D(GLCanvas3DManager::create_wxglcanvas(panel3d)) #if ENABLE_NEW_MENU_LAYOUT , project_filename(wxEmptyString) #endif // ENABLE_NEW_MENU_LAYOUT @@ -1027,9 +1028,19 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) : _3DScene::add_canvas(canvas3D); _3DScene::allow_multisample(canvas3D, GLCanvas3DManager::can_multisample()); - notebook->AddPage(canvas3D, _(L("3D"))); + + auto *panel3dsizer = new wxBoxSizer(wxVERTICAL); + panel3dsizer->Add(canvas3D, 1, wxEXPAND); + auto *panel_gizmo_widgets = new wxPanel(panel3d, wxID_ANY); + panel_gizmo_widgets->SetSizer(new wxBoxSizer(wxVERTICAL)); + panel3dsizer->Add(panel_gizmo_widgets, 0, wxEXPAND); + + panel3d->SetSizer(panel3dsizer); + notebook->AddPage(panel3d, _(L("3D"))); preview = new GUI::Preview(notebook, config, &print, &gcode_preview_data, [this](){ schedule_background_process(); }); + _3DScene::get_canvas(canvas3D)->set_external_gizmo_widgets_parent(panel_gizmo_widgets); + // XXX: If have OpenGL _3DScene::enable_picking(canvas3D, true); _3DScene::enable_moving(canvas3D, true); @@ -1092,7 +1103,6 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) : canvas3D->Bind(EVT_GLTOOLBAR_FEWER, [q](SimpleEvent&) { q->decrease_instances(); }); canvas3D->Bind(EVT_GLTOOLBAR_SPLIT_OBJECTS, &priv::on_action_split_objects, this); canvas3D->Bind(EVT_GLTOOLBAR_SPLIT_VOLUMES, &priv::on_action_split_volumes, this); - canvas3D->Bind(EVT_GLTOOLBAR_CUT, &priv::on_action_cut, this); canvas3D->Bind(EVT_GLTOOLBAR_LAYERSEDITING, &priv::on_action_layersediting, this); // Preview events: @@ -1475,7 +1485,6 @@ void Plater::priv::selection_changed() _3DScene::enable_toolbar_item(canvas3D, "fewer", can_decrease_instances()); _3DScene::enable_toolbar_item(canvas3D, "splitobjects", can_split_to_objects()); _3DScene::enable_toolbar_item(canvas3D, "splitvolumes", can_split_to_volumes()); - _3DScene::enable_toolbar_item(canvas3D, "cut", can_cut_object()); _3DScene::enable_toolbar_item(canvas3D, "layersediting", layers_height_allowed()); // forces a frame render to update the view (to avoid a missed update if, for example, the context menu appears) _3DScene::render(canvas3D); @@ -1970,11 +1979,6 @@ void Plater::priv::on_action_split_volumes(SimpleEvent&) split_volume(); } -void Plater::priv::on_action_cut(SimpleEvent&) -{ - // TODO -} - void Plater::priv::on_action_layersediting(SimpleEvent&) { bool enable = !_3DScene::is_layers_editing_enabled(canvas3D); @@ -2114,12 +2118,6 @@ bool Plater::priv::can_split_to_volumes() const return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && !model.objects[obj_idx]->is_multiparts(); } -bool Plater::priv::can_cut_object() const -{ - int obj_idx = get_selected_object_idx(); - return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()); -} - bool Plater::priv::layers_height_allowed() const { int obj_idx = get_selected_object_idx(); @@ -2313,6 +2311,21 @@ void Plater::set_number_of_copies(/*size_t num*/) decrease_instances(-diff); } +void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z) +{ + wxCHECK_RET(obj_idx < p->model.objects.size(), "obj_idx out of bounds"); + auto *object = p->model.objects[obj_idx]; + + wxCHECK_RET(instance_idx < object->instances.size(), "instance_idx out of bounds"); + + const auto new_objects = object->cut(instance_idx, z); + + remove(obj_idx); + p->load_model_objects(new_objects); + + p->arrange(); +} + void Plater::export_gcode(fs::path output_path) { if (p->model.objects.empty()) diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index f23fbe72f3..d6716c620a 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -135,6 +135,8 @@ public: void decrease_instances(size_t num = 1); void set_number_of_copies(/*size_t num*/); + void cut(size_t obj_idx, size_t instance_idx, coordf_t z); + // Note: empty path means "use the default" void export_gcode(boost::filesystem::path output_path = boost::filesystem::path()); void export_stl();