diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 5be1efed12..4c83e1f0d2 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -4155,10 +4155,11 @@ void ObjectList::change_part_type() if (obj_idx < 0) return; const ModelVolumeType type = volume->type(); + const ModelObject* obj = object(obj_idx); if (type == ModelVolumeType::MODEL_PART) { int model_part_cnt = 0; - for (auto vol : (*m_objects)[obj_idx]->volumes) { + for (auto vol : obj->volumes) { if (vol->type() == ModelVolumeType::MODEL_PART) ++model_part_cnt; } @@ -4169,9 +4170,18 @@ void ObjectList::change_part_type() } } - const wxString names[] = { _L("Part"), _L("Negative Volume"), _L("Modifier"), _L("Support Blocker"), _L("Support Enforcer") }; - auto new_type = ModelVolumeType(wxGetApp().GetSingleChoiceIndex(_L("Type:"), _L("Select type of part"), wxArrayString(5, names), int(type))); + const bool is_cut_object = obj->is_cut(); + wxArrayString names; + names.Alloc(is_cut_object ? 3 : 5); + if (!is_cut_object) + for (const wxString& type : { _L("Part"), _L("Negative Volume") }) + names.Add(type); + for (const wxString& type : { _L("Modifier"), _L("Support Blocker"), _L("Support Enforcer") } ) + names.Add(type); + + const int type_shift = is_cut_object ? 2 : 0; + auto new_type = ModelVolumeType(type_shift + wxGetApp().GetSingleChoiceIndex(_L("Type:"), _L("Select type of part"), names, int(type) - type_shift)); if (new_type == type || new_type == ModelVolumeType::INVALID) return; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 7695a902d6..802cd3006e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -928,6 +928,7 @@ void GLGizmoCut3D::on_set_state() { if (m_state == On) { update_bb(); + m_connectors_editing = !m_selected.empty(); // initiate archived values m_ar_plane_center = m_plane_center; @@ -937,6 +938,7 @@ void GLGizmoCut3D::on_set_state() } else { m_c->object_clipper()->release(); + m_selected.clear(); } force_update_clipper_on_render = m_state == On; } @@ -1257,16 +1259,20 @@ void GLGizmoCut3D::on_stop_dragging() void GLGizmoCut3D::set_center_pos(const Vec3d& center_pos, bool force/* = false*/) { - const BoundingBoxf3 tbb = transformed_bounding_box(center_pos); - - bool can_set_center_pos = force || (tbb.max.z() > -1. && tbb.min.z() < 1.); + bool can_set_center_pos = force; if (!can_set_center_pos) { - const double old_dist = (m_bb_center - m_plane_center).norm(); - const double new_dist = (m_bb_center - center_pos).norm(); - // check if forcing is reasonable - if ( new_dist < old_dist) + const BoundingBoxf3 tbb = transformed_bounding_box(center_pos); + if (tbb.max.z() > -1. && tbb.min.z() < 1.) can_set_center_pos = true; + else { + const double old_dist = (m_bb_center - m_plane_center).norm(); + const double new_dist = (m_bb_center - center_pos).norm(); + // check if forcing is reasonable + if (new_dist < old_dist) + can_set_center_pos = true; + } } + if (can_set_center_pos) { m_plane_center = center_pos; m_center_offset = m_plane_center - m_bb_center; @@ -1299,7 +1305,7 @@ BoundingBoxf3 GLGizmoCut3D::transformed_bounding_box(const Vec3d& plane_center, if (!mo) return ret; const int instance_idx = sel_info->get_active_instance(); - if (instance_idx < 0) + if (instance_idx < 0 || mo->instances.empty()) return ret; const ModelInstance* mi = mo->instances[instance_idx]; @@ -1372,7 +1378,6 @@ bool GLGizmoCut3D::update_bb() clear_selection(); if (CommonGizmosDataObjects::SelectionInfo* selection = m_c->selection_info()) m_selected.resize(selection->model_object()->cut_connectors.size(), false); - m_connectors_editing = !m_selected.empty(); return true; } @@ -1791,8 +1796,18 @@ void GLGizmoCut3D::init_input_window_data(CutConnectors &connectors) void GLGizmoCut3D::render_input_window_warning() const { - if (wxGetApp().plater()->printer_technology() == ptFFF && m_has_invalid_connector) - m_imgui->text(wxString(ImGui::WarningMarkerSmall) + _L("Invalid connectors detected.")); + if (wxGetApp().plater()->printer_technology() == ptFFF && m_has_invalid_connector) { + 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), + m_info_stats.outside_cut_contour); + if (m_info_stats.outside_bb > size_t(0)) + out += "\n - " + format_wxstr(_L_PLURAL("%1$d connector is out of object", "%1$d connectors are out of object", m_info_stats.outside_bb), + m_info_stats.outside_bb); + if (m_info_stats.is_overlap) + out += "\n - " + _L("Some connectors are overlapped"); + m_imgui->text(out); + } if (!m_keep_upper && !m_keep_lower) m_imgui->text(wxString(ImGui::WarningMarkerSmall) + _L("Invalid state. \nNo one part is selected for keep after cut")); } @@ -1848,24 +1863,30 @@ Transform3d GLGizmoCut3D::get_volume_transformation(const ModelVolume* volume) c bool GLGizmoCut3D::is_conflict_for_connector(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()->containes(cur_pos)) + if (m_c->object_clipper() && !m_c->object_clipper()->is_projection_inside_cut(cur_pos)) { + m_info_stats.outside_cut_contour++; return true; + } const CutConnector& cur_connector = connectors[idx]; const Transform3d matrix = translation_transform(cur_pos) * m_rotation_m * scale_transform(Vec3f(cur_connector.radius, cur_connector.radius, cur_connector.height).cast()); const BoundingBoxf3 cur_tbb = m_shapes[cur_connector.attribs].model.get_bounding_box().transformed(matrix); - if (!bounding_box().contains(cur_tbb)) + if (!bounding_box().contains(cur_tbb)) { + m_info_stats.outside_bb++; return true; + } for (size_t i = 0; i < connectors.size(); ++i) { if (i == idx) continue; const CutConnector& connector = connectors[i]; - if ((connector.pos - cur_connector.pos).norm() < double(connector.radius + cur_connector.radius)) + if ((connector.pos - cur_connector.pos).norm() < double(connector.radius + cur_connector.radius)) { + m_info_stats.is_overlap = true; return true; + } } return false; @@ -1899,6 +1920,7 @@ void GLGizmoCut3D::render_connectors() const Vec3d& normal = cp && cp->is_active() ? cp->get_normal() : m_clp_normal; m_has_invalid_connector = false; + m_info_stats.invalidate(); for (size_t i = 0; i < connectors.size(); ++i) { const CutConnector& connector = connectors[i]; @@ -1970,6 +1992,8 @@ void GLGizmoCut3D::apply_connectors_in_model(ModelObject* mo, bool &create_dowel void GLGizmoCut3D::perform_cut(const Selection& selection) { + if (!can_perform_cut()) + return; const int instance_idx = selection.get_instance_idx(); const int object_idx = selection.get_object_idx(); @@ -1985,13 +2009,13 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) // 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 double object_cut_z = m_plane_center.z() - 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; - if (0.0 < object_cut_z) { + // perform cut + { Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Cut by Plane")); bool create_dowels_as_separate_object = false; @@ -2008,9 +2032,6 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower) | only_if(create_dowels_as_separate_object, ModelObjectCutAttribute::CreateDowels)); } - else { - // the object is SLA-elevated and the plane is under it. - } } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index e33c5c897c..e0fc4d3e87 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -74,6 +74,19 @@ class GLGizmoCut3D : public GLGizmoBase Vec3d m_old_center; + struct InvalidConnectorsStatistics + { + unsigned int outside_cut_contour; + unsigned int outside_bb; + bool is_overlap; + + void invalidate() { + outside_cut_contour = 0; + outside_bb = 0; + is_overlap = false; + } + } m_info_stats; + bool m_keep_upper{ true }; bool m_keep_lower{ true }; bool m_place_on_cut_upper{ true }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index 435d8dc6e4..9734f1b77f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -456,62 +456,14 @@ void ObjectClipper::render_cut() const } } -bool ObjectClipper::containes(Vec3d point) const +bool ObjectClipper::is_projection_inside_cut(const Vec3d& point) const { - if (m_clp_ratio == 0.) - return false; - const SelectionInfo* sel_info = get_pool()->selection_info(); - int sel_instance_idx = sel_info->get_active_instance(); - if (sel_instance_idx < 0) - return false; - const ModelObject* mo = sel_info->model_object(); - const Geometry::Transformation inst_trafo = mo->instances[sel_instance_idx]->get_transformation(); - - size_t clipper_id = 0; - for (const ModelVolume* mv : mo->volumes) { - const Geometry::Transformation vol_trafo = mv->get_transformation(); - Geometry::Transformation trafo = inst_trafo * vol_trafo; - trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift())); - - auto& clipper = m_clippers[clipper_id]; - clipper->set_plane(*m_clp); - clipper->set_transformation(trafo); - clipper->set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD)); - if (clipper->contains(point)) - return true; - - ++clipper_id; - } - return false; + return m_clp_ratio != 0. && std::any_of(m_clippers.begin(), m_clippers.end(), [point](const std::unique_ptr& cl) { return cl->is_projection_inside_cut(point); }); } bool ObjectClipper::has_valid_contour() const { - if (m_clp_ratio == 0.) - return false; - const SelectionInfo* sel_info = get_pool()->selection_info(); - int sel_instance_idx = sel_info->get_active_instance(); - if (sel_instance_idx < 0) - return false; - const ModelObject* mo = sel_info->model_object(); - const Geometry::Transformation inst_trafo = mo->instances[sel_instance_idx]->get_transformation(); - - size_t clipper_id = 0; - for (const ModelVolume* mv : mo->volumes) { - const Geometry::Transformation vol_trafo = mv->get_transformation(); - Geometry::Transformation trafo = inst_trafo * vol_trafo; - trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift())); - - auto& clipper = m_clippers[clipper_id]; - clipper->set_plane(*m_clp); - clipper->set_transformation(trafo); - clipper->set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD)); - if (clipper->has_valid_contour()) - return true; - - ++clipper_id; - } - return false; + return m_clp_ratio != 0. && std::any_of(m_clippers.begin(), m_clippers.end(), [](const std::unique_ptr& cl) { return cl->has_valid_contour(); }); } 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 79483e4033..f8ead27f9e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp @@ -266,7 +266,7 @@ public: void pass_mouse_click(const Vec3d& pt); std::vector get_disabled_contours() const; - bool containes(Vec3d point) const; + bool 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 f711ecf03c..5ec066f441 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -146,41 +146,23 @@ void MeshClipper::render_contour() #endif // ENABLE_LEGACY_OPENGL_REMOVAL } -bool MeshClipper::contains(Vec3d point) +bool MeshClipper::is_projection_inside_cut(const Vec3d& point_in) const { - if (!m_result) - recalculate_triangles(); + if (!m_result || m_result->cut_islands.empty()) + return false; + 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) { - BoundingBoxf3 bb = isl.model_expanded.get_bounding_box(); - - // instead of using of standard bb.contains(point) - // because of precision (Note, that model_expanded is pretranslate(0.003 * normal.normalized())) - constexpr double pres = 0.01; - bool ret = (point.x() > bb.min.x() || is_approx(point.x(), bb.min.x(), pres)) && (point.x() < bb.max.x() || is_approx(point.x(), bb.max.x(), pres)) - && (point.y() > bb.min.y() || is_approx(point.y(), bb.min.y(), pres)) && (point.y() < bb.max.y() || is_approx(point.y(), bb.max.y(), pres)) - && (point.z() > bb.min.z() || is_approx(point.z(), bb.min.z(), pres)) && (point.z() < bb.max.z() || is_approx(point.z(), bb.max.z(), pres)); - if (ret) { - // when we detected, that model_expanded's bb contains a point, then check if its polygon contains this point - Vec3d point_inv = m_result->trafo.inverse() * point; - Point pt = Point(scale_(point_inv.x()), scale_(point_inv.y())); - if (isl.expoly.contains(pt)) - return true; - } + for (const CutIsland& isl : m_result->cut_islands) { + if (isl.expoly_bb.contains(pt_2d) && isl.expoly.contains(pt_2d)) + return true; } return false; } -bool MeshClipper::has_valid_contour() +bool MeshClipper::has_valid_contour() const { - if (!m_result) - recalculate_triangles(); - - for (CutIsland& isl : m_result->cut_islands) - if (isl.model_expanded.get_bounding_box().defined) - return true; - - return false; + return m_result && std::any_of(m_result->cut_islands.begin(), m_result->cut_islands.end(), [](const CutIsland& isl) { return !isl.expoly.empty(); }); } diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp index 186b74febc..9db2ed1b1f 100644 --- a/src/slic3r/GUI/MeshUtils.hpp +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -115,8 +115,8 @@ public: void pass_mouse_click(const Vec3d& pt); - bool contains(Vec3d point); - bool has_valid_contour(); + bool is_projection_inside_cut(const Vec3d& point) const; + bool has_valid_contour() const; private: void recalculate_triangles();