From 1c4d43b3a498530908eaf4877289548a13984511 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 27 Feb 2023 12:34:08 +0100 Subject: [PATCH 01/17] Cut WIP: Use contours to perform a cut. --- src/libslic3r/Model.cpp | 1 - src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 159 ++++++++++++++++++++--- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 12 +- src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp | 10 +- src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp | 3 +- src/slic3r/GUI/MeshUtils.cpp | 8 +- src/slic3r/GUI/MeshUtils.hpp | 3 +- 7 files changed, 171 insertions(+), 25 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index f2a1db3c9..74dabb8e1 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1565,7 +1565,6 @@ void ModelObject::process_volume_cut(ModelVolume* volume, const Transform3d& ins if (attributes.has(ModelObjectCutAttribute::KeepLower)) lower_mesh = TriangleMesh(lower_its); } - void ModelObject::process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, Vec3d& local_displace) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index f5ee14812..9d9344617 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -289,11 +289,28 @@ bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event) gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), mouse_event.CmdDown()); } else if (m_hover_id == CutPlane) { - if (mouse_event.LeftDown()) - m_was_cut_plane_dragged = false; - else if (mouse_event.LeftUp() && !m_was_cut_plane_dragged) + if (mouse_event.LeftDown()) { + m_was_cut_plane_dragged = m_was_contour_selected = false; + + // disable / enable current contour + Vec3d pos; + Vec3d pos_world; + m_was_contour_selected = unproject_on_cut_plane(mouse_pos.cast(), pos, pos_world, false); + if (m_was_contour_selected) { + // Following would inform the clipper about the mouse click, so it can + // toggle the respective contour as disabled. + m_c->object_clipper()->pass_mouse_click(pos_world); + process_contours(); + return true; + } + + } + else if (mouse_event.LeftUp() && !m_was_cut_plane_dragged && !m_was_contour_selected) flip_cut_plane(); } + + if (!m_cut_by_contour_glmodels.empty()) + m_parent.toggle_model_objects_visibility(false); return true; } @@ -429,6 +446,9 @@ void GLGizmoCut3D::update_clipper() on_register_raycasters_for_picking(); else update_raycasters_for_picking_transform(); + + if (!m_c->object_clipper()->has_disable_contour()) + reset_cut_by_contours(); } void GLGizmoCut3D::set_center(const Vec3d& center, bool update_tbb /*=false*/) @@ -559,6 +579,8 @@ void GLGizmoCut3D::render_move_center_input(int axis) Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Move cut plane"), UndoRedo::SnapshotType::GizmoAction); set_center(move, true); m_ar_plane_center = m_plane_center; + + reset_cut_by_contours(); } } @@ -1160,10 +1182,14 @@ void GLGizmoCut3D::on_dragging(const UpdateData& data) { if (m_hover_id < 0) return; - if (m_hover_id == Z || m_hover_id == CutPlane) + if (m_hover_id == Z || m_hover_id == CutPlane) { dragging_grabber_z(data); - else if (m_hover_id == X || m_hover_id == Y) + reset_cut_by_contours(); + } + else if (m_hover_id == X || m_hover_id == Y) { dragging_grabber_xy(data); + reset_cut_by_contours(); + } else if (m_hover_id >= m_connectors_group_id && m_connector_mode == CutConnectorMode::Manual) dragging_connector(data); } @@ -1358,6 +1384,31 @@ void GLGizmoCut3D::render_clipper_cut() ::glEnable(GL_DEPTH_TEST); } +void GLGizmoCut3D::render_colored_parts() +{ + if (m_cut_by_contour_glmodels.empty()) + return; + assert(m_cut_by_contour_objects[0]->volumes.size() == m_cut_by_contour_glmodels.size()); + + if (GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light")) { + const Camera& camera = wxGetApp().plater()->get_camera(); + + shader->start_using(); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + + const Vec3d inst_offset = m_cut_by_contour_objects[0]->instances[0]->get_offset(); + const Transform3d view_inst_matrix= camera.get_view_matrix() * translation_transform(inst_offset); + + for (size_t id = 0; id < m_cut_by_contour_glmodels.size(); id++) { + const Vec3d volume_offset = m_cut_by_contour_objects[0]->volumes[id]->get_offset(); + shader->set_uniform("view_model_matrix", view_inst_matrix * translation_transform(volume_offset)); + m_cut_by_contour_glmodels[id].render(); + } + + shader->stop_using(); + } +} + void GLGizmoCut3D::on_render() { if (m_state == On) { @@ -1374,6 +1425,9 @@ void GLGizmoCut3D::on_render() render_connectors(); + if (!m_connectors_editing) + render_colored_parts(); + render_clipper_cut(); if (!m_hide_cut_plane && !m_connectors_editing) { @@ -1587,6 +1641,8 @@ void GLGizmoCut3D::reset_cut_plane() set_center(m_bb_center); m_start_dragging_m = m_rotation_m = Transform3d::Identity(); m_ar_plane_center = m_plane_center; + + reset_cut_by_contours(); } void GLGizmoCut3D::invalidate_cut_plane() @@ -1623,6 +1679,62 @@ void GLGizmoCut3D::flip_cut_plane() update_clipper(); } +void GLGizmoCut3D::reset_cut_by_contours() +{ + if (!m_cut_by_contour_objects.empty()) { + m_cut_by_contour_objects.clear(); + m_cut_by_contour_glmodels.clear(); + m_disabled_contours.clear(); + + m_parent.toggle_model_objects_visibility(true); + } +} + +void GLGizmoCut3D::process_contours() +{ + reset_cut_by_contours(); + + if (!m_c->object_clipper()->has_disable_contour()) + return; + + const Selection& selection = m_parent.get_selection(); + const ModelObjectPtrs& model_objects = selection.get_model()->objects; + + wxBusyCursor wait; + + const int instance_idx = selection.get_instance_idx(); + const int object_idx = selection.get_object_idx(); + + m_cut_by_contour_objects = model_objects[object_idx]->cut(instance_idx, get_cut_matrix(selection), + ModelObjectCutAttribute::KeepUpper | + ModelObjectCutAttribute::KeepLower | + ModelObjectCutAttribute::KeepAsParts | + ModelObjectCutAttribute::InvalidateCutInfo); + + assert(m_cut_by_contour_objects.size() == 1); + + const ModelVolumePtrs& volumes = m_cut_by_contour_objects[0]->volumes; + + // split to parts + for (int id = int(volumes.size())-1; id >= 0; id--) + if (volumes[id]->is_splittable()) + volumes[id]->split(1); + + m_cut_by_contour_glmodels.reserve(volumes.size()); + for (const ModelVolume* volume : volumes) { + assert(volume != nullptr); + + GLModel glmodel; + // any condition to test + if (volume->name.find("_A") != std::string::npos) + glmodel.set_color({ 0.5f, 0.9f, 0.9f, 0.5f }); // glmodel.set_color(UPPER_PART_COLOR); + else + glmodel.set_color({ 0.9f, 0.5f, 0.9f, 0.5f }); // glmodel.set_color(LOWER_PART_COLOR); + + m_cut_by_contour_glmodels.push_back(glmodel); + } +} + void GLGizmoCut3D::render_flip_plane_button(bool disable_pred /*=false*/) { ImGui::SameLine(); @@ -1949,7 +2061,7 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) bool GLGizmoCut3D::is_outside_of_cut_contour(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos) { // check if connector pos is out of clipping plane - if (m_c->object_clipper() && !m_c->object_clipper()->is_projection_inside_cut(cur_pos)) { + if (m_c->object_clipper() && !m_c->object_clipper()->is_projection_inside_cut(cur_pos, true)) { m_info_stats.outside_cut_contour++; return true; } @@ -1975,7 +2087,7 @@ bool GLGizmoCut3D::is_outside_of_cut_contour(size_t idx, const CutConnectors& co its_transform(mesh, translation_transform(cur_pos) * m_rotation_m); for (auto vertex : vertices) { - if (m_c->object_clipper() && !m_c->object_clipper()->is_projection_inside_cut(vertex.cast())) { + if (m_c->object_clipper() && !m_c->object_clipper()->is_projection_inside_cut(vertex.cast(), true)) { m_info_stats.outside_cut_contour++; return true; } @@ -2132,6 +2244,24 @@ void GLGizmoCut3D::apply_connectors_in_model(ModelObject* mo, bool &create_dowel } } +Transform3d GLGizmoCut3D::get_cut_matrix(const Selection& selection) +{ + const int instance_idx = selection.get_instance_idx(); + const int object_idx = selection.get_object_idx(); + ModelObject* mo = selection.get_model()->objects[object_idx]; + if (!mo) + return Transform3d::Identity(); + + // m_cut_z is the distance from the bed. Subtract possible SLA elevation. + const double sla_shift_z = selection.get_first_volume()->get_sla_shift_z(); + + const Vec3d instance_offset = mo->instances[instance_idx]->get_offset(); + Vec3d cut_center_offset = m_plane_center - instance_offset; + cut_center_offset[Z] -= sla_shift_z; + + return translation_transform(cut_center_offset) * m_rotation_m; +} + void GLGizmoCut3D::perform_cut(const Selection& selection) { if (!can_perform_cut()) @@ -2149,13 +2279,6 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) // deactivate CutGizmo and than perform a cut m_parent.reset_all_gizmos(); - // m_cut_z is the distance from the bed. Subtract possible SLA elevation. - const double sla_shift_z = selection.get_first_volume()->get_sla_shift_z(); - - const Vec3d instance_offset = mo->instances[instance_idx]->get_offset(); - Vec3d cut_center_offset = m_plane_center - instance_offset; - cut_center_offset[Z] -= sla_shift_z; - // perform cut { Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Cut by Plane")); @@ -2165,7 +2288,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) // update connectors pos as offset of its center before cut performing apply_connectors_in_model(mo, create_dowels_as_separate_object); - plater->cut(object_idx, instance_idx, translation_transform(cut_center_offset) * m_rotation_m, + plater->cut(object_idx, instance_idx, get_cut_matrix(selection), only_if(has_connectors ? true : m_keep_upper, ModelObjectCutAttribute::KeepUpper) | only_if(has_connectors ? true : m_keep_lower, ModelObjectCutAttribute::KeepLower) | only_if(has_connectors ? false: m_keep_as_parts, ModelObjectCutAttribute::KeepAsParts) | @@ -2182,7 +2305,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) // Unprojects the mouse position on the mesh and saves hit point and normal of the facet into pos_and_normal // Return false if no intersection was found, true otherwise. -bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, Vec3d& pos, Vec3d& pos_world) +bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, Vec3d& pos, Vec3d& pos_world, bool respect_disabled_contour/* = true*/) { const float sla_shift = m_c->selection_info()->get_sla_shift(); @@ -2204,7 +2327,7 @@ bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, Vec3d& po } else return false; - if (! m_c->object_clipper()->is_projection_inside_cut(hit)) + if (! m_c->object_clipper()->is_projection_inside_cut(hit, respect_disabled_contour)) return false; // recalculate hit to object's local position @@ -2284,6 +2407,8 @@ bool GLGizmoCut3D::process_cut_line(SLAGizmoEventType action, const Vec2d& mouse } if (cut_line_processing()) { + reset_cut_by_contours(); + m_line_end = pt; if (action == SLAGizmoEventType::LeftDown || action == SLAGizmoEventType::LeftUp) { Vec3d line_dir = m_line_end - m_line_beg; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 49d1848e4..ca2084aa1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -135,6 +135,12 @@ class GLGizmoCut3D : public GLGizmoBase bool m_has_invalid_connector{ false }; bool m_was_cut_plane_dragged { false }; + bool m_was_contour_selected { false }; + + std::vector m_cut_by_contour_glmodels; + ModelObjectPtrs m_cut_by_contour_objects; + // attributes for disabled contours + std::vector> m_disabled_contours; bool m_show_shortcuts{ false }; std::vector> m_shortcuts; @@ -176,7 +182,7 @@ public: GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); std::string get_tooltip() const override; - bool unproject_on_cut_plane(const Vec2d& mouse_pos, Vec3d& pos, Vec3d& pos_world); + bool unproject_on_cut_plane(const Vec2d& mouse_pos, Vec3d& pos, Vec3d& pos_world, bool respect_disabled_contour = true); bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); bool is_in_editing_mode() const override { return m_connectors_editing; } @@ -229,6 +235,8 @@ protected: void reset_cut_plane(); void set_connectors_editing(bool connectors_editing); void flip_cut_plane(); + void process_contours(); + void reset_cut_by_contours(); void render_flip_plane_button(bool disable_pred = false); void add_vertical_scaled_interval(float interval); void add_horizontal_scaled_interval(float interval); @@ -257,6 +265,7 @@ protected: std::string get_action_snapshot_name() const override { return _u8L("Cut gizmo editing"); } void data_changed(bool is_serializing) override; + Transform3d get_cut_matrix(const Selection& selection); private: void set_center(const Vec3d& center, bool update_tbb = false); @@ -290,6 +299,7 @@ private: void init_picking_models(); void init_rendering_items(); void render_clipper_cut(); + void render_colored_parts(); void clear_selection(); void reset_connectors(); void init_connector_shapes(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index 4edb01c2b..b642da5ca 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -388,9 +388,10 @@ void ObjectClipper::render_cut() const } } -bool ObjectClipper::is_projection_inside_cut(const Vec3d& point) const +bool ObjectClipper::is_projection_inside_cut(const Vec3d& point, bool respect_disabled_contour) const { - return m_clp_ratio != 0. && std::any_of(m_clippers.begin(), m_clippers.end(), [point](const auto& cl) { return cl.first->is_projection_inside_cut(point); }); + return m_clp_ratio != 0. && std::any_of(m_clippers.begin(), m_clippers.end(), + [point, respect_disabled_contour](const auto& cl) { return cl.first->is_projection_inside_cut(point, respect_disabled_contour); }); } bool ObjectClipper::has_valid_contour() const @@ -398,6 +399,11 @@ bool ObjectClipper::has_valid_contour() const return m_clp_ratio != 0. && std::any_of(m_clippers.begin(), m_clippers.end(), [](const auto& cl) { return cl.first->has_valid_contour(); }); } +bool ObjectClipper::has_disable_contour() const +{ + return m_clp_ratio != 0. && std::any_of(m_clippers.begin(), m_clippers.end(), [](const auto& cl) { return cl.first->has_disable_contour(); }); +} + void ObjectClipper::set_position_by_ratio(double pos, bool keep_normal) { const ModelObject* mo = get_pool()->selection_info()->model_object(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp index e0d2cdb68..15bf65814 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp @@ -250,8 +250,9 @@ public: void pass_mouse_click(const Vec3d& pt); std::vector get_disabled_contours() const; - bool is_projection_inside_cut(const Vec3d& point_in) const; + bool is_projection_inside_cut(const Vec3d& point_in, bool respect_disabled_contour) const; bool has_valid_contour() const; + bool has_disable_contour() const; protected: diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index 59be6cabb..9f6cd1960 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -146,7 +146,7 @@ void MeshClipper::render_contour(const ColorRGBA& color) curr_shader->start_using(); } -bool MeshClipper::is_projection_inside_cut(const Vec3d& point_in) const +bool MeshClipper::is_projection_inside_cut(const Vec3d& point_in, bool respect_disabled_contour) const { if (!m_result || m_result->cut_islands.empty()) return false; @@ -155,7 +155,7 @@ bool MeshClipper::is_projection_inside_cut(const Vec3d& point_in) const for (const CutIsland& isl : m_result->cut_islands) { if (isl.expoly_bb.contains(pt_2d) && isl.expoly.contains(pt_2d)) - return !isl.disabled; + return respect_disabled_contour ? !isl.disabled : true; } return false; } @@ -165,6 +165,10 @@ bool MeshClipper::has_valid_contour() const return m_result && std::any_of(m_result->cut_islands.begin(), m_result->cut_islands.end(), [](const CutIsland& isl) { return !isl.expoly.empty(); }); } +bool MeshClipper::has_disable_contour() const +{ + return m_result && std::any_of(m_result->cut_islands.begin(), m_result->cut_islands.end(), [](const CutIsland& isl) { return isl.disabled; }); +} void MeshClipper::pass_mouse_click(const Vec3d& point_in) { diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp index b10fa5ff8..0a854e5f3 100644 --- a/src/slic3r/GUI/MeshUtils.hpp +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -120,8 +120,9 @@ public: void pass_mouse_click(const Vec3d& pt); - bool is_projection_inside_cut(const Vec3d& point) const; + bool is_projection_inside_cut(const Vec3d& point, bool respect_disabled_contour) const; bool has_valid_contour() const; + bool has_disable_contour() const; private: void recalculate_triangles(); From e5b66f125f4041b2da7f3835bf01748bb49b2fda Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 23 Feb 2023 01:45:14 +0100 Subject: [PATCH 02/17] Cut: experiment with selecting parts: Instead of clicking on contours, the user would click on the object itself. Right mouse button is used currently. Many loose ends ! Really just an experiment. --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 171 +++++++++++++---------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 24 +++- src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp | 36 ++--- src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp | 6 +- src/slic3r/GUI/MeshUtils.cpp | 28 +--- src/slic3r/GUI/MeshUtils.hpp | 7 +- 6 files changed, 148 insertions(+), 124 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 9d9344617..edf91aba8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -299,8 +299,8 @@ bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event) if (m_was_contour_selected) { // Following would inform the clipper about the mouse click, so it can // toggle the respective contour as disabled. - m_c->object_clipper()->pass_mouse_click(pos_world); - process_contours(); + //m_c->object_clipper()->pass_mouse_click(pos_world); + //process_contours(); return true; } @@ -309,7 +309,7 @@ bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event) flip_cut_plane(); } - if (!m_cut_by_contour_glmodels.empty()) + if (m_part_selection.valid) m_parent.toggle_model_objects_visibility(false); return true; } @@ -351,6 +351,14 @@ bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event) return true; } else if (mouse_event.RightDown()) { + if (! m_connectors_editing) { + // Check the internal part raycasters. + if (! m_part_selection.valid) + process_contours(); + m_part_selection.toggle_selection(mouse_pos); + return true; + } + if (m_parent.get_selection().get_object_idx() != -1 && gizmo_event(SLAGizmoEventType::RightDown, mouse_pos, false, false, false)) { // we need to set the following right up as processed to avoid showing @@ -446,9 +454,6 @@ void GLGizmoCut3D::update_clipper() on_register_raycasters_for_picking(); else update_raycasters_for_picking_transform(); - - if (!m_c->object_clipper()->has_disable_contour()) - reset_cut_by_contours(); } void GLGizmoCut3D::set_center(const Vec3d& center, bool update_tbb /*=false*/) @@ -1384,11 +1389,10 @@ void GLGizmoCut3D::render_clipper_cut() ::glEnable(GL_DEPTH_TEST); } -void GLGizmoCut3D::render_colored_parts() +void GLGizmoCut3D::PartSelection::render(const Vec3d* normal) { - if (m_cut_by_contour_glmodels.empty()) + if (! valid) return; - assert(m_cut_by_contour_objects[0]->volumes.size() == m_cut_by_contour_glmodels.size()); if (GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light")) { const Camera& camera = wxGetApp().plater()->get_camera(); @@ -1396,19 +1400,44 @@ void GLGizmoCut3D::render_colored_parts() shader->start_using(); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); - const Vec3d inst_offset = m_cut_by_contour_objects[0]->instances[0]->get_offset(); + // FIXME: Cache the transforms. + + const Vec3d inst_offset = model_object->instances[0]->get_offset(); const Transform3d view_inst_matrix= camera.get_view_matrix() * translation_transform(inst_offset); - for (size_t id = 0; id < m_cut_by_contour_glmodels.size(); id++) { - const Vec3d volume_offset = m_cut_by_contour_objects[0]->volumes[id]->get_offset(); + for (size_t id=0; idvolumes[id]->get_offset(); shader->set_uniform("view_model_matrix", view_inst_matrix * translation_transform(volume_offset)); - m_cut_by_contour_glmodels[id].render(); + //parts[id].glmodel.set_color(parts[id].selected ? ColorRGBA(1.f, 0.f, 0.f, 1.f) : ColorRGBA(0.f, 1.f, 0.f, 1.f)); + parts[id].glmodel.set_color(parts[id].selected ? UPPER_PART_COLOR : LOWER_PART_COLOR); + parts[id].glmodel.render(); } shader->stop_using(); } } + +void GLGizmoCut3D::PartSelection::toggle_selection(const Vec2d& mouse_pos) +{ + // FIXME: Cache the transforms. + const Camera& camera = wxGetApp().plater()->get_camera(); + + Vec3f pos; + Vec3f normal; + + for (size_t id=0; idvolumes[id]->get_offset(); + Transform3d tr = model_object->instances.front()->get_matrix() * model_object->volumes[id]->get_matrix(); + if (parts[id].raycaster.unproject_on_mesh(mouse_pos, tr, camera, pos, normal)) { + parts[id].selected = ! parts[id].selected; + return; + } + } +} + void GLGizmoCut3D::on_render() { if (m_state == On) { @@ -1426,7 +1455,9 @@ void GLGizmoCut3D::on_render() render_connectors(); if (!m_connectors_editing) - render_colored_parts(); + m_part_selection.render(); + else + m_part_selection.render(&m_cut_normal); render_clipper_cut(); @@ -1679,60 +1710,68 @@ void GLGizmoCut3D::flip_cut_plane() update_clipper(); } -void GLGizmoCut3D::reset_cut_by_contours() + +GLGizmoCut3D::PartSelection::PartSelection(ModelObject* mo, const Vec3d& center, const Vec3d& normal) { - if (!m_cut_by_contour_objects.empty()) { - m_cut_by_contour_objects.clear(); - m_cut_by_contour_glmodels.clear(); - m_disabled_contours.clear(); + model_object = mo; // FIXME: Ownership. - m_parent.toggle_model_objects_visibility(true); - } -} - -void GLGizmoCut3D::process_contours() -{ - reset_cut_by_contours(); - - if (!m_c->object_clipper()->has_disable_contour()) - return; - - const Selection& selection = m_parent.get_selection(); - const ModelObjectPtrs& model_objects = selection.get_model()->objects; - - wxBusyCursor wait; - - const int instance_idx = selection.get_instance_idx(); - const int object_idx = selection.get_object_idx(); - - m_cut_by_contour_objects = model_objects[object_idx]->cut(instance_idx, get_cut_matrix(selection), - ModelObjectCutAttribute::KeepUpper | - ModelObjectCutAttribute::KeepLower | - ModelObjectCutAttribute::KeepAsParts | - ModelObjectCutAttribute::InvalidateCutInfo); - - assert(m_cut_by_contour_objects.size() == 1); - - const ModelVolumePtrs& volumes = m_cut_by_contour_objects[0]->volumes; + const ModelVolumePtrs& volumes = mo->volumes; // split to parts for (int id = int(volumes.size())-1; id >= 0; id--) if (volumes[id]->is_splittable()) volumes[id]->split(1); - m_cut_by_contour_glmodels.reserve(volumes.size()); + parts.clear(); for (const ModelVolume* volume : volumes) { assert(volume != nullptr); + parts.emplace_back(Part{GLModel(), MeshRaycaster(volume->mesh()), true}); + parts.back().glmodel.set_color({ 0.f, 0.f, 1.f, 1.f }); + parts.back().glmodel.init_from(volume->mesh()); - GLModel glmodel; - // any condition to test - if (volume->name.find("_A") != std::string::npos) - glmodel.set_color({ 0.5f, 0.9f, 0.9f, 0.5f }); // glmodel.set_color(UPPER_PART_COLOR); - else - glmodel.set_color({ 0.9f, 0.5f, 0.9f, 0.5f }); // glmodel.set_color(LOWER_PART_COLOR); - - m_cut_by_contour_glmodels.push_back(glmodel); + // Now check whether this part is below or above the plane. + Transform3d tr = (model_object->instances.front()->get_matrix() * volume->get_matrix()).inverse(); + Vec3f pos = (tr * center).cast(); + Vec3f norm = (tr.linear().inverse().transpose() * normal).cast(); + for (const Vec3f& v : volume->mesh().its.vertices) { + double p = (v - pos).dot(norm); + if (std::abs(p) > EPSILON) { + parts.back().selected = p > 0.; + break; + } + } } + + valid = true; +} + + +void GLGizmoCut3D::reset_cut_by_contours() +{ + m_part_selection = PartSelection(); + m_parent.toggle_model_objects_visibility(true); +} + +void GLGizmoCut3D::process_contours() +{ + reset_cut_by_contours(); + + const Selection& selection = m_parent.get_selection(); + const ModelObjectPtrs& model_objects = selection.get_model()->objects; + + wxBusyCursor wait; + const int instance_idx = selection.get_instance_idx(); + const int object_idx = selection.get_object_idx(); + + ModelObjectPtrs moptrs = model_objects[object_idx]->cut(instance_idx, get_cut_matrix(selection), + ModelObjectCutAttribute::KeepUpper | + ModelObjectCutAttribute::KeepLower | + ModelObjectCutAttribute::KeepAsParts | + ModelObjectCutAttribute::InvalidateCutInfo); + + assert(moptrs.size() == 1); + m_part_selection = PartSelection(moptrs.front(), m_plane_center, m_cut_normal); + m_parent.toggle_model_objects_visibility(false); } void GLGizmoCut3D::render_flip_plane_button(bool disable_pred /*=false*/) @@ -2061,7 +2100,7 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) bool GLGizmoCut3D::is_outside_of_cut_contour(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos) { // check if connector pos is out of clipping plane - if (m_c->object_clipper() && !m_c->object_clipper()->is_projection_inside_cut(cur_pos, true)) { + if (m_c->object_clipper() && m_c->object_clipper()->is_projection_inside_cut(cur_pos) == -1) { m_info_stats.outside_cut_contour++; return true; } @@ -2087,7 +2126,7 @@ bool GLGizmoCut3D::is_outside_of_cut_contour(size_t idx, const CutConnectors& co its_transform(mesh, translation_transform(cur_pos) * m_rotation_m); for (auto vertex : vertices) { - if (m_c->object_clipper() && !m_c->object_clipper()->is_projection_inside_cut(vertex.cast(), true)) { + if (m_c->object_clipper() && m_c->object_clipper()->is_projection_inside_cut(vertex.cast()) == -1) { m_info_stats.outside_cut_contour++; return true; } @@ -2327,7 +2366,7 @@ bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, Vec3d& po } else return false; - if (! m_c->object_clipper()->is_projection_inside_cut(hit, respect_disabled_contour)) + if (m_c->object_clipper()->is_projection_inside_cut(hit) == -1) return false; // recalculate hit to object's local position @@ -2555,20 +2594,8 @@ bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_posi if (!m_keep_upper || !m_keep_lower) return false; - if (!m_connectors_editing) { - if (0 && action == SLAGizmoEventType::LeftDown) { - // disable / enable current contour - Vec3d pos; - Vec3d pos_world; - if (unproject_on_cut_plane(mouse_position.cast(), pos, pos_world)) { - // Following would inform the clipper about the mouse click, so it can - // toggle the respective contour as disabled. - m_c->object_clipper()->pass_mouse_click(pos_world); - return true; - } - } + if (!m_connectors_editing) return false; - } CutConnectors& connectors = m_c->selection_info()->model_object()->cut_connectors; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index ca2084aa1..9bf805188 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -137,10 +137,25 @@ class GLGizmoCut3D : public GLGizmoBase bool m_was_cut_plane_dragged { false }; bool m_was_contour_selected { false }; - std::vector m_cut_by_contour_glmodels; - ModelObjectPtrs m_cut_by_contour_objects; - // attributes for disabled contours - std::vector> m_disabled_contours; + struct PartSelection { + PartSelection() = default; + PartSelection(ModelObject* mo, const Vec3d& center, const Vec3d& normal); + + void render(const Vec3d* normal = nullptr); + void toggle_selection(const Vec2d& mouse_pos); + + struct Part { + GLModel glmodel; + MeshRaycaster raycaster; + bool selected; + bool upper; + }; + ModelObject* model_object; // FIXME: Ownership ! + std::vector parts; + bool valid = false; + }; + + PartSelection m_part_selection; bool m_show_shortcuts{ false }; std::vector> m_shortcuts; @@ -299,7 +314,6 @@ private: void init_picking_models(); void init_rendering_items(); void render_clipper_cut(); - void render_colored_parts(); void clear_selection(); void reset_connectors(); void init_connector_shapes(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index b642da5ca..549540610 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -388,10 +388,26 @@ void ObjectClipper::render_cut() const } } -bool ObjectClipper::is_projection_inside_cut(const Vec3d& point, bool respect_disabled_contour) const + +int ObjectClipper::get_number_of_contours() const { - return m_clp_ratio != 0. && std::any_of(m_clippers.begin(), m_clippers.end(), - [point, respect_disabled_contour](const auto& cl) { return cl.first->is_projection_inside_cut(point, respect_disabled_contour); }); + int sum = 0; + for (const auto& [clipper, trafo] : m_clippers) + sum += clipper->get_number_of_contours(); + return sum; +} + +int ObjectClipper::is_projection_inside_cut(const Vec3d& point) const +{ + if (m_clp_ratio == 0.) + return -1; + int idx_offset = 0; + for (const auto& [clipper, trafo] : m_clippers) { + if (int idx = clipper->is_projection_inside_cut(point); idx != -1) + return idx_offset + idx; + idx_offset += clipper->get_number_of_contours(); + } + return -1; } bool ObjectClipper::has_valid_contour() const @@ -399,10 +415,6 @@ bool ObjectClipper::has_valid_contour() const return m_clp_ratio != 0. && std::any_of(m_clippers.begin(), m_clippers.end(), [](const auto& cl) { return cl.first->has_valid_contour(); }); } -bool ObjectClipper::has_disable_contour() const -{ - return m_clp_ratio != 0. && std::any_of(m_clippers.begin(), m_clippers.end(), [](const auto& cl) { return cl.first->has_disable_contour(); }); -} void ObjectClipper::set_position_by_ratio(double pos, bool keep_normal) { @@ -442,16 +454,6 @@ void ObjectClipper::set_behavior(bool hide_clipped, bool fill_cut, double contou clipper.first->set_behaviour(fill_cut, contour_width); } -void ObjectClipper::pass_mouse_click(const Vec3d& pt) -{ - for (auto& clipper : m_clippers) - clipper.first->pass_mouse_click(pt); -} - -std::vector ObjectClipper::get_disabled_contours() const -{ - return std::vector(); -} void SupportsClipper::on_update() { diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp index 15bf65814..4152cce51 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp @@ -247,12 +247,10 @@ public: void set_range_and_pos(const Vec3d& cpl_normal, double cpl_offset, double pos); void set_behavior(bool hide_clipped, bool fill_cut, double contour_width); - void pass_mouse_click(const Vec3d& pt); - std::vector get_disabled_contours() const; + int get_number_of_contours() const; - bool is_projection_inside_cut(const Vec3d& point_in, bool respect_disabled_contour) const; + int is_projection_inside_cut(const Vec3d& point_in) const; bool has_valid_contour() const; - bool has_disable_contour() const; protected: diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index 9f6cd1960..cee684115 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -146,18 +146,19 @@ void MeshClipper::render_contour(const ColorRGBA& color) curr_shader->start_using(); } -bool MeshClipper::is_projection_inside_cut(const Vec3d& point_in, bool respect_disabled_contour) const +int MeshClipper::is_projection_inside_cut(const Vec3d& point_in) const { if (!m_result || m_result->cut_islands.empty()) - return false; + return -1; Vec3d point = m_result->trafo.inverse() * point_in; Point pt_2d = Point::new_scale(Vec2d(point.x(), point.y())); - for (const CutIsland& isl : m_result->cut_islands) { + for (int i=0; icut_islands.size()); ++i) { + const CutIsland& isl = m_result->cut_islands[i]; if (isl.expoly_bb.contains(pt_2d) && isl.expoly.contains(pt_2d)) - return respect_disabled_contour ? !isl.disabled : true; + return i; // TODO: handle intersecting contours } - return false; + return -1; } bool MeshClipper::has_valid_contour() const @@ -165,23 +166,6 @@ bool MeshClipper::has_valid_contour() const return m_result && std::any_of(m_result->cut_islands.begin(), m_result->cut_islands.end(), [](const CutIsland& isl) { return !isl.expoly.empty(); }); } -bool MeshClipper::has_disable_contour() const -{ - return m_result && std::any_of(m_result->cut_islands.begin(), m_result->cut_islands.end(), [](const CutIsland& isl) { return isl.disabled; }); -} - -void MeshClipper::pass_mouse_click(const Vec3d& point_in) -{ - if (! m_result || m_result->cut_islands.empty()) - return; - Vec3d point = m_result->trafo.inverse() * point_in; - Point pt_2d = Point::new_scale(Vec2d(point.x(), point.y())); - - for (CutIsland& isl : m_result->cut_islands) { - if (isl.expoly_bb.contains(pt_2d) && isl.expoly.contains(pt_2d)) - isl.disabled = ! isl.disabled; - } -} void MeshClipper::recalculate_triangles() { diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp index 0a854e5f3..257b500f9 100644 --- a/src/slic3r/GUI/MeshUtils.hpp +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -118,11 +118,10 @@ public: void render_cut(const ColorRGBA& color); void render_contour(const ColorRGBA& color); - void pass_mouse_click(const Vec3d& pt); - - bool is_projection_inside_cut(const Vec3d& point, bool respect_disabled_contour) const; + // Returns index of the contour which was clicked, -1 otherwise. + int is_projection_inside_cut(const Vec3d& point) const; bool has_valid_contour() const; - bool has_disable_contour() const; + int get_number_of_contours() const { return m_result ? m_result->cut_islands.size() : 0; } private: void recalculate_triangles(); From 1a7f46001b9d30d13b21821777bc18477eeb5498 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 28 Feb 2023 17:38:30 +0100 Subject: [PATCH 03/17] Cut: Process cut in respect to the selected parts --- src/libslic3r/Model.cpp | 65 +++--------- src/libslic3r/Model.hpp | 8 +- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 146 +++++++++++++++++++++------ src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 5 +- src/slic3r/GUI/Plater.cpp | 6 ++ src/slic3r/GUI/Plater.hpp | 1 + 6 files changed, 145 insertions(+), 86 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 74dabb8e1..6c316e889 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1353,6 +1353,7 @@ void ModelObject::synchronize_model_after_cut() if (obj->is_cut() && obj->cut_id.has_same_id(this->cut_id)) obj->cut_id.copy(this->cut_id); } + this->invalidate_cut(); } void ModelObject::apply_cut_attributes(ModelObjectCutAttributes attributes) @@ -1378,7 +1379,7 @@ void ModelObject::apply_cut_attributes(ModelObjectCutAttributes attributes) void ModelObject::clone_for_cut(ModelObject** obj) { (*obj) = ModelObject::new_clone(*this); - (*obj)->set_model(nullptr); + (*obj)->set_model(this->get_model()); (*obj)->sla_support_points.clear(); (*obj)->sla_drain_holes.clear(); (*obj)->sla_points_status = sla::PointsStatus::NoPoints; @@ -1461,7 +1462,7 @@ static void add_cut_volume(TriangleMesh& mesh, ModelObject* object, const ModelV void ModelObject::process_connector_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, - std::vector& dowels, Vec3d& local_dowels_displace) + std::vector& dowels) { assert(volume->cut_info.is_connector); volume->cut_info.set_processed(); @@ -1497,9 +1498,6 @@ void ModelObject::process_connector_cut(ModelVolume* volume, const Transform3d& vol->set_rotation(Vec3d::Zero()); vol->set_offset(Z, 0.0); - // Compute the displacement (in instance coordinates) to be applied to place the dowels - local_dowels_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(1.0, 1.0, 0.0)); - dowels.push_back(dowel); } @@ -1566,7 +1564,7 @@ void ModelObject::process_volume_cut(ModelVolume* volume, const Transform3d& ins lower_mesh = TriangleMesh(lower_its); } void ModelObject::process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, - ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, Vec3d& local_displace) + ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower) { // Perform cut TriangleMesh upper_mesh, lower_mesh; @@ -1583,31 +1581,12 @@ void ModelObject::process_solid_part_cut(ModelVolume* volume, const Transform3d& if (attributes.has(ModelObjectCutAttribute::KeepUpper)) add_cut_volume(upper_mesh, upper, volume, cut_matrix); - if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty()) { + if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty()) add_cut_volume(lower_mesh, lower, volume, cut_matrix); - - // Compute the displacement (in instance coordinates) to be applied to place the upper parts - // The upper part displacement is set to half of the lower part bounding box - // this is done in hope at least a part of the upper part will always be visible and draggable - local_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(-0.5, -0.5, 0.0)); - } } -static void invalidate_translations(ModelObject* object, const ModelInstance* src_instance) -{ - if (!object->origin_translation.isApprox(Vec3d::Zero()) && src_instance->get_offset().isApprox(Vec3d::Zero())) { - object->center_around_origin(); - object->translate_instances(-object->origin_translation); - object->origin_translation = Vec3d::Zero(); - } - else { - object->invalidate_bounding_box(); - object->center_around_origin(); - } -} - -static void reset_instance_transformation(ModelObject* object, size_t src_instance_idx, const Transform3d& cut_matrix, - bool place_on_cut = false, bool flip = false, Vec3d local_displace = Vec3d::Zero()) +void ModelObject::reset_instance_transformation(ModelObject* object, size_t src_instance_idx, const Transform3d& cut_matrix, + bool place_on_cut/* = false*/, bool flip/* = false*/) { using namespace Geometry; @@ -1615,15 +1594,8 @@ static void reset_instance_transformation(ModelObject* object, size_t src_instan for (size_t i = 0; i < object->instances.size(); ++i) { auto& obj_instance = object->instances[i]; - const Vec3d offset = obj_instance->get_offset(); const double rot_z = obj_instance->get_rotation().z(); - obj_instance->set_transformation(Transformation()); - - const Vec3d displace = local_displace.isApprox(Vec3d::Zero()) ? Vec3d::Zero() : - rotation_transform(obj_instance->get_rotation()) * local_displace; - obj_instance->set_offset(offset + displace); - Vec3d rotation = Vec3d::Zero(); if (!flip && !place_on_cut) { if ( i != src_instance_idx) @@ -1680,10 +1652,6 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Transform3d& cut_matrix, const Transformation cut_transformation = Transformation(cut_matrix); const Transform3d inverse_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1. * cut_transformation.get_offset()); - // Displacement (in instance coordinates) to be applied to place the upper parts - Vec3d local_displace = Vec3d::Zero(); - Vec3d local_dowels_displace = Vec3d::Zero(); - for (ModelVolume* volume : volumes) { volume->reset_extra_facets(); @@ -1691,10 +1659,10 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Transform3d& cut_matrix, if (volume->cut_info.is_processed) process_modifier_cut(volume, instance_matrix, inverse_cut_matrix, attributes, upper, lower); else - process_connector_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower, dowels, local_dowels_displace); + process_connector_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower, dowels); } else if (!volume->mesh().empty()) - process_solid_part_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower, local_displace); + process_solid_part_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower); } // Post-process cut parts @@ -1707,31 +1675,22 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Transform3d& cut_matrix, } else { if (attributes.has(ModelObjectCutAttribute::KeepUpper) && !upper->volumes.empty()) { - invalidate_translations(upper, instances[instance]); - reset_instance_transformation(upper, instance, cut_matrix, attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper), - attributes.has(ModelObjectCutAttribute::FlipUpper), - local_displace); + attributes.has(ModelObjectCutAttribute::FlipUpper)); res.push_back(upper); } if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower->volumes.empty()) { - invalidate_translations(lower, instances[instance]); - reset_instance_transformation(lower, instance, cut_matrix, attributes.has(ModelObjectCutAttribute::PlaceOnCutLower), - attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) ? true : attributes.has(ModelObjectCutAttribute::FlipLower)); + attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) || attributes.has(ModelObjectCutAttribute::FlipLower)); res.push_back(lower); } if (attributes.has(ModelObjectCutAttribute::CreateDowels) && !dowels.empty()) { for (auto dowel : dowels) { - invalidate_translations(dowel, instances[instance]); - - reset_instance_transformation(dowel, instance, Transform3d::Identity(), false, false, local_dowels_displace); - - local_dowels_displace += dowel->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(-1.5, -1.5, 0.0)); + reset_instance_transformation(dowel, instance, Transform3d::Identity()); dowel->name += "-Dowel-" + dowel->volumes[0]->name; res.push_back(dowel); } diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 359e1fbbd..ccbe0bf64 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -472,13 +472,17 @@ public: void clone_for_cut(ModelObject **obj); void process_connector_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, - std::vector& dowels, Vec3d& local_dowels_displace); + std::vector& dowels); void process_modifier_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& inverse_cut_matrix, ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower); void process_volume_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes, TriangleMesh& upper_mesh, TriangleMesh& lower_mesh); void process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, - ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, Vec3d& local_displace); + ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower); + + static void reset_instance_transformation(ModelObject* object, size_t src_instance_idx, const Transform3d& cut_matrix, + bool place_on_cut = false, bool flip = false); + ModelObjectPtrs cut(size_t instance, const Transform3d&cut_matrix, ModelObjectCutAttributes attributes); void split(ModelObjectPtrs*new_objects); void merge(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index edf91aba8..92a38f9ec 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -892,6 +892,7 @@ void GLGizmoCut3D::on_set_state() // initiate archived values m_ar_plane_center = m_plane_center; m_start_dragging_m = m_rotation_m; + reset_cut_by_contours(); m_parent.request_extra_frame(); } @@ -1116,6 +1117,8 @@ void GLGizmoCut3D::dragging_grabber_z(const GLGizmoBase::UpdateData &data) projection = m_snap_step * std::round(projection / m_snap_step); const Vec3d shift = starting_vec * projection; + if (shift != Vec3d::Zero()) + reset_cut_by_contours(); // move cut plane center set_center(m_plane_center + shift, true); @@ -1153,6 +1156,9 @@ void GLGizmoCut3D::dragging_grabber_xy(const GLGizmoBase::UpdateData &data) if (m_hover_id == X) theta += 0.5 * PI; + if (!is_approx(theta, 0.0)) + reset_cut_by_contours(); + Vec3d rotation = Vec3d::Zero(); rotation[m_hover_id] = theta; @@ -1187,14 +1193,10 @@ void GLGizmoCut3D::on_dragging(const UpdateData& data) { if (m_hover_id < 0) return; - if (m_hover_id == Z || m_hover_id == CutPlane) { + if (m_hover_id == Z || m_hover_id == CutPlane) dragging_grabber_z(data); - reset_cut_by_contours(); - } - else if (m_hover_id == X || m_hover_id == Y) { + else if (m_hover_id == X || m_hover_id == Y) dragging_grabber_xy(data); - reset_cut_by_contours(); - } else if (m_hover_id >= m_connectors_group_id && m_connector_mode == CutConnectorMode::Manual) dragging_connector(data); } @@ -1301,6 +1303,7 @@ void GLGizmoCut3D::update_bb() m_bounding_box = box; invalidate_cut_plane(); + reset_cut_by_contours(); m_max_pos = box.max; m_min_pos = box.min; @@ -1402,11 +1405,14 @@ void GLGizmoCut3D::PartSelection::render(const Vec3d* normal) // FIXME: Cache the transforms. - const Vec3d inst_offset = model_object->instances[0]->get_offset(); + const Vec3d inst_offset = model_object->instances[instance_idx]->get_offset(); const Transform3d view_inst_matrix= camera.get_view_matrix() * translation_transform(inst_offset); + const bool is_looking_forward = normal && camera.get_dir_forward().dot(*normal) < 0.05; + for (size_t id=0; idvolumes[id]->get_offset(); shader->set_uniform("view_model_matrix", view_inst_matrix * translation_transform(volume_offset)); @@ -1419,7 +1425,6 @@ void GLGizmoCut3D::PartSelection::render(const Vec3d* normal) } } - void GLGizmoCut3D::PartSelection::toggle_selection(const Vec2d& mouse_pos) { // FIXME: Cache the transforms. @@ -1430,7 +1435,7 @@ void GLGizmoCut3D::PartSelection::toggle_selection(const Vec2d& mouse_pos) for (size_t id=0; idvolumes[id]->get_offset(); - Transform3d tr = model_object->instances.front()->get_matrix() * model_object->volumes[id]->get_matrix(); + Transform3d tr = model_object->instances[instance_idx]->get_matrix() * model_object->volumes[id]->get_matrix(); if (parts[id].raycaster.unproject_on_mesh(mouse_pos, tr, camera, pos, normal)) { parts[id].selected = ! parts[id].selected; return; @@ -1438,6 +1443,12 @@ void GLGizmoCut3D::PartSelection::toggle_selection(const Vec2d& mouse_pos) } } +void GLGizmoCut3D::PartSelection::turn_over_selection() +{ + for (Part& part : parts) + part.selected = !part.selected; +} + void GLGizmoCut3D::on_render() { if (m_state == On) { @@ -1708,12 +1719,14 @@ void GLGizmoCut3D::flip_cut_plane() m_start_dragging_m = m_rotation_m; update_clipper(); + m_part_selection.turn_over_selection(); } -GLGizmoCut3D::PartSelection::PartSelection(ModelObject* mo, const Vec3d& center, const Vec3d& normal) +GLGizmoCut3D::PartSelection::PartSelection(ModelObject* mo, int instance_idx_in, const Vec3d& center, const Vec3d& normal) { model_object = mo; // FIXME: Ownership. + instance_idx = instance_idx_in; const ModelVolumePtrs& volumes = mo->volumes; @@ -1730,7 +1743,7 @@ GLGizmoCut3D::PartSelection::PartSelection(ModelObject* mo, const Vec3d& center, parts.back().glmodel.init_from(volume->mesh()); // Now check whether this part is below or above the plane. - Transform3d tr = (model_object->instances.front()->get_matrix() * volume->get_matrix()).inverse(); + Transform3d tr = (model_object->instances[instance_idx]->get_matrix() * volume->get_matrix()).inverse(); Vec3f pos = (tr * center).cast(); Vec3f norm = (tr.linear().inverse().transpose() * normal).cast(); for (const Vec3f& v : volume->mesh().its.vertices) { @@ -1749,7 +1762,10 @@ GLGizmoCut3D::PartSelection::PartSelection(ModelObject* mo, const Vec3d& center, void GLGizmoCut3D::reset_cut_by_contours() { m_part_selection = PartSelection(); - m_parent.toggle_model_objects_visibility(true); + + const Selection& selection = m_parent.get_selection(); + const ModelObjectPtrs& model_objects = selection.get_model()->objects; + m_parent.toggle_model_objects_visibility(true, model_objects[selection.get_object_idx()], selection.get_instance_idx()); } void GLGizmoCut3D::process_contours() @@ -1763,14 +1779,14 @@ void GLGizmoCut3D::process_contours() const int instance_idx = selection.get_instance_idx(); const int object_idx = selection.get_object_idx(); - ModelObjectPtrs moptrs = model_objects[object_idx]->cut(instance_idx, get_cut_matrix(selection), + m_cut_part_ptrs.clear(); + m_cut_part_ptrs = model_objects[object_idx]->cut(instance_idx, get_cut_matrix(selection), ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::KeepLower | - ModelObjectCutAttribute::KeepAsParts | - ModelObjectCutAttribute::InvalidateCutInfo); + ModelObjectCutAttribute::KeepAsParts); + assert(m_cut_part_ptrs.size() == 1); - assert(moptrs.size() == 1); - m_part_selection = PartSelection(moptrs.front(), m_plane_center, m_cut_normal); + m_part_selection = PartSelection(m_cut_part_ptrs.front(), instance_idx, m_plane_center, m_cut_normal); m_parent.toggle_model_objects_visibility(false); } @@ -1919,7 +1935,7 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) ImGuiWrapper::text(_L("Cut result") + ": "); add_vertical_scaled_interval(0.5f); - m_imgui->disabled_begin(has_connectors || m_keep_as_parts); + m_imgui->disabled_begin(has_connectors || m_keep_as_parts || m_part_selection.valid); render_part_name("A", m_keep_upper, m_imgui->to_ImU32(UPPER_PART_COLOR)); ImGui::SameLine(h_shift + ImGui::GetCurrentWindow()->WindowPadding.x); render_part_name("B", m_keep_lower, m_imgui->to_ImU32(LOWER_PART_COLOR)); @@ -1938,6 +1954,9 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) m_imgui->disabled_begin(has_connectors); ImGuiWrapper::text(_L("Cut into") + ":"); + if (m_part_selection.valid) + m_keep_as_parts = false; + add_horizontal_scaled_interval(1.2f); // TRN CutGizmo: RadioButton Cut into ... if (m_imgui->radio_button(_L("Objects"), !m_keep_as_parts)) @@ -2322,21 +2341,88 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) { Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Cut by Plane")); + const bool cut_by_contour = m_part_selection.valid && !m_cut_part_ptrs.empty(); + ModelObject* cut_mo = cut_by_contour ? m_cut_part_ptrs.front() : nullptr; + if (cut_mo) + cut_mo->cut_connectors = mo->cut_connectors; + bool create_dowels_as_separate_object = false; const bool has_connectors = !mo->cut_connectors.empty(); // update connectors pos as offset of its center before cut performing - apply_connectors_in_model(mo, create_dowels_as_separate_object); + apply_connectors_in_model(cut_mo ? cut_mo : mo , create_dowels_as_separate_object); - plater->cut(object_idx, instance_idx, get_cut_matrix(selection), - only_if(has_connectors ? true : m_keep_upper, ModelObjectCutAttribute::KeepUpper) | - only_if(has_connectors ? true : m_keep_lower, ModelObjectCutAttribute::KeepLower) | - only_if(has_connectors ? false: m_keep_as_parts, ModelObjectCutAttribute::KeepAsParts) | - only_if(m_place_on_cut_upper, ModelObjectCutAttribute::PlaceOnCutUpper) | - only_if(m_place_on_cut_lower, ModelObjectCutAttribute::PlaceOnCutLower) | - only_if(m_rotate_upper, ModelObjectCutAttribute::FlipUpper) | - only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower) | - only_if(create_dowels_as_separate_object, ModelObjectCutAttribute::CreateDowels) | - only_if(!has_connectors, ModelObjectCutAttribute::InvalidateCutInfo)); + wxBusyCursor wait; + + const Transform3d cut_matrix = get_cut_matrix(selection); + + ModelObjectCutAttributes attributes = only_if(has_connectors ? true : m_keep_upper, ModelObjectCutAttribute::KeepUpper) | + only_if(has_connectors ? true : m_keep_lower, ModelObjectCutAttribute::KeepLower) | + only_if(has_connectors ? false : m_keep_as_parts, ModelObjectCutAttribute::KeepAsParts) | + only_if(m_place_on_cut_upper, ModelObjectCutAttribute::PlaceOnCutUpper) | + only_if(m_place_on_cut_lower, ModelObjectCutAttribute::PlaceOnCutLower) | + only_if(m_rotate_upper, ModelObjectCutAttribute::FlipUpper) | + only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower) | + only_if(create_dowels_as_separate_object, ModelObjectCutAttribute::CreateDowels) | + only_if(!has_connectors, ModelObjectCutAttribute::InvalidateCutInfo); + + ModelObjectPtrs cut_object_ptrs; + if (cut_by_contour) { + // apply cut attributes for object + cut_mo->apply_cut_attributes(ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper | + only_if(create_dowels_as_separate_object, ModelObjectCutAttribute::CreateDowels)); + + // Clone the object to duplicate instances, materials etc. + ModelObject* upper{ nullptr }; + cut_mo->clone_for_cut(&upper); + ModelObject* lower{ nullptr }; + cut_mo->clone_for_cut(&lower); + + auto add_cut_objects = [this, &instance_idx, &cut_matrix](ModelObjectPtrs& cut_objects, ModelObject* upper, ModelObject* lower, bool invalidate_cut = true) { + if (!upper->volumes.empty()) { + ModelObject::reset_instance_transformation(upper, instance_idx, cut_matrix, m_place_on_cut_upper, m_rotate_upper); + if (invalidate_cut) + upper->invalidate_cut(); + cut_objects.push_back(upper); + } + if (!lower->volumes.empty()) { + ModelObject::reset_instance_transformation(lower, instance_idx, cut_matrix, m_place_on_cut_lower, m_place_on_cut_lower || m_rotate_lower); + if (invalidate_cut) + lower->invalidate_cut(); + cut_objects.push_back(lower); + } + }; + + const size_t cut_parts_cnt = m_part_selection.parts.size(); + for (size_t id = 0; id < cut_parts_cnt; ++id) + (m_part_selection.parts[id].selected ? upper : lower)->add_volume(*(cut_mo->volumes[id])); + + ModelVolumePtrs& volumes = cut_mo->volumes; + if (volumes.size() == cut_parts_cnt) + add_cut_objects(cut_object_ptrs, upper, lower); + else if (volumes.size() > cut_parts_cnt) { + for (size_t id = 0; id < cut_parts_cnt; id++) + delete *(volumes.begin() + id); + volumes.erase(volumes.begin(), volumes.begin() + cut_parts_cnt); + + const auto cut_connectors_obj = cut_mo->cut(instance_idx, get_cut_matrix(selection), attributes); + assert(create_dowels_as_separate_object ? cut_connectors_obj.size() >= 3 : cut_connectors_obj.size() == 2); + + for (const ModelVolume* volume : cut_connectors_obj[0]->volumes) + upper->add_volume(*volume, volume->type()); + for (const ModelVolume* volume : cut_connectors_obj[1]->volumes) + lower->add_volume(*volume, volume->type()); + + add_cut_objects(cut_object_ptrs, upper, lower, false); + + if (cut_connectors_obj.size() >= 3) + for (size_t id = 2; id < cut_connectors_obj.size(); id++) + cut_object_ptrs.push_back(cut_connectors_obj[id]); + } + } + else + cut_object_ptrs = mo->cut(instance_idx, cut_matrix, attributes); + + plater->cut(object_idx, cut_object_ptrs); } } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 9bf805188..598bc1d5a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -139,10 +139,11 @@ class GLGizmoCut3D : public GLGizmoBase struct PartSelection { PartSelection() = default; - PartSelection(ModelObject* mo, const Vec3d& center, const Vec3d& normal); + PartSelection(ModelObject* mo, int instance_idx, const Vec3d& center, const Vec3d& normal); void render(const Vec3d* normal = nullptr); void toggle_selection(const Vec2d& mouse_pos); + void turn_over_selection(); struct Part { GLModel glmodel; @@ -151,11 +152,13 @@ class GLGizmoCut3D : public GLGizmoBase bool upper; }; ModelObject* model_object; // FIXME: Ownership ! + int instance_idx; std::vector parts; bool valid = false; }; PartSelection m_part_selection; + ModelObjectPtrs m_cut_part_ptrs; bool m_show_shortcuts{ false }; std::vector> m_shortcuts; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 061f39622..35308c058 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -6266,7 +6266,11 @@ void Plater::cut(size_t obj_idx, size_t instance_idx, const Transform3d& cut_mat wxBusyCursor wait; const auto new_objects = object->cut(instance_idx, cut_matrix, attributes); + cut(obj_idx, new_objects); +} +void Plater::cut(size_t obj_idx, const ModelObjectPtrs& new_objects) +{ model().delete_object(obj_idx); sidebar().obj_list()->delete_object_from_list(obj_idx); @@ -6284,6 +6288,8 @@ void Plater::cut(size_t obj_idx, size_t instance_idx, const Transform3d& cut_mat size_t last_id = p->model.objects.size() - 1; for (size_t i = 0; i < new_objects.size(); ++i) selection.add_object((unsigned int)(last_id - i), i == 0); + + arrange(); } void Plater::export_gcode(bool prefer_removable) diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 58a840a4b..035932869 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -260,6 +260,7 @@ public: void toggle_layers_editing(bool enable); void cut(size_t obj_idx, size_t instance_idx, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes); + void cut(size_t init_obj_idx, const ModelObjectPtrs& cut_objects); void export_gcode(bool prefer_removable); void export_stl_obj(bool extended = false, bool selection_only = false); From 7ea51fc07c7e9c0cf30a8af9723f1ae924b183d9 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 28 Mar 2023 08:55:22 +0200 Subject: [PATCH 04/17] Cut: when parts are selected, the result shall have only one parts with the combined meshes --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 92a38f9ec..a4f2f975b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -2418,6 +2418,26 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) for (size_t id = 2; id < cut_connectors_obj.size(); id++) cut_object_ptrs.push_back(cut_connectors_obj[id]); } + + // Now merge all model parts together: + { + for (ModelObject* mo : cut_object_ptrs) { + TriangleMesh mesh; + for (const ModelVolume* mv : mo->volumes) { + if (mv->is_model_part()) { + TriangleMesh m = mv->mesh(); + m.transform(mv->get_matrix()); + mesh.merge(m); + } + } + if (! mesh.empty()) { + ModelVolume* new_volume = mo->add_volume(mesh); + for (int i=int(mo->volumes.size())-2; i>=0; --i) + if (mo->volumes[i]->type() == ModelVolumeType::MODEL_PART) + mo->delete_volume(i); + } + } + } } else cut_object_ptrs = mo->cut(instance_idx, cut_matrix, attributes); From 732dd0f6ac472241d370d61e286ef0b6b7d0c2fd Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 28 Mar 2023 10:00:44 +0200 Subject: [PATCH 05/17] Cut: fixed rendering of individual parts, removed some unnecessary variables --- src/libslic3r/Model.cpp | 8 -------- src/libslic3r/Model.hpp | 14 +++++++++----- src/slic3r/GUI/GUI_App.cpp | 16 ---------------- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 17 ++++++++--------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 2 -- 5 files changed, 17 insertions(+), 40 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 6c316e889..c7844f807 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -2627,14 +2627,6 @@ bool model_has_multi_part_objects(const Model &model) return false; } -bool model_has_connectors(const Model &model) -{ - for (const ModelObject *model_object : model.objects) - if (!model_object->cut_connectors.empty()) - return true; - return false; -} - bool model_has_advanced_features(const Model &model) { auto config_is_advanced = [](const ModelConfig &config) { diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index ccbe0bf64..27adf8fd3 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -467,9 +467,15 @@ public: void invalidate_cut(); // delete volumes which are marked as connector for this object void delete_connectors(); - void synchronize_model_after_cut(); - void apply_cut_attributes(ModelObjectCutAttributes attributes); void clone_for_cut(ModelObject **obj); + + void apply_cut_attributes(ModelObjectCutAttributes attributes); +private: + // FIXME: These functions would best not be here at all. It might make sense to separate the + // cut-related methods elsewhere. Same holds for cut_connectors data member, which is currently + // just a temporary variable used by cut gizmo only. + void synchronize_model_after_cut(); + void process_connector_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, std::vector& dowels); @@ -479,7 +485,7 @@ public: ModelObjectCutAttributes attributes, TriangleMesh& upper_mesh, TriangleMesh& lower_mesh); void process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower); - +public: static void reset_instance_transformation(ModelObject* object, size_t src_instance_idx, const Transform3d& cut_matrix, bool place_on_cut = false, bool flip = false); @@ -1395,8 +1401,6 @@ bool model_has_parameter_modifiers_in_objects(const Model& model); // If the model has multi-part objects, then it is currently not supported by the SLA mode. // Either the model cannot be loaded, or a SLA printer has to be activated. bool model_has_multi_part_objects(const Model &model); -// If the model has objects with cut connectrs, then it is currently not supported by the SLA mode. -bool model_has_connectors(const Model& model); // If the model has advanced features, then it cannot be processed in simple mode. bool model_has_advanced_features(const Model &model); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 83c3cf13f..7382f87bf 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -3023,22 +3023,6 @@ bool GUI_App::may_switch_to_SLA_preset(const wxString& caption) caption); return false; } -/* - if (model_has_multi_part_objects(model())) { - show_info(nullptr, - _L("It's impossible to print multi-part object(s) with SLA technology.") + "\n\n" + - _L("Please check your object list before preset changing."), - caption); - return false; - } - if (model_has_connectors(model())) { - show_info(nullptr, - _L("SLA technology doesn't support cut with connectors") + "\n\n" + - _L("Please check your object list before preset changing."), - caption); - return false; - } -*/ return true; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index a4f2f975b..2f48f4392 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -1402,6 +1402,7 @@ void GLGizmoCut3D::PartSelection::render(const Vec3d* normal) shader->start_using(); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + shader->set_uniform("emission_factor", 0.f); // FIXME: Cache the transforms. @@ -1416,7 +1417,6 @@ void GLGizmoCut3D::PartSelection::render(const Vec3d* normal) continue; const Vec3d volume_offset = model_object->volumes[id]->get_offset(); shader->set_uniform("view_model_matrix", view_inst_matrix * translation_transform(volume_offset)); - //parts[id].glmodel.set_color(parts[id].selected ? ColorRGBA(1.f, 0.f, 0.f, 1.f) : ColorRGBA(0.f, 1.f, 0.f, 1.f)); parts[id].glmodel.set_color(parts[id].selected ? UPPER_PART_COLOR : LOWER_PART_COLOR); parts[id].glmodel.render(); } @@ -1779,14 +1779,13 @@ void GLGizmoCut3D::process_contours() const int instance_idx = selection.get_instance_idx(); const int object_idx = selection.get_object_idx(); - m_cut_part_ptrs.clear(); - m_cut_part_ptrs = model_objects[object_idx]->cut(instance_idx, get_cut_matrix(selection), + ModelObjectPtrs cut_part_ptrs = model_objects[object_idx]->cut(instance_idx, get_cut_matrix(selection), ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepAsParts); - assert(m_cut_part_ptrs.size() == 1); + assert(cut_part_ptrs.size() == 1); - m_part_selection = PartSelection(m_cut_part_ptrs.front(), instance_idx, m_plane_center, m_cut_normal); + m_part_selection = PartSelection(cut_part_ptrs.front(), instance_idx, m_plane_center, m_cut_normal); m_parent.toggle_model_objects_visibility(false); } @@ -2144,7 +2143,7 @@ bool GLGizmoCut3D::is_outside_of_cut_contour(size_t idx, const CutConnectors& co } its_transform(mesh, translation_transform(cur_pos) * m_rotation_m); - for (auto vertex : vertices) { + for (const Vec3f& vertex : vertices) { if (m_c->object_clipper() && m_c->object_clipper()->is_projection_inside_cut(vertex.cast()) == -1) { m_info_stats.outside_cut_contour++; return true; @@ -2341,8 +2340,8 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) { Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Cut by Plane")); - const bool cut_by_contour = m_part_selection.valid && !m_cut_part_ptrs.empty(); - ModelObject* cut_mo = cut_by_contour ? m_cut_part_ptrs.front() : nullptr; + const bool cut_by_contour = m_part_selection.valid; + ModelObject* cut_mo = cut_by_contour ? m_part_selection.model_object : nullptr; if (cut_mo) cut_mo->cut_connectors = mo->cut_connectors; @@ -2404,7 +2403,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) delete *(volumes.begin() + id); volumes.erase(volumes.begin(), volumes.begin() + cut_parts_cnt); - const auto cut_connectors_obj = cut_mo->cut(instance_idx, get_cut_matrix(selection), attributes); + const ModelObjectPtrs cut_connectors_obj = cut_mo->cut(instance_idx, get_cut_matrix(selection), attributes); assert(create_dowels_as_separate_object ? cut_connectors_obj.size() >= 3 : cut_connectors_obj.size() == 2); for (const ModelVolume* volume : cut_connectors_obj[0]->volumes) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 598bc1d5a..793d36358 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -149,7 +149,6 @@ class GLGizmoCut3D : public GLGizmoBase GLModel glmodel; MeshRaycaster raycaster; bool selected; - bool upper; }; ModelObject* model_object; // FIXME: Ownership ! int instance_idx; @@ -158,7 +157,6 @@ class GLGizmoCut3D : public GLGizmoBase }; PartSelection m_part_selection; - ModelObjectPtrs m_cut_part_ptrs; bool m_show_shortcuts{ false }; std::vector> m_shortcuts; From 2005882ac5db693b70f7105e96d85307ed1f8503 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 28 Mar 2023 11:22:19 +0200 Subject: [PATCH 06/17] Cut: always toggle the closest part --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 34 +++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 2f48f4392..4aaa7ea8b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -1428,18 +1428,25 @@ void GLGizmoCut3D::PartSelection::render(const Vec3d* normal) void GLGizmoCut3D::PartSelection::toggle_selection(const Vec2d& mouse_pos) { // FIXME: Cache the transforms. - const Camera& camera = wxGetApp().plater()->get_camera(); + const Camera& camera = wxGetApp().plater()->get_camera(); + const Vec3d& camera_pos = camera.get_position(); Vec3f pos; Vec3f normal; - + + std::vector> hits_id_and_sqdist; + for (size_t id=0; idvolumes[id]->get_offset(); Transform3d tr = model_object->instances[instance_idx]->get_matrix() * model_object->volumes[id]->get_matrix(); if (parts[id].raycaster.unproject_on_mesh(mouse_pos, tr, camera, pos, normal)) { - parts[id].selected = ! parts[id].selected; - return; - } + hits_id_and_sqdist.emplace_back(id, (camera_pos - tr*(pos.cast())).squaredNorm()); + } + } + if (! hits_id_and_sqdist.empty()) { + size_t id = std::min_element(hits_id_and_sqdist.begin(), hits_id_and_sqdist.end(), + [](const std::pair& a, const std::pair& b) { return a.second < b.second; })->first; + parts[id].selected = ! parts[id].selected; } } @@ -2470,7 +2477,22 @@ bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, Vec3d& po hit = (point + t * direction); } else return false; - + + // Now check if the hit is not obscured by a selected part on this side of the plane. + // FIXME: This would be better solved by remembering which contours are active. We will + // probably need that anyway because there is not other way to find out which contours + // to render. If you want to uncomment it, fix it first. It does not work yet. + /*for (size_t id = 0; id < m_part_selection.parts.size(); ++id) { + if (! m_part_selection.parts[id].selected) { + Vec3f pos, normal; + const ModelObject* model_object = m_part_selection.model_object; + const Vec3d volume_offset = m_part_selection.model_object->volumes[id]->get_offset(); + Transform3d tr = model_object->instances[m_part_selection.instance_idx]->get_matrix() * model_object->volumes[id]->get_matrix(); + if (m_part_selection.parts[id].raycaster.unproject_on_mesh(mouse_position, tr, camera, pos, normal)) + return false; + } + }*/ + if (m_c->object_clipper()->is_projection_inside_cut(hit) == -1) return false; From ae5fe9ce8cfd57f53362bd133a77629249faa87d Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 28 Mar 2023 13:06:11 +0200 Subject: [PATCH 07/17] Cut: separate Model for the pre-cut object --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 35 +++++++++++++++------------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 6 +++-- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 4aaa7ea8b..38bccfb4a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -1406,7 +1406,7 @@ void GLGizmoCut3D::PartSelection::render(const Vec3d* normal) // FIXME: Cache the transforms. - const Vec3d inst_offset = model_object->instances[instance_idx]->get_offset(); + const Vec3d inst_offset = model_object()->instances[instance_idx]->get_offset(); const Transform3d view_inst_matrix= camera.get_view_matrix() * translation_transform(inst_offset); const bool is_looking_forward = normal && camera.get_dir_forward().dot(*normal) < 0.05; @@ -1415,7 +1415,7 @@ void GLGizmoCut3D::PartSelection::render(const Vec3d* normal) if (normal && (( is_looking_forward && parts[id].selected) || (!is_looking_forward && !parts[id].selected) ) ) continue; - const Vec3d volume_offset = model_object->volumes[id]->get_offset(); + const Vec3d volume_offset = model_object()->volumes[id]->get_offset(); shader->set_uniform("view_model_matrix", view_inst_matrix * translation_transform(volume_offset)); parts[id].glmodel.set_color(parts[id].selected ? UPPER_PART_COLOR : LOWER_PART_COLOR); parts[id].glmodel.render(); @@ -1437,8 +1437,8 @@ void GLGizmoCut3D::PartSelection::toggle_selection(const Vec2d& mouse_pos) std::vector> hits_id_and_sqdist; for (size_t id=0; idvolumes[id]->get_offset(); - Transform3d tr = model_object->instances[instance_idx]->get_matrix() * model_object->volumes[id]->get_matrix(); + const Vec3d volume_offset = model_object()->volumes[id]->get_offset(); + Transform3d tr = model_object()->instances[instance_idx]->get_matrix() * model_object()->volumes[id]->get_matrix(); if (parts[id].raycaster.unproject_on_mesh(mouse_pos, tr, camera, pos, normal)) { hits_id_and_sqdist.emplace_back(id, (camera_pos - tr*(pos.cast())).squaredNorm()); } @@ -1730,12 +1730,21 @@ void GLGizmoCut3D::flip_cut_plane() } -GLGizmoCut3D::PartSelection::PartSelection(ModelObject* mo, int instance_idx_in, const Vec3d& center, const Vec3d& normal) +GLGizmoCut3D::PartSelection::PartSelection(const ModelObject* mo, const Transform3d& cut_matrix, int instance_idx_in, const Vec3d& center, const Vec3d& normal) { - model_object = mo; // FIXME: Ownership. + model = Model(); + model.add_object(*mo); + ModelObjectPtrs cut_part_ptrs = model.objects.front()->cut(instance_idx_in, cut_matrix, + ModelObjectCutAttribute::KeepUpper | + ModelObjectCutAttribute::KeepLower | + ModelObjectCutAttribute::KeepAsParts); + assert(cut_part_ptrs.size() == 1); + model = Model(); + model.add_object(*cut_part_ptrs.front()); + instance_idx = instance_idx_in; - const ModelVolumePtrs& volumes = mo->volumes; + const ModelVolumePtrs& volumes = model_object()->volumes; // split to parts for (int id = int(volumes.size())-1; id >= 0; id--) @@ -1750,7 +1759,7 @@ GLGizmoCut3D::PartSelection::PartSelection(ModelObject* mo, int instance_idx_in, parts.back().glmodel.init_from(volume->mesh()); // Now check whether this part is below or above the plane. - Transform3d tr = (model_object->instances[instance_idx]->get_matrix() * volume->get_matrix()).inverse(); + Transform3d tr = (model_object()->instances[instance_idx]->get_matrix() * volume->get_matrix()).inverse(); Vec3f pos = (tr * center).cast(); Vec3f norm = (tr.linear().inverse().transpose() * normal).cast(); for (const Vec3f& v : volume->mesh().its.vertices) { @@ -1786,13 +1795,7 @@ void GLGizmoCut3D::process_contours() const int instance_idx = selection.get_instance_idx(); const int object_idx = selection.get_object_idx(); - ModelObjectPtrs cut_part_ptrs = model_objects[object_idx]->cut(instance_idx, get_cut_matrix(selection), - ModelObjectCutAttribute::KeepUpper | - ModelObjectCutAttribute::KeepLower | - ModelObjectCutAttribute::KeepAsParts); - assert(cut_part_ptrs.size() == 1); - - m_part_selection = PartSelection(cut_part_ptrs.front(), instance_idx, m_plane_center, m_cut_normal); + m_part_selection = PartSelection(model_objects[object_idx], get_cut_matrix(selection), instance_idx, m_plane_center, m_cut_normal); m_parent.toggle_model_objects_visibility(false); } @@ -2348,7 +2351,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Cut by Plane")); const bool cut_by_contour = m_part_selection.valid; - ModelObject* cut_mo = cut_by_contour ? m_part_selection.model_object : nullptr; + ModelObject* cut_mo = cut_by_contour ? m_part_selection.model_object() : nullptr; if (cut_mo) cut_mo->cut_connectors = mo->cut_connectors; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 793d36358..37f461038 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -139,18 +139,20 @@ class GLGizmoCut3D : public GLGizmoBase struct PartSelection { PartSelection() = default; - PartSelection(ModelObject* mo, int instance_idx, const Vec3d& center, const Vec3d& normal); + PartSelection(const ModelObject* mo, const Transform3d& cut_matrix, int instance_idx, const Vec3d& center, const Vec3d& normal); void render(const Vec3d* normal = nullptr); void toggle_selection(const Vec2d& mouse_pos); void turn_over_selection(); + ModelObject* model_object() { return model.objects.front(); } struct Part { GLModel glmodel; MeshRaycaster raycaster; bool selected; }; - ModelObject* model_object; // FIXME: Ownership ! + + Model model; int instance_idx; std::vector parts; bool valid = false; From 8bea83c6c7611ac08e25aa2b71e918399a8ff611 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 28 Mar 2023 15:13:04 +0200 Subject: [PATCH 08/17] Cut: slightly refactored PartSelection --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 67 +++++++++++++++------------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 25 ++++++----- 2 files changed, 50 insertions(+), 42 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 38bccfb4a..50e446292 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -309,7 +309,7 @@ bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event) flip_cut_plane(); } - if (m_part_selection.valid) + if (m_part_selection.valid()) m_parent.toggle_model_objects_visibility(false); return true; } @@ -353,7 +353,7 @@ bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event) else if (mouse_event.RightDown()) { if (! m_connectors_editing) { // Check the internal part raycasters. - if (! m_part_selection.valid) + if (! m_part_selection.valid()) process_contours(); m_part_selection.toggle_selection(mouse_pos); return true; @@ -1394,7 +1394,7 @@ void GLGizmoCut3D::render_clipper_cut() void GLGizmoCut3D::PartSelection::render(const Vec3d* normal) { - if (! valid) + if (! valid()) return; if (GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light")) { @@ -1406,19 +1406,19 @@ void GLGizmoCut3D::PartSelection::render(const Vec3d* normal) // FIXME: Cache the transforms. - const Vec3d inst_offset = model_object()->instances[instance_idx]->get_offset(); + const Vec3d inst_offset = model_object()->instances[m_instance_idx]->get_offset(); const Transform3d view_inst_matrix= camera.get_view_matrix() * translation_transform(inst_offset); const bool is_looking_forward = normal && camera.get_dir_forward().dot(*normal) < 0.05; - for (size_t id=0; idvolumes[id]->get_offset(); shader->set_uniform("view_model_matrix", view_inst_matrix * translation_transform(volume_offset)); - parts[id].glmodel.set_color(parts[id].selected ? UPPER_PART_COLOR : LOWER_PART_COLOR); - parts[id].glmodel.render(); + m_parts[id].glmodel.set_color(m_parts[id].selected ? UPPER_PART_COLOR : LOWER_PART_COLOR); + m_parts[id].glmodel.render(); } shader->stop_using(); @@ -1436,23 +1436,23 @@ void GLGizmoCut3D::PartSelection::toggle_selection(const Vec2d& mouse_pos) std::vector> hits_id_and_sqdist; - for (size_t id=0; idvolumes[id]->get_offset(); - Transform3d tr = model_object()->instances[instance_idx]->get_matrix() * model_object()->volumes[id]->get_matrix(); - if (parts[id].raycaster.unproject_on_mesh(mouse_pos, tr, camera, pos, normal)) { + Transform3d tr = model_object()->instances[m_instance_idx]->get_matrix() * model_object()->volumes[id]->get_matrix(); + if (m_parts[id].raycaster.unproject_on_mesh(mouse_pos, tr, camera, pos, normal)) { hits_id_and_sqdist.emplace_back(id, (camera_pos - tr*(pos.cast())).squaredNorm()); } } if (! hits_id_and_sqdist.empty()) { size_t id = std::min_element(hits_id_and_sqdist.begin(), hits_id_and_sqdist.end(), [](const std::pair& a, const std::pair& b) { return a.second < b.second; })->first; - parts[id].selected = ! parts[id].selected; + m_parts[id].selected = ! m_parts[id].selected; } } void GLGizmoCut3D::PartSelection::turn_over_selection() { - for (Part& part : parts) + for (Part& part : m_parts) part.selected = !part.selected; } @@ -1732,17 +1732,17 @@ void GLGizmoCut3D::flip_cut_plane() GLGizmoCut3D::PartSelection::PartSelection(const ModelObject* mo, const Transform3d& cut_matrix, int instance_idx_in, const Vec3d& center, const Vec3d& normal) { - model = Model(); - model.add_object(*mo); - ModelObjectPtrs cut_part_ptrs = model.objects.front()->cut(instance_idx_in, cut_matrix, + m_model = Model(); + m_model.add_object(*mo); + ModelObjectPtrs cut_part_ptrs = m_model.objects.front()->cut(instance_idx_in, cut_matrix, ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepAsParts); assert(cut_part_ptrs.size() == 1); - model = Model(); - model.add_object(*cut_part_ptrs.front()); + m_model = Model(); + m_model.add_object(*cut_part_ptrs.front()); - instance_idx = instance_idx_in; + m_instance_idx = instance_idx_in; const ModelVolumePtrs& volumes = model_object()->volumes; @@ -1751,27 +1751,27 @@ GLGizmoCut3D::PartSelection::PartSelection(const ModelObject* mo, const Transfor if (volumes[id]->is_splittable()) volumes[id]->split(1); - parts.clear(); + m_parts.clear(); for (const ModelVolume* volume : volumes) { assert(volume != nullptr); - parts.emplace_back(Part{GLModel(), MeshRaycaster(volume->mesh()), true}); - parts.back().glmodel.set_color({ 0.f, 0.f, 1.f, 1.f }); - parts.back().glmodel.init_from(volume->mesh()); + m_parts.emplace_back(Part{GLModel(), MeshRaycaster(volume->mesh()), true}); + m_parts.back().glmodel.set_color({ 0.f, 0.f, 1.f, 1.f }); + m_parts.back().glmodel.init_from(volume->mesh()); // Now check whether this part is below or above the plane. - Transform3d tr = (model_object()->instances[instance_idx]->get_matrix() * volume->get_matrix()).inverse(); + Transform3d tr = (model_object()->instances[m_instance_idx]->get_matrix() * volume->get_matrix()).inverse(); Vec3f pos = (tr * center).cast(); Vec3f norm = (tr.linear().inverse().transpose() * normal).cast(); for (const Vec3f& v : volume->mesh().its.vertices) { double p = (v - pos).dot(norm); if (std::abs(p) > EPSILON) { - parts.back().selected = p > 0.; + m_parts.back().selected = p > 0.; break; } } } - valid = true; + m_valid = true; } @@ -1944,7 +1944,7 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) ImGuiWrapper::text(_L("Cut result") + ": "); add_vertical_scaled_interval(0.5f); - m_imgui->disabled_begin(has_connectors || m_keep_as_parts || m_part_selection.valid); + m_imgui->disabled_begin(has_connectors || m_keep_as_parts || m_part_selection.valid()); render_part_name("A", m_keep_upper, m_imgui->to_ImU32(UPPER_PART_COLOR)); ImGui::SameLine(h_shift + ImGui::GetCurrentWindow()->WindowPadding.x); render_part_name("B", m_keep_lower, m_imgui->to_ImU32(LOWER_PART_COLOR)); @@ -1963,7 +1963,7 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) m_imgui->disabled_begin(has_connectors); ImGuiWrapper::text(_L("Cut into") + ":"); - if (m_part_selection.valid) + if (m_part_selection.valid()) m_keep_as_parts = false; add_horizontal_scaled_interval(1.2f); @@ -2350,7 +2350,10 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) { Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Cut by Plane")); - const bool cut_by_contour = m_part_selection.valid; + // This shall delete the part selection class and deallocate the memory. + ScopeGuard part_selection_killer([this]() { m_part_selection = PartSelection(); }); + + const bool cut_by_contour = m_part_selection.valid(); ModelObject* cut_mo = cut_by_contour ? m_part_selection.model_object() : nullptr; if (cut_mo) cut_mo->cut_connectors = mo->cut_connectors; @@ -2401,9 +2404,9 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) } }; - const size_t cut_parts_cnt = m_part_selection.parts.size(); + const size_t cut_parts_cnt = m_part_selection.parts().size(); for (size_t id = 0; id < cut_parts_cnt; ++id) - (m_part_selection.parts[id].selected ? upper : lower)->add_volume(*(cut_mo->volumes[id])); + (m_part_selection.parts()[id].selected ? upper : lower)->add_volume(*(cut_mo->volumes[id])); ModelVolumePtrs& volumes = cut_mo->volumes; if (volumes.size() == cut_parts_cnt) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 37f461038..255d1a568 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -137,25 +137,30 @@ class GLGizmoCut3D : public GLGizmoBase bool m_was_cut_plane_dragged { false }; bool m_was_contour_selected { false }; - struct PartSelection { + class PartSelection { + public: PartSelection() = default; PartSelection(const ModelObject* mo, const Transform3d& cut_matrix, int instance_idx, const Vec3d& center, const Vec3d& normal); - void render(const Vec3d* normal = nullptr); - void toggle_selection(const Vec2d& mouse_pos); - void turn_over_selection(); - ModelObject* model_object() { return model.objects.front(); } - struct Part { GLModel glmodel; MeshRaycaster raycaster; bool selected; }; - Model model; - int instance_idx; - std::vector parts; - bool valid = false; + void render(const Vec3d* normal = nullptr); + void toggle_selection(const Vec2d& mouse_pos); + void turn_over_selection(); + ModelObject* model_object() { return m_model.objects.front(); } + bool valid() const { return m_valid; } + const std::vector& parts() const { return m_parts; } + + + private: + Model m_model; + int m_instance_idx; + std::vector m_parts; + bool m_valid = false; }; PartSelection m_part_selection; From b9bba5ff6b11e1f84df6e9d540536f50d5b6144c Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 6 Apr 2023 14:16:58 +0200 Subject: [PATCH 09/17] Cut: optimization - do not check invalid connectors in each frame --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 39 ++++++++++++++++++++++------ src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 3 ++- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 50e446292..e2e9ec72a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -459,6 +459,7 @@ void GLGizmoCut3D::update_clipper() void GLGizmoCut3D::set_center(const Vec3d& center, bool update_tbb /*=false*/) { set_center_pos(center, update_tbb); + check_and_update_connectors_state(); update_clipper(); } @@ -1199,6 +1200,7 @@ void GLGizmoCut3D::on_dragging(const UpdateData& data) dragging_grabber_xy(data); else if (m_hover_id >= m_connectors_group_id && m_connector_mode == CutConnectorMode::Manual) dragging_connector(data); + check_and_update_connectors_state(); } void GLGizmoCut3D::on_start_dragging() @@ -1224,6 +1226,7 @@ void GLGizmoCut3D::on_stop_dragging() Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Move cut plane"), UndoRedo::SnapshotType::GizmoAction); m_ar_plane_center = m_plane_center; } + //check_and_update_connectors_state(); } void GLGizmoCut3D::set_center_pos(const Vec3d& center_pos, bool update_tbb /*=false*/) @@ -1586,7 +1589,7 @@ void GLGizmoCut3D::apply_selected_connectors(std::function app for (size_t idx = 0; idx < m_selected.size(); idx++) if (m_selected[idx]) apply_fn(idx); - + check_and_update_connectors_state(); update_raycasters_for_picking_transform(); } @@ -2083,7 +2086,7 @@ void GLGizmoCut3D::render_input_window_warning() const { if (m_is_contour_changed) return; - if (m_has_invalid_connector) { + if (! m_invalid_connectors_idxs.empty()) { wxString out = wxString(ImGui::WarningMarkerSmall) + _L("Invalid connectors detected") + ":"; if (m_info_stats.outside_cut_contour > size_t(0)) out += "\n - " + format_wxstr(_L_PLURAL("%1$d connector is out of cut contour", "%1$d connectors are out of cut contour", m_info_stats.outside_cut_contour), @@ -2195,6 +2198,26 @@ bool GLGizmoCut3D::is_conflict_for_connector(size_t idx, const CutConnectors& co return false; } +void GLGizmoCut3D::check_and_update_connectors_state() +{ + m_invalid_connectors_idxs.clear(); + const ModelObject* mo = m_c->selection_info()->model_object(); + auto inst_id = m_c->selection_info()->get_active_instance(); + if (inst_id < 0) + return; + const CutConnectors& connectors = mo->cut_connectors; + const ModelInstance* mi = mo->instances[inst_id]; + const Vec3d& instance_offset = mi->get_offset(); + const double sla_shift = double(m_c->selection_info()->get_sla_shift()); + + for (size_t i = 0; i < connectors.size(); ++i) { + const CutConnector& connector = connectors[i]; + Vec3d pos = connector.pos + instance_offset + sla_shift * Vec3d::UnitZ(); // recalculate connector position to world position + if (is_conflict_for_connector(i, connectors, pos)) + m_invalid_connectors_idxs.emplace_back(i); + } +} + void GLGizmoCut3D::render_connectors() { ::glEnable(GL_DEPTH_TEST); @@ -2220,7 +2243,6 @@ void GLGizmoCut3D::render_connectors() const Vec3d& instance_offset = mi->get_offset(); const double sla_shift = double(m_c->selection_info()->get_sla_shift()); - m_has_invalid_connector = false; m_info_stats.invalidate(); const bool looking_forward = is_looking_forward(); @@ -2233,11 +2255,10 @@ void GLGizmoCut3D::render_connectors() Vec3d pos = connector.pos + instance_offset + sla_shift * Vec3d::UnitZ(); // First decide about the color of the point. - const bool conflict_connector = is_conflict_for_connector(i, connectors, pos); - if (conflict_connector) { - m_has_invalid_connector = true; + assert(std::is_sorted(m_invalid_connectors_idxs.begin(), m_invalid_connectors_idxs.end())); + const bool conflict_connector = std::binary_search(m_invalid_connectors_idxs.begin(), m_invalid_connectors_idxs.end(), i); + if (conflict_connector) render_color = CONNECTOR_ERR_COLOR; - } else // default connector color render_color = connector.attribs.type == CutConnectorType::Dowel ? DOWEL_COLOR : PLAG_COLOR; @@ -2277,7 +2298,7 @@ void GLGizmoCut3D::render_connectors() bool GLGizmoCut3D::can_perform_cut() const { - if (m_has_invalid_connector || (!m_keep_upper && !m_keep_lower) || m_connectors_editing) + if (! m_invalid_connectors_idxs.empty() || (!m_keep_upper && !m_keep_lower) || m_connectors_editing) return false; return true;// has_valid_contour(); @@ -2640,6 +2661,7 @@ bool GLGizmoCut3D::add_connector(CutConnectors& connectors, const Vec2d& mouse_p assert(m_selected.size() == connectors.size()); update_raycasters_for_picking(); m_parent.set_as_dirty(); + check_and_update_connectors_state(); return true; } @@ -2665,6 +2687,7 @@ bool GLGizmoCut3D::delete_selected_connectors(CutConnectors& connectors) assert(m_selected.size() == connectors.size()); update_raycasters_for_picking(); m_parent.set_as_dirty(); + check_and_update_connectors_state(); return true; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 255d1a568..4108690a6 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -133,7 +133,7 @@ class GLGizmoCut3D : public GLGizmoBase GLSelectionRectangle m_selection_rectangle; - bool m_has_invalid_connector{ false }; + std::vector m_invalid_connectors_idxs; bool m_was_cut_plane_dragged { false }; bool m_was_contour_selected { false }; @@ -328,6 +328,7 @@ private: void update_connector_shape(); void validate_connector_settings(); bool process_cut_line(SLAGizmoEventType action, const Vec2d& mouse_position); + void check_and_update_connectors_state(); }; } // namespace GUI From d0ee5e7ca339773f20920cf2e7fab480fe4dbf38 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 20 Apr 2023 11:15:15 +0200 Subject: [PATCH 10/17] Cut: ObjectClipper now allows to not render all the contours --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 26 ++++++++++++-- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 4 ++- src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp | 44 +++++++++++++++++++----- src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp | 3 +- src/slic3r/GUI/MeshUtils.cpp | 28 ++++++++++++--- src/slic3r/GUI/MeshUtils.hpp | 5 +-- 6 files changed, 92 insertions(+), 18 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index e2e9ec72a..1975d6eba 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -1467,6 +1467,19 @@ void GLGizmoCut3D::on_render() m_c->selection_info()->set_use_shift(true); } + + ::glDisable(GL_DEPTH_TEST); + std::vector pts = m_c->object_clipper()->point_per_contour(); + if (! pts.empty()) { + const Vec3d dir = (m_plane_center-pts.front()).dot(m_cut_normal) * m_cut_normal; + for (const Vec3d& pt : pts) + render_model(m_sphere.model, ColorRGBA::GREEN(), wxGetApp().plater()->get_camera().get_view_matrix() * translation_transform(pt+dir)); + } + ::glEnable(GL_DEPTH_TEST); + + + + update_clipper(); init_picking_models(); @@ -1733,7 +1746,7 @@ void GLGizmoCut3D::flip_cut_plane() } -GLGizmoCut3D::PartSelection::PartSelection(const ModelObject* mo, const Transform3d& cut_matrix, int instance_idx_in, const Vec3d& center, const Vec3d& normal) +GLGizmoCut3D::PartSelection::PartSelection(const ModelObject* mo, const Transform3d& cut_matrix, int instance_idx_in, const Vec3d& center, const Vec3d& normal, const CommonGizmosDataObjects::ObjectClipper& oc) { m_model = Model(); m_model.add_object(*mo); @@ -1773,6 +1786,15 @@ GLGizmoCut3D::PartSelection::PartSelection(const ModelObject* mo, const Transfor } } } + + // Now go through the contours and create a map from contours to parts. + if (std::vector pts = oc.point_per_contour();! pts.empty()) { + const Vec3d dir = (center-pts.front()).dot(normal) * normal; + for (Vec3d& pt : pts) + pt = pt+dir; + // pts are now in world coordinates. + } + m_valid = true; } @@ -1798,7 +1820,7 @@ void GLGizmoCut3D::process_contours() const int instance_idx = selection.get_instance_idx(); const int object_idx = selection.get_object_idx(); - m_part_selection = PartSelection(model_objects[object_idx], get_cut_matrix(selection), instance_idx, m_plane_center, m_cut_normal); + m_part_selection = PartSelection(model_objects[object_idx], get_cut_matrix(selection), instance_idx, m_plane_center, m_cut_normal, *m_c->object_clipper()); m_parent.toggle_model_objects_visibility(false); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 4108690a6..b765ce14b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -20,6 +20,8 @@ class Selection; enum class SLAGizmoEventType : unsigned char; +namespace CommonGizmosDataObjects { class ObjectClipper; } + class GLGizmoCut3D : public GLGizmoBase { enum GrabberID { @@ -140,7 +142,7 @@ class GLGizmoCut3D : public GLGizmoBase class PartSelection { public: PartSelection() = default; - PartSelection(const ModelObject* mo, const Transform3d& cut_matrix, int instance_idx, const Vec3d& center, const Vec3d& normal); + PartSelection(const ModelObject* mo, const Transform3d& cut_matrix, int instance_idx, const Vec3d& center, const Vec3d& normal, const CommonGizmosDataObjects::ObjectClipper& oc); struct Part { GLModel glmodel; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index 549540610..3d7b77a50 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -370,21 +370,30 @@ void ObjectClipper::on_release() } -void ObjectClipper::render_cut() const +void ObjectClipper::render_cut(const std::vector* ignore_idxs) const { if (m_clp_ratio == 0.) return; const SelectionInfo* sel_info = get_pool()->selection_info(); const Geometry::Transformation inst_trafo = sel_info->model_object()->instances[sel_info->get_active_instance()]->get_transformation(); + + std::vector ignore_idxs_local = ignore_idxs ? *ignore_idxs : std::vector(); for (auto& clipper : m_clippers) { - Geometry::Transformation trafo = inst_trafo * clipper.second; - trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift())); - clipper.first->set_plane(*m_clp); - clipper.first->set_transformation(trafo); - clipper.first->set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD)); - clipper.first->render_cut({ 1.0f, 0.37f, 0.0f, 1.0f }); - clipper.first->render_contour({ 1.f, 1.f, 1.f, 1.f }); + Geometry::Transformation trafo = inst_trafo * clipper.second; + trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift())); + clipper.first->set_plane(*m_clp); + clipper.first->set_transformation(trafo); + clipper.first->set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD)); + clipper.first->render_cut({ 1.0f, 0.37f, 0.0f, 1.0f }, &ignore_idxs_local); + clipper.first->render_contour({ 1.f, 1.f, 1.f, 1.f }, &ignore_idxs_local); + + // Now update the ignore idxs. Find the first element belonging to the next clipper, + // and remove everything before it and decrement everything by current number of contours. + const int num_of_contours = clipper.first->get_number_of_contours(); + ignore_idxs_local.erase(ignore_idxs_local.begin(), std::find_if(ignore_idxs_local.begin(), ignore_idxs_local.end(), [num_of_contours](size_t idx) { return idx >= num_of_contours; } )); + for (size_t& idx : ignore_idxs_local) + idx -= num_of_contours; } } @@ -415,6 +424,25 @@ bool ObjectClipper::has_valid_contour() const return m_clp_ratio != 0. && std::any_of(m_clippers.begin(), m_clippers.end(), [](const auto& cl) { return cl.first->has_valid_contour(); }); } +std::vector ObjectClipper::point_per_contour() const +{ + std::vector pts; + + const SelectionInfo* sel_info = get_pool()->selection_info(); + const Geometry::Transformation inst_trafo = sel_info->model_object()->instances[sel_info->get_active_instance()]->get_transformation(); + + for (auto& clipper : m_clippers) { + Geometry::Transformation trafo = inst_trafo * clipper.second; + trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift())); + + // FIXME: do not assume just one clipper + pts = clipper.first->point_per_contour(); + //for (Vec3d& v : pts) + // v = trafo.get_matrix() * v; + } + return pts; +} + void ObjectClipper::set_position_by_ratio(double pos, bool keep_normal) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp index 4152cce51..e9d85de8d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp @@ -242,12 +242,13 @@ public: void set_normal(const Vec3d& dir); double get_position() const { return m_clp_ratio; } const ClippingPlane* get_clipping_plane(bool ignore_hide_clipped = false) const; - void render_cut() const; + void render_cut(const std::vector* ignore_idxs = nullptr) const; void set_position_by_ratio(double pos, bool keep_normal); void set_range_and_pos(const Vec3d& cpl_normal, double cpl_offset, double pos); void set_behavior(bool hide_clipped, bool fill_cut, double contour_width); int get_number_of_contours() const; + std::vector point_per_contour() const; int is_projection_inside_cut(const Vec3d& point_in) const; bool has_valid_contour() const; diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index cee684115..8e29a1c52 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -94,7 +94,7 @@ void MeshClipper::set_transformation(const Geometry::Transformation& trafo) } } -void MeshClipper::render_cut(const ColorRGBA& color) +void MeshClipper::render_cut(const ColorRGBA& color, const std::vector* ignore_idxs) { if (! m_result) recalculate_triangles(); @@ -108,7 +108,10 @@ void MeshClipper::render_cut(const ColorRGBA& color) const Camera& camera = wxGetApp().plater()->get_camera(); shader->set_uniform("view_model_matrix", camera.get_view_matrix()); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); - for (CutIsland& isl : m_result->cut_islands) { + for (size_t i=0; icut_islands.size(); ++i) { + if (ignore_idxs && std::binary_search(ignore_idxs->begin(), ignore_idxs->end(), i)) + continue; + CutIsland& isl = m_result->cut_islands[i]; isl.model.set_color(isl.disabled ? ColorRGBA(0.5f, 0.5f, 0.5f, 1.f) : color); isl.model.render(); } @@ -120,7 +123,7 @@ void MeshClipper::render_cut(const ColorRGBA& color) } -void MeshClipper::render_contour(const ColorRGBA& color) +void MeshClipper::render_contour(const ColorRGBA& color, const std::vector* ignore_idxs) { if (! m_result) recalculate_triangles(); @@ -135,7 +138,10 @@ void MeshClipper::render_contour(const ColorRGBA& color) const Camera& camera = wxGetApp().plater()->get_camera(); shader->set_uniform("view_model_matrix", camera.get_view_matrix()); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); - for (CutIsland& isl : m_result->cut_islands) { + for (size_t i=0; icut_islands.size(); ++i) { + if (ignore_idxs && std::binary_search(ignore_idxs->begin(), ignore_idxs->end(), i)) + continue; + CutIsland& isl = m_result->cut_islands[i]; isl.model_expanded.set_color(isl.disabled ? ColorRGBA(1.f, 0.f, 0.f, 1.f) : color); isl.model_expanded.render(); } @@ -166,6 +172,20 @@ bool MeshClipper::has_valid_contour() const return m_result && std::any_of(m_result->cut_islands.begin(), m_result->cut_islands.end(), [](const CutIsland& isl) { return !isl.expoly.empty(); }); } +std::vector MeshClipper::point_per_contour() const +{ + std::vector out; + if (!m_result || m_result->cut_islands.empty()) + return out; + + for (const CutIsland& isl : m_result->cut_islands) { + // FIXME: There might be holes ! + Vec2d c = unscale(isl.expoly.contour.centroid()); + out.emplace_back(m_result->trafo * Vec3d(c.x(), c.y(), 0.)); + } + return out; +} + void MeshClipper::recalculate_triangles() { diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp index 257b500f9..be95daa15 100644 --- a/src/slic3r/GUI/MeshUtils.hpp +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -115,13 +115,14 @@ public: // Render the triangulated cut. Transformation matrices should // be set in world coords. - void render_cut(const ColorRGBA& color); - void render_contour(const ColorRGBA& color); + void render_cut(const ColorRGBA& color, const std::vector* ignore_idxs = nullptr); + void render_contour(const ColorRGBA& color, const std::vector* ignore_idxs = nullptr); // Returns index of the contour which was clicked, -1 otherwise. int is_projection_inside_cut(const Vec3d& point) const; bool has_valid_contour() const; int get_number_of_contours() const { return m_result ? m_result->cut_islands.size() : 0; } + std::vector point_per_contour() const; private: void recalculate_triangles(); From 44aa32d6e2613d38241bd2a961ee4e95fd1d7fa2 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 26 Apr 2023 13:18:12 +0200 Subject: [PATCH 11/17] Cut: correspondence between parts and contours --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 244 ++++++++++++++++------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 10 +- src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp | 14 +- 3 files changed, 179 insertions(+), 89 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 1975d6eba..897414b95 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -1390,19 +1390,110 @@ void GLGizmoCut3D::render_clipper_cut() { if (! m_connectors_editing) ::glDisable(GL_DEPTH_TEST); - m_c->object_clipper()->render_cut(); + + GLboolean cull_face = GL_FALSE; + ::glGetBooleanv(GL_CULL_FACE, &cull_face); + ::glDisable(GL_CULL_FACE); + m_c->object_clipper()->render_cut(m_part_selection.get_ignored_contours_ptr()); + if (cull_face) + ::glEnable(GL_CULL_FACE); + if (! m_connectors_editing) ::glEnable(GL_DEPTH_TEST); } -void GLGizmoCut3D::PartSelection::render(const Vec3d* normal) + +GLGizmoCut3D::PartSelection::PartSelection(const ModelObject* mo, const Transform3d& cut_matrix, int instance_idx_in, const Vec3d& center, const Vec3d& normal, const CommonGizmosDataObjects::ObjectClipper& oc) +{ + m_model = Model(); + m_model.add_object(*mo); + ModelObjectPtrs cut_part_ptrs = m_model.objects.front()->cut(instance_idx_in, cut_matrix, + ModelObjectCutAttribute::KeepUpper | + ModelObjectCutAttribute::KeepLower | + ModelObjectCutAttribute::KeepAsParts); + assert(cut_part_ptrs.size() == 1); + m_model = Model(); + m_model.add_object(*cut_part_ptrs.front()); + + m_instance_idx = instance_idx_in; + + const ModelVolumePtrs& volumes = model_object()->volumes; + + // split to parts + for (int id = int(volumes.size())-1; id >= 0; id--) + if (volumes[id]->is_splittable()) + volumes[id]->split(1); + + m_parts.clear(); + for (const ModelVolume* volume : volumes) { + assert(volume != nullptr); + m_parts.emplace_back(Part{GLModel(), MeshRaycaster(volume->mesh()), true}); + m_parts.back().glmodel.set_color({ 0.f, 0.f, 1.f, 1.f }); + m_parts.back().glmodel.init_from(volume->mesh()); + + // Now check whether this part is below or above the plane. + Transform3d tr = (model_object()->instances[m_instance_idx]->get_matrix() * volume->get_matrix()).inverse(); + Vec3f pos = (tr * center).cast(); + Vec3f norm = (tr.linear().inverse().transpose() * normal).cast(); + for (const Vec3f& v : volume->mesh().its.vertices) { + double p = (v - pos).dot(norm); + if (std::abs(p) > EPSILON) { + m_parts.back().selected = p > 0.; + break; + } + } + } + + // Now go through the contours and create a map from contours to parts. + m_contour_points.clear(); + m_contour_to_parts.clear(); + m_debug_pts = std::vector>(m_parts.size(), std::vector()); + if (std::vector pts = oc.point_per_contour();! pts.empty()) { + + m_contour_to_parts.resize(pts.size()); + + for (size_t pt_idx=0; pt_idxinstances[m_instance_idx]->get_offset()) * translation_transform(model_object()->volumes[part_id]->get_offset())).inverse(); + for (double d : {-1., 1.}) { + const Vec3d dir_mesh = d * tr.linear().inverse().transpose() * normal; + const Vec3d src = tr * (m_contour_points[pt_idx] + d*0.01 * normal); + AABBMesh::hit_result hit = aabb.query_ray_hit(src, dir_mesh); + + m_debug_pts[part_id].emplace_back(src); + + if (hit.is_inside()) { + // This part belongs to this point. + if (d == 1.) + m_contour_to_parts[pt_idx].first.emplace_back(part_id); + else + m_contour_to_parts[pt_idx].second.emplace_back(part_id); + } + } + } + } + + } + + + m_valid = true; +} + +void GLGizmoCut3D::PartSelection::render(const Vec3d* normal, GLModel& sphere_model) { if (! valid()) return; - if (GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light")) { - const Camera& camera = wxGetApp().plater()->get_camera(); + const Camera& camera = wxGetApp().plater()->get_camera(); + if (GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light")) { shader->start_using(); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); shader->set_uniform("emission_factor", 0.f); @@ -1426,8 +1517,52 @@ void GLGizmoCut3D::PartSelection::render(const Vec3d* normal) shader->stop_using(); } + + + + // { // Debugging render: + + // static int idx = -1; + // ImGui::Begin("DEBUG"); + // for (int i=0; i= m_parts.size()) + // idx = -1; + // ImGui::End(); + + // ::glDisable(GL_DEPTH_TEST); + // if (valid()) { + // for (size_t i=0; ivolumes[id]->get_offset(); - Transform3d tr = model_object()->instances[m_instance_idx]->get_matrix() * model_object()->volumes[id]->get_matrix(); + Transform3d tr = translation_transform(model_object()->instances[m_instance_idx]->get_offset()) * translation_transform(model_object()->volumes[id]->get_offset()); if (m_parts[id].raycaster.unproject_on_mesh(mouse_pos, tr, camera, pos, normal)) { hits_id_and_sqdist.emplace_back(id, (camera_pos - tr*(pos.cast())).squaredNorm()); } @@ -1450,6 +1585,20 @@ void GLGizmoCut3D::PartSelection::toggle_selection(const Vec2d& mouse_pos) size_t id = std::min_element(hits_id_and_sqdist.begin(), hits_id_and_sqdist.end(), [](const std::pair& a, const std::pair& b) { return a.second < b.second; })->first; m_parts[id].selected = ! m_parts[id].selected; + + // And now recalculate the contours which should be ignored. + m_ignored_contours.clear(); + size_t cont_id = 0; + for (const auto& [parts_above, parts_below] : m_contour_to_parts) { + for (size_t upper : parts_above) { + bool upper_sel = m_parts[upper].selected; + if (std::find_if(parts_below.begin(), parts_below.end(), [this, &upper_sel](const size_t& i) { return m_parts[i].selected == upper_sel; }) != parts_below.end()) { + m_ignored_contours.emplace_back(cont_id); + break; + } + } + ++cont_id; + } } } @@ -1468,18 +1617,6 @@ void GLGizmoCut3D::on_render() } - ::glDisable(GL_DEPTH_TEST); - std::vector pts = m_c->object_clipper()->point_per_contour(); - if (! pts.empty()) { - const Vec3d dir = (m_plane_center-pts.front()).dot(m_cut_normal) * m_cut_normal; - for (const Vec3d& pt : pts) - render_model(m_sphere.model, ColorRGBA::GREEN(), wxGetApp().plater()->get_camera().get_view_matrix() * translation_transform(pt+dir)); - } - ::glEnable(GL_DEPTH_TEST); - - - - update_clipper(); init_picking_models(); @@ -1489,9 +1626,9 @@ void GLGizmoCut3D::on_render() render_connectors(); if (!m_connectors_editing) - m_part_selection.render(); + m_part_selection.render(nullptr, m_sphere.model); else - m_part_selection.render(&m_cut_normal); + m_part_selection.render(&m_cut_normal, m_sphere.model); render_clipper_cut(); @@ -1745,61 +1882,6 @@ void GLGizmoCut3D::flip_cut_plane() m_part_selection.turn_over_selection(); } - -GLGizmoCut3D::PartSelection::PartSelection(const ModelObject* mo, const Transform3d& cut_matrix, int instance_idx_in, const Vec3d& center, const Vec3d& normal, const CommonGizmosDataObjects::ObjectClipper& oc) -{ - m_model = Model(); - m_model.add_object(*mo); - ModelObjectPtrs cut_part_ptrs = m_model.objects.front()->cut(instance_idx_in, cut_matrix, - ModelObjectCutAttribute::KeepUpper | - ModelObjectCutAttribute::KeepLower | - ModelObjectCutAttribute::KeepAsParts); - assert(cut_part_ptrs.size() == 1); - m_model = Model(); - m_model.add_object(*cut_part_ptrs.front()); - - m_instance_idx = instance_idx_in; - - const ModelVolumePtrs& volumes = model_object()->volumes; - - // split to parts - for (int id = int(volumes.size())-1; id >= 0; id--) - if (volumes[id]->is_splittable()) - volumes[id]->split(1); - - m_parts.clear(); - for (const ModelVolume* volume : volumes) { - assert(volume != nullptr); - m_parts.emplace_back(Part{GLModel(), MeshRaycaster(volume->mesh()), true}); - m_parts.back().glmodel.set_color({ 0.f, 0.f, 1.f, 1.f }); - m_parts.back().glmodel.init_from(volume->mesh()); - - // Now check whether this part is below or above the plane. - Transform3d tr = (model_object()->instances[m_instance_idx]->get_matrix() * volume->get_matrix()).inverse(); - Vec3f pos = (tr * center).cast(); - Vec3f norm = (tr.linear().inverse().transpose() * normal).cast(); - for (const Vec3f& v : volume->mesh().its.vertices) { - double p = (v - pos).dot(norm); - if (std::abs(p) > EPSILON) { - m_parts.back().selected = p > 0.; - break; - } - } - } - - // Now go through the contours and create a map from contours to parts. - if (std::vector pts = oc.point_per_contour();! pts.empty()) { - const Vec3d dir = (center-pts.front()).dot(normal) * normal; - for (Vec3d& pt : pts) - pt = pt+dir; - // pts are now in world coordinates. - } - - - m_valid = true; -} - - void GLGizmoCut3D::reset_cut_by_contours() { m_part_selection = PartSelection(); @@ -2542,8 +2624,18 @@ bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, Vec3d& po } }*/ - if (m_c->object_clipper()->is_projection_inside_cut(hit) == -1) - return false; + { + // Do not react to clicks outside a contour (or inside a contour that is ignored) + int cont_id = m_c->object_clipper()->is_projection_inside_cut(hit); + if (cont_id == -1) + return false; + if (m_part_selection.valid()) { + const std::vector& ign = *m_part_selection.get_ignored_contours_ptr(); + if (std::find(ign.begin(), ign.end(), cont_id) != ign.end()) + return false; + } + } + // recalculate hit to object's local position Vec3d hit_d = hit; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index b765ce14b..0f300b0fa 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -150,12 +150,18 @@ class GLGizmoCut3D : public GLGizmoBase bool selected; }; - void render(const Vec3d* normal = nullptr); + void render(const Vec3d* normal, GLModel& sphere_model); void toggle_selection(const Vec2d& mouse_pos); void turn_over_selection(); ModelObject* model_object() { return m_model.objects.front(); } bool valid() const { return m_valid; } const std::vector& parts() const { return m_parts; } + const std::vector* get_ignored_contours_ptr() const { return (valid() ? &m_ignored_contours : nullptr); } + + std::vector m_contour_points; // TEMPORARILY PUBLIC + std::vector, std::vector>> m_contour_to_parts; // for each contour, there is a vector of parts above and a vector of parts below + std::vector m_ignored_contours; // contour that should not be rendered (the parts on both sides will both be parts of the same object) + std::vector> m_debug_pts; private: @@ -312,7 +318,7 @@ private: void discard_cut_line_processing(); void render_cut_plane(); - void render_model(GLModel& model, const ColorRGBA& color, Transform3d view_model_matrix); + static void render_model(GLModel& model, const ColorRGBA& color, Transform3d view_model_matrix); void render_line(GLModel& line_model, const ColorRGBA& color, Transform3d view_model_matrix, float width); void render_rotation_snapping(GrabberID axis, const ColorRGBA& color); void render_grabber_connection(const ColorRGBA& color, Transform3d view_matrix); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index 3d7b77a50..79c77692a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -428,17 +428,9 @@ std::vector ObjectClipper::point_per_contour() const { std::vector pts; - const SelectionInfo* sel_info = get_pool()->selection_info(); - const Geometry::Transformation inst_trafo = sel_info->model_object()->instances[sel_info->get_active_instance()]->get_transformation(); - - for (auto& clipper : m_clippers) { - Geometry::Transformation trafo = inst_trafo * clipper.second; - trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift())); - - // FIXME: do not assume just one clipper - pts = clipper.first->point_per_contour(); - //for (Vec3d& v : pts) - // v = trafo.get_matrix() * v; + for (const auto& clipper : m_clippers) { + const std::vector pts_clipper = clipper.first->point_per_contour(); + pts.insert(pts.end(), pts_clipper.begin(), pts_clipper.end());; } return pts; } From ab0ade05395e2c73aeb2d809edd6ae8f164010a3 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 27 Apr 2023 11:14:38 +0200 Subject: [PATCH 12/17] Cut: contours indexing no longer breaks when the normal is inverted, detection of the situation when all parts are assigned to one side --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 19 ++++++++++- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 12 +++---- src/slic3r/GUI/MeshUtils.cpp | 48 +++++++++++++++++++++++++--- src/slic3r/GUI/MeshUtils.hpp | 1 + 4 files changed, 68 insertions(+), 12 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 897414b95..dc068e7cb 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -1563,6 +1563,21 @@ void GLGizmoCut3D::PartSelection::render(const Vec3d* normal, GLModel& sphere_mo } +bool GLGizmoCut3D::PartSelection::is_one_object() const +{ + // In theory, the implementation could be just this: + // return m_contour_to_parts.size() == m_ignored_contours.size(); + // However, this would require that the part-contour correspondence works + // flawlessly. Because it is currently not always so for self-intersecting + // objects, let's better check the parts itself: + if (m_parts.size() < 2) + return true; + return std::all_of(m_parts.begin(), m_parts.end(), [this](const Part& part) { + return part.selected == m_parts.front().selected; + }); +} + + void GLGizmoCut3D::PartSelection::toggle_selection(const Vec2d& mouse_pos) { // FIXME: Cache the transforms. @@ -1984,7 +1999,7 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) add_vertical_scaled_interval(0.75f); - m_imgui->disabled_begin(!m_keep_upper || !m_keep_lower || m_keep_as_parts); + m_imgui->disabled_begin(!m_keep_upper || !m_keep_lower || m_keep_as_parts || (m_part_selection.valid() && m_part_selection.is_one_object())); if (m_imgui->button(has_connectors ? _L("Edit connectors") : _L("Add connectors"))) set_connectors_editing(true); m_imgui->disabled_end(); @@ -2404,6 +2419,8 @@ bool GLGizmoCut3D::can_perform_cut() const { if (! m_invalid_connectors_idxs.empty() || (!m_keep_upper && !m_keep_lower) || m_connectors_editing) return false; + if (m_part_selection.valid()) + return ! m_part_selection.is_one_object(); return true;// has_valid_contour(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 0f300b0fa..02f62d628 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -155,20 +155,20 @@ class GLGizmoCut3D : public GLGizmoBase void turn_over_selection(); ModelObject* model_object() { return m_model.objects.front(); } bool valid() const { return m_valid; } + bool is_one_object() const; const std::vector& parts() const { return m_parts; } const std::vector* get_ignored_contours_ptr() const { return (valid() ? &m_ignored_contours : nullptr); } - std::vector m_contour_points; // TEMPORARILY PUBLIC - std::vector, std::vector>> m_contour_to_parts; // for each contour, there is a vector of parts above and a vector of parts below - std::vector m_ignored_contours; // contour that should not be rendered (the parts on both sides will both be parts of the same object) - std::vector> m_debug_pts; - - private: Model m_model; int m_instance_idx; std::vector m_parts; bool m_valid = false; + std::vector, std::vector>> m_contour_to_parts; // for each contour, there is a vector of parts above and a vector of parts below + std::vector m_ignored_contours; // contour that should not be rendered (the parts on both sides will both be parts of the same object) + + std::vector m_contour_points; // Debugging + std::vector> m_debug_pts; // Debugging }; PartSelection m_part_selection; diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index 8e29a1c52..663712ca4 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -174,13 +174,40 @@ bool MeshClipper::has_valid_contour() const std::vector MeshClipper::point_per_contour() const { + assert(m_result); std::vector out; - if (!m_result || m_result->cut_islands.empty()) - return out; - + for (const CutIsland& isl : m_result->cut_islands) { - // FIXME: There might be holes ! - Vec2d c = unscale(isl.expoly.contour.centroid()); + assert(isl.expoly.contour.size() > 2); + // Now return a point lying inside the contour but not in a hole. + // We do this by taking a point lying close to the edge, repeating + // this several times for different edges and distances from them. + // (We prefer point not extremely close to the border. + bool done = false; + Vec2d p; + size_t i = 1; + while (i < isl.expoly.contour.size()) { + const Vec2d& a = unscale(isl.expoly.contour.points[i-1]); + const Vec2d& b = unscale(isl.expoly.contour.points[i]); + Vec2d n = (b-a).normalized(); + std::swap(n.x(), n.y()); + n.x() = -1 * n.x(); + double f = 10.; + while (f > 0.05) { + p = (0.5*(b+a)) + f * n; + if (isl.expoly.contains(Point::new_scale(p))) { + done = true; + break; + } + f = f/10.; + } + if (done) + break; + i += isl.expoly.contour.size() / 5; + } + // If the above failed, just return the centroid, regardless of whether + // it is inside the contour or in a hole (we must return something). + Vec2d c = done ? p : unscale(isl.expoly.contour.centroid()); out.emplace_back(m_result->trafo * Vec3d(c.x(), c.y(), 0.)); } return out; @@ -366,7 +393,18 @@ void MeshClipper::recalculate_triangles() isl.expoly = std::move(exp); isl.expoly_bb = get_extents(exp); + isl.hash = 0; + for (const Point& pt : isl.expoly.contour) { + isl.hash ^= pt.x(); + isl.hash ^= pt.y(); + } } + + // Now sort the islands so they are in defined order. This is a hack needed by cut gizmo, which sometimes + // flips the normal of the cut, in which case the contours stay the same but their order may change. + std::sort(m_result->cut_islands.begin(), m_result->cut_islands.end(), [](const CutIsland& a, const CutIsland& b) { + return a.hash < b.hash; + }); } diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp index be95daa15..e5fa97e39 100644 --- a/src/slic3r/GUI/MeshUtils.hpp +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -141,6 +141,7 @@ private: ExPolygon expoly; BoundingBox expoly_bb; bool disabled = false; + coord_t hash; }; struct ClipResult { std::vector cut_islands; From 0e1d43322caa4aa684513302848201030ea182b7 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 27 Apr 2023 12:15:19 +0200 Subject: [PATCH 13/17] Cut: Deallocate memory when the gizmo is turned off --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 7 +++++++ src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 1 + 2 files changed, 8 insertions(+) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index dc068e7cb..5aa4896df 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -905,6 +905,13 @@ void GLGizmoCut3D::on_set_state() m_selected.clear(); m_parent.set_use_color_clip_plane(false); m_c->selection_info()->set_use_shift(false); + + // Make sure that the part selection data are released when the gizmo is closed. + // The CallAfter is needed because in perform_cut, the gizmo is closed BEFORE + // the cut is performed (because of undo/redo snapshots), so the data would + // be deleted prematurely. + if (m_part_selection.valid()) + wxGetApp().CallAfter([this]() { m_part_selection = PartSelection(); }); } } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 02f62d628..627f6c133 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -143,6 +143,7 @@ class GLGizmoCut3D : public GLGizmoBase public: PartSelection() = default; PartSelection(const ModelObject* mo, const Transform3d& cut_matrix, int instance_idx, const Vec3d& center, const Vec3d& normal, const CommonGizmosDataObjects::ObjectClipper& oc); + ~PartSelection() { m_model.clear_objects(); } struct Part { GLModel glmodel; From 7b576d33127c6f82299eba6050164307ac14d4d4 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 3 May 2023 13:22:18 +0200 Subject: [PATCH 14/17] Cut: - fixed incomplete message about invalid connectors - added detection of a connectors placed on an internal contour - fixed a hang during part selection calculation when a contour was too small --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 19 ++++++++++++++----- src/slic3r/GUI/MeshUtils.cpp | 2 +- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 5aa4896df..1179830a0 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -356,6 +356,7 @@ bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event) if (! m_part_selection.valid()) process_contours(); m_part_selection.toggle_selection(mouse_pos); + check_and_update_connectors_state(); // after a contour is deactivated, its connectors are inside the object return true; } @@ -2283,9 +2284,18 @@ bool GLGizmoCut3D::is_outside_of_cut_contour(size_t idx, const CutConnectors& co its_transform(mesh, translation_transform(cur_pos) * m_rotation_m); for (const Vec3f& vertex : vertices) { - if (m_c->object_clipper() && m_c->object_clipper()->is_projection_inside_cut(vertex.cast()) == -1) { - m_info_stats.outside_cut_contour++; - return true; + if (m_c->object_clipper()) { + int contour_idx = m_c->object_clipper()->is_projection_inside_cut(vertex.cast()); + bool is_invalid = (contour_idx == -1); + if (m_part_selection.valid() && ! is_invalid) { + assert(contour_idx >= 0); + const std::vector& ignored = *(m_part_selection.get_ignored_contours_ptr()); + is_invalid = (std::find(ignored.begin(), ignored.end(), size_t(contour_idx)) != ignored.end()); + } + if (is_invalid) { + m_info_stats.outside_cut_contour++; + return true; + } } } @@ -2326,6 +2336,7 @@ bool GLGizmoCut3D::is_conflict_for_connector(size_t idx, const CutConnectors& co void GLGizmoCut3D::check_and_update_connectors_state() { + m_info_stats.invalidate(); m_invalid_connectors_idxs.clear(); const ModelObject* mo = m_c->selection_info()->model_object(); auto inst_id = m_c->selection_info()->get_active_instance(); @@ -2369,8 +2380,6 @@ void GLGizmoCut3D::render_connectors() const Vec3d& instance_offset = mi->get_offset(); const double sla_shift = double(m_c->selection_info()->get_sla_shift()); - m_info_stats.invalidate(); - const bool looking_forward = is_looking_forward(); for (size_t i = 0; i < connectors.size(); ++i) { diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index 663712ca4..99a241c48 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -203,7 +203,7 @@ std::vector MeshClipper::point_per_contour() const } if (done) break; - i += isl.expoly.contour.size() / 5; + i += std::max(size_t(2), isl.expoly.contour.size() / 5); } // If the above failed, just return the centroid, regardless of whether // it is inside the contour or in a hole (we must return something). From 2aa55ef950a5372d45a68a32b34d1d8e366dd945 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 3 May 2023 16:14:26 +0200 Subject: [PATCH 15/17] Cut: fixed an issue when the view rotates to the other side of the plane --- src/slic3r/GUI/MeshUtils.cpp | 11 +++++------ src/slic3r/GUI/MeshUtils.hpp | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index 99a241c48..60ea6c856 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -392,12 +392,11 @@ void MeshClipper::recalculate_triangles() } isl.expoly = std::move(exp); - isl.expoly_bb = get_extents(exp); - isl.hash = 0; - for (const Point& pt : isl.expoly.contour) { - isl.hash ^= pt.x(); - isl.hash ^= pt.y(); - } + isl.expoly_bb = get_extents(isl.expoly); + + Point centroid_scaled = isl.expoly.contour.centroid(); + Vec3d centroid_world = m_result->trafo * Vec3d(unscale(centroid_scaled).x(), unscale(centroid_scaled).y(), 0.); + isl.hash = isl.expoly.contour.size() + size_t(std::abs(100.*centroid_world.x())) + size_t(std::abs(100.*centroid_world.y())) + size_t(std::abs(100.*centroid_world.z())); } // Now sort the islands so they are in defined order. This is a hack needed by cut gizmo, which sometimes diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp index e5fa97e39..3645ecc02 100644 --- a/src/slic3r/GUI/MeshUtils.hpp +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -141,7 +141,7 @@ private: ExPolygon expoly; BoundingBox expoly_bb; bool disabled = false; - coord_t hash; + size_t hash; }; struct ClipResult { std::vector cut_islands; From e4c01a58573cb00e2d2e9aca1b5c6e44c026669e Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 12 May 2023 13:25:27 +0200 Subject: [PATCH 16/17] Cut fixes: - When cutting a scaled part, do not apply the transformation twice - Right click does not trigger part selection when any modifier is used - When 'flip plane' is clicked, extra render is forced - The choice to keep object A/B is enabled even when part selection is active - 'Cut into' radio buttons are disabled when part selection is active - Added a missing update of connector state - Amended tooltip --- src/libslic3r/Model.cpp | 2 ++ src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 11 +++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index c7844f807..9aa4cf3c6 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1595,6 +1595,8 @@ void ModelObject::reset_instance_transformation(ModelObject* object, size_t src_ for (size_t i = 0; i < object->instances.size(); ++i) { auto& obj_instance = object->instances[i]; const double rot_z = obj_instance->get_rotation().z(); + + obj_instance->set_transformation(Transformation(obj_instance->get_transformation().get_matrix_no_scaling_factor())); Vec3d rotation = Vec3d::Zero(); if (!flip && !place_on_cut) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 1179830a0..2cb0a8151 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -250,7 +250,8 @@ std::string GLGizmoCut3D::get_tooltip() const if (!m_dragging && m_hover_id == CutPlane) return _u8L("Click to flip the cut plane\n" - "Drag to move the cut plane"); + "Drag to move the cut plane\n" + "Right-click a part to assign it to the other side"); if (tooltip.empty() && (m_hover_id == X || m_hover_id == Y)) { std::string axis = m_hover_id == X ? "X" : "Y"; @@ -351,7 +352,7 @@ bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event) return true; } else if (mouse_event.RightDown()) { - if (! m_connectors_editing) { + if (! m_connectors_editing && mouse_event.GetModifiers() == wxMOD_NONE) { // Check the internal part raycasters. if (! m_part_selection.valid()) process_contours(); @@ -1868,6 +1869,7 @@ void GLGizmoCut3D::reset_cut_plane() m_ar_plane_center = m_plane_center; reset_cut_by_contours(); + m_parent.request_extra_frame(); } void GLGizmoCut3D::invalidate_cut_plane() @@ -2074,7 +2076,7 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) ImGuiWrapper::text(_L("Cut result") + ": "); add_vertical_scaled_interval(0.5f); - m_imgui->disabled_begin(has_connectors || m_keep_as_parts || m_part_selection.valid()); + m_imgui->disabled_begin(has_connectors || m_keep_as_parts); render_part_name("A", m_keep_upper, m_imgui->to_ImU32(UPPER_PART_COLOR)); ImGui::SameLine(h_shift + ImGui::GetCurrentWindow()->WindowPadding.x); render_part_name("B", m_keep_lower, m_imgui->to_ImU32(LOWER_PART_COLOR)); @@ -2090,7 +2092,7 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) add_vertical_scaled_interval(0.75f); - m_imgui->disabled_begin(has_connectors); + m_imgui->disabled_begin(has_connectors || m_part_selection.valid()); ImGuiWrapper::text(_L("Cut into") + ":"); if (m_part_selection.valid()) @@ -2693,6 +2695,7 @@ void GLGizmoCut3D::reset_connectors() m_c->selection_info()->model_object()->cut_connectors.clear(); update_raycasters_for_picking(); clear_selection(); + check_and_update_connectors_state(); } void GLGizmoCut3D::init_connector_shapes() From 7553cf1007a3178e4c6e5034d4f8a266d60dd224 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 12 May 2023 14:20:04 +0200 Subject: [PATCH 17/17] Cut: Respect to the selected parts when perform a cut --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 2cb0a8151..2aaea5391 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -2540,23 +2540,24 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) ModelObjectPtrs cut_object_ptrs; if (cut_by_contour) { // apply cut attributes for object - cut_mo->apply_cut_attributes(ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper | - only_if(create_dowels_as_separate_object, ModelObjectCutAttribute::CreateDowels)); + if (m_keep_upper && m_keep_lower) + cut_mo->apply_cut_attributes(ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper | + only_if(create_dowels_as_separate_object, ModelObjectCutAttribute::CreateDowels)); // Clone the object to duplicate instances, materials etc. ModelObject* upper{ nullptr }; - cut_mo->clone_for_cut(&upper); + if (m_keep_upper) cut_mo->clone_for_cut(&upper); ModelObject* lower{ nullptr }; - cut_mo->clone_for_cut(&lower); + if (m_keep_lower) cut_mo->clone_for_cut(&lower); auto add_cut_objects = [this, &instance_idx, &cut_matrix](ModelObjectPtrs& cut_objects, ModelObject* upper, ModelObject* lower, bool invalidate_cut = true) { - if (!upper->volumes.empty()) { + if (upper && !upper->volumes.empty()) { ModelObject::reset_instance_transformation(upper, instance_idx, cut_matrix, m_place_on_cut_upper, m_rotate_upper); if (invalidate_cut) upper->invalidate_cut(); cut_objects.push_back(upper); } - if (!lower->volumes.empty()) { + if (lower && !lower->volumes.empty()) { ModelObject::reset_instance_transformation(lower, instance_idx, cut_matrix, m_place_on_cut_lower, m_place_on_cut_lower || m_rotate_lower); if (invalidate_cut) lower->invalidate_cut(); @@ -2565,8 +2566,10 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) }; const size_t cut_parts_cnt = m_part_selection.parts().size(); - for (size_t id = 0; id < cut_parts_cnt; ++id) - (m_part_selection.parts()[id].selected ? upper : lower)->add_volume(*(cut_mo->volumes[id])); + for (size_t id = 0; id < cut_parts_cnt; ++id) { + if (ModelObject* obj = (m_part_selection.parts()[id].selected ? upper : lower)) + obj->add_volume(*(cut_mo->volumes[id])); + } ModelVolumePtrs& volumes = cut_mo->volumes; if (volumes.size() == cut_parts_cnt)