diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 9a0cc777a..3385bd9aa 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1696,7 +1696,7 @@ bool ModelObject::has_connectors() const return false; } -indexed_triangle_set ModelObject::get_connector_mesh(CutConnectorAttributes connector_attributes) +indexed_triangle_set ModelObject::get_connector_mesh(CutConnectorAttributes connector_attributes, CutConnectorParas para) { indexed_triangle_set connector_mesh; @@ -1718,7 +1718,9 @@ indexed_triangle_set ModelObject::get_connector_mesh(CutConnectorAttributes conn break; } - if (connector_attributes.style == CutConnectorStyle::Prizm) + if (connector_attributes.type == CutConnectorType::Snap) + connector_mesh = its_make_snap(1.0, 1.0, para.snap_space_proportion, para.snap_bulge_proportion); + else if(connector_attributes.style == CutConnectorStyle::Prizm) connector_mesh = its_make_cylinder(1.0, 1.0, (2 * PI / sectorCount)); else if (connector_attributes.type == CutConnectorType::Plug) connector_mesh = its_make_cone(1.0, 1.0, (2 * PI / sectorCount)); @@ -1737,7 +1739,7 @@ void ModelObject::apply_cut_connectors(const std::string &name) size_t connector_id = cut_id.connectors_cnt(); for (const CutConnector &connector : cut_connectors) { - TriangleMesh mesh = TriangleMesh(get_connector_mesh(connector.attribs)); + TriangleMesh mesh = TriangleMesh(get_connector_mesh(connector.attribs, connector.paras)); // Mesh will be centered when loading. ModelVolume *new_volume = add_volume(std::move(mesh), ModelVolumeType::NEGATIVE_VOLUME); @@ -1865,7 +1867,18 @@ void ModelObject::process_connector_cut( // This transformation is already there if (volume->cut_info.connector_type != CutConnectorType::Dowel) { if (attributes.has(ModelObjectCutAttribute::KeepUpper)) { - ModelVolume *vol = upper->add_volume(*volume); + ModelVolume *vol = nullptr; + if (volume->cut_info.connector_type == CutConnectorType::Snap) { + TriangleMesh mesh = TriangleMesh(its_make_cylinder(1.0, 1.0, PI / 180.)); + + vol = upper->add_volume(std::move(mesh)); + vol->set_transformation(volume->get_transformation()); + vol->set_type(ModelVolumeType::NEGATIVE_VOLUME); + + vol->cut_info = volume->cut_info; + vol->name = volume->name; + } else + vol = upper->add_volume(*volume); vol->set_transformation(volume_matrix); vol->apply_tolerance(); } @@ -2832,6 +2845,16 @@ void ModelVolume::apply_tolerance() sf[Z] *= height_scale; set_scaling_factor(sf); + + // correct offset in respect to the new depth + Vec3d rot_norm = Geometry::rotation_transform(get_rotation()) * Vec3d::UnitZ(); + if (rot_norm.norm() != 0.0) rot_norm.normalize(); + + double z_offset = 0.5 * static_cast(cut_info.height_tolerance); + if (cut_info.connector_type == CutConnectorType::Plug || cut_info.connector_type == CutConnectorType::Snap) + z_offset -= 0.05; // add small Z offset to better preview + + set_offset(get_offset() + rot_norm * z_offset); } // BBS diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 2353804d9..b0473f2f3 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -237,6 +237,7 @@ private: enum class CutConnectorType : int { Plug, Dowel, + Snap, Undef }; @@ -255,6 +256,11 @@ enum class CutConnectorShape : int { Undef //,D-shape }; +struct CutConnectorParas +{ + float snap_space_proportion{0.3}; + float snap_bulge_proportion{0.15}; +}; struct CutConnectorAttributes { @@ -290,7 +296,7 @@ struct CutConnector float radius_tolerance; // [0.f : 1.f] float height_tolerance; // [0.f : 1.f] CutConnectorAttributes attribs; - + CutConnectorParas paras; CutConnector() : pos(Vec3d::Zero()), rotation_m(Transform3d::Identity()), radius(5.f), height(10.f), radius_tolerance(0.f), height_tolerance(0.1f) {} CutConnector(Vec3d p, Transform3d rot, float r, float h, float rt, float ht, CutConnectorAttributes attributes) @@ -468,7 +474,7 @@ public: bool is_cut() const { return cut_id.id().valid(); } bool has_connectors() const; - static indexed_triangle_set get_connector_mesh(CutConnectorAttributes connector_attributes); + static indexed_triangle_set get_connector_mesh(CutConnectorAttributes connector_attributes, CutConnectorParas para); void apply_cut_connectors(const std::string &name); // invalidate cut state for this object and its connectors/volumes void invalidate_cut(); diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index b5d0d1088..b13fc538d 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -1116,6 +1116,124 @@ indexed_triangle_set its_make_sphere(double radius, double fa) return mesh; } +indexed_triangle_set its_make_snap(double r, double h, float space_proportion, float bulge_proportion) +{ + const float radius = (float) r; + const float height = (float) h; + const size_t sectors_cnt = 10; //(float)fa; + const float halfPI = 0.5f * (float) PI; + + const float space_len = space_proportion * radius; + + const float b_len = radius; + const float m_len = (1 + bulge_proportion) * radius; + const float t_len = 0.5f * radius; + + const float b_height = 0.f; + const float m_height = 0.5f * height; + const float t_height = height; + + const float b_angle = acos(space_len / b_len); + const float t_angle = acos(space_len / t_len); + + const float b_angle_step = b_angle / (float) sectors_cnt; + const float t_angle_step = t_angle / (float) sectors_cnt; + + const Vec2f b_vec = Eigen::Vector2f(0, b_len); + const Vec2f t_vec = Eigen::Vector2f(0, t_len); + + auto add_side_vertices = [b_vec, t_vec, b_height, m_height, t_height](std::vector &vertices, float b_angle, float t_angle, const Vec2f &m_vec) { + Vec2f b_pt = Eigen::Rotation2Df(b_angle) * b_vec; + Vec2f m_pt = Eigen::Rotation2Df(b_angle) * m_vec; + Vec2f t_pt = Eigen::Rotation2Df(t_angle) * t_vec; + + vertices.emplace_back(Vec3f(b_pt(0), b_pt(1), b_height)); + vertices.emplace_back(Vec3f(m_pt(0), m_pt(1), m_height)); + vertices.emplace_back(Vec3f(t_pt(0), t_pt(1), t_height)); + }; + + auto add_side_facets = [](std::vector &facets, int vertices_cnt, int frst_id, int scnd_id) { + int id = vertices_cnt - 1; + + facets.emplace_back(frst_id, id - 2, id - 5); + + facets.emplace_back(id - 2, id - 1, id - 5); + facets.emplace_back(id - 1, id - 4, id - 5); + facets.emplace_back(id - 4, id - 1, id); + facets.emplace_back(id, id - 3, id - 4); + + facets.emplace_back(id, scnd_id, id - 3); + }; + + const float f = (b_len - m_len) / m_len; // Flattening + + auto get_m_len = [b_len, f](float angle) { + const float rad_sqr = b_len * b_len; + const float sin_sqr = sin(angle) * sin(angle); + const float f_sqr = (1 - f) * (1 - f); + return sqrtf(rad_sqr / (1 + (1 / f_sqr - 1) * sin_sqr)); + }; + + auto add_sub_mesh = [add_side_vertices, add_side_facets, get_m_len, b_height, t_height, b_angle, t_angle, b_angle_step, + t_angle_step](indexed_triangle_set &mesh, float center_x, float angle_rotation, int frst_vertex_id) { + auto &vertices = mesh.vertices; + auto &facets = mesh.indices; + + // 2 special vertices, top and bottom center, rest are relative to this + vertices.emplace_back(Vec3f(center_x, 0.f, b_height)); + vertices.emplace_back(Vec3f(center_x, 0.f, t_height)); + + float b_angle_start = angle_rotation - b_angle; + float t_angle_start = angle_rotation - t_angle; + const float b_angle_stop = angle_rotation + b_angle; + + const int frst_id = frst_vertex_id; + const int scnd_id = frst_id + 1; + + // add first side vertices and internal facets + { + const Vec2f m_vec = Eigen::Vector2f(0, get_m_len(b_angle_start)); + add_side_vertices(vertices, b_angle_start, t_angle_start, m_vec); + + int id = (int) vertices.size() - 1; + + facets.emplace_back(frst_id, id - 2, id - 1); + facets.emplace_back(frst_id, id - 1, id); + facets.emplace_back(frst_id, id, scnd_id); + } + + // add d side vertices and facets + while (!is_approx(b_angle_start, b_angle_stop)) { + b_angle_start += b_angle_step; + t_angle_start += t_angle_step; + + const Vec2f m_vec = Eigen::Vector2f(0, get_m_len(b_angle_start)); + add_side_vertices(vertices, b_angle_start, t_angle_start, m_vec); + + add_side_facets(facets, (int) vertices.size(), frst_id, scnd_id); + } + + // add last internal facets to close the mesh + { + int id = (int) vertices.size() - 1; + + facets.emplace_back(frst_id, scnd_id, id); + facets.emplace_back(frst_id, id, id - 1); + facets.emplace_back(frst_id, id - 1, id - 2); + } + }; + + indexed_triangle_set mesh; + + mesh.vertices.reserve(2 * (3 * (2 * sectors_cnt + 1) + 2)); + mesh.indices.reserve(2 * (6 * 2 * sectors_cnt + 6)); + + add_sub_mesh(mesh, -space_len, halfPI, 0); + add_sub_mesh(mesh, space_len, 3 * halfPI, (int) mesh.vertices.size()); + + return mesh; +} + indexed_triangle_set its_convex_hull(const std::vector &pts) { std::vector dst_vertices; diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index 9afaddd4e..9d878b0b7 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -340,6 +340,7 @@ indexed_triangle_set its_make_cone(double r, double h, double fa=(2*PI/360)); indexed_triangle_set its_make_frustum_dowel(double r, double h, int sectorCount); indexed_triangle_set its_make_pyramid(float base, float height); indexed_triangle_set its_make_sphere(double radius, double fa); +indexed_triangle_set its_make_snap(double r, double h, float space_proportion = 0.25f, float bulge_proportion = 0.125f); indexed_triangle_set its_convex_hull(const std::vector &pts); inline indexed_triangle_set its_convex_hull(const indexed_triangle_set &its) { return its_convex_hull(its.vertices); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp index ba9780501..c96d4db5e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp @@ -1065,11 +1065,12 @@ void GLGizmoAdvancedCut::clear_selection() void GLGizmoAdvancedCut::init_connector_shapes() { - for (const CutConnectorType &type : {CutConnectorType::Dowel, CutConnectorType::Plug}) + for (const CutConnectorType &type : {CutConnectorType::Snap, CutConnectorType::Dowel, CutConnectorType::Plug}) for (const CutConnectorStyle &style : {CutConnectorStyle::Frustum, CutConnectorStyle::Prizm}) for (const CutConnectorShape &shape : {CutConnectorShape::Circle, CutConnectorShape::Hexagon, CutConnectorShape::Square, CutConnectorShape::Triangle}) { - const CutConnectorAttributes attribs = {type, style, shape}; - const indexed_triangle_set its = ModelObject::get_connector_mesh(attribs); + CutConnectorAttributes attribs = {type, style, shape}; + CutConnectorParas paras = {m_snap_space_proportion, m_snap_bulge_proportion}; + const indexed_triangle_set its = ModelObject::get_connector_mesh(attribs, paras); m_shapes[attribs].init_from(its); } } @@ -1088,13 +1089,15 @@ void GLGizmoAdvancedCut::reset_connectors() clear_selection(); } -void GLGizmoAdvancedCut::update_connector_shape() +void GLGizmoAdvancedCut::update_connector_shape()//update mesh { - CutConnectorAttributes attribs = {m_connector_type, CutConnectorStyle(m_connector_style), CutConnectorShape(m_connector_shape_id)}; - - const indexed_triangle_set its = ModelObject::get_connector_mesh(attribs); - m_connector_mesh.clear(); - m_connector_mesh = TriangleMesh(its); + CutConnectorAttributes attribs = { m_connector_type, CutConnectorStyle(m_connector_style), CutConnectorShape(m_connector_shape_id)}; + CutConnectorParas paras = {m_snap_space_proportion, m_snap_bulge_proportion}; + if (m_connector_type == CutConnectorType::Snap) { + indexed_triangle_set its = ModelObject::get_connector_mesh(attribs, paras); + m_shapes[attribs].reset(); + m_shapes[attribs].init_from(its); + } } void GLGizmoAdvancedCut::apply_selected_connectors(std::function apply_fn) @@ -1572,6 +1575,7 @@ void GLGizmoAdvancedCut::render_connectors_input_window(float x, float y, float ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(0.00f, 0.00f, 0.00f, 1.00f)); bool type_changed = render_connect_type_radio_button(CutConnectorType::Plug); type_changed |= render_connect_type_radio_button(CutConnectorType::Dowel); + type_changed |= render_connect_type_radio_button(CutConnectorType::Snap); if (type_changed) apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.type = CutConnectorType(m_connector_type); }); ImGui::PopStyleColor(1); @@ -1579,7 +1583,7 @@ void GLGizmoAdvancedCut::render_connectors_input_window(float x, float y, float std::vector connector_styles = {_u8L("Prizm"), _u8L("Frustum")}; std::vector connector_shapes = { _u8L("Triangle"), _u8L("Square"), _u8L("Hexagon"), _u8L("Circle") }; - m_imgui->disabled_begin(m_connector_type == CutConnectorType::Dowel); + m_imgui->disabled_begin(m_connector_type == CutConnectorType::Dowel || m_connector_type == CutConnectorType::Snap); if (type_changed && m_connector_type == CutConnectorType::Dowel) { m_connector_style = size_t(CutConnectorStyle::Prizm); apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.style = CutConnectorStyle(m_connector_style); }); @@ -1588,14 +1592,19 @@ void GLGizmoAdvancedCut::render_connectors_input_window(float x, float y, float ImGuiWrapper::push_combo_style(m_parent.get_scale()); if (render_combo(_u8L("Style"), connector_styles, m_connector_style)) apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.style = CutConnectorStyle(m_connector_style); }); - m_imgui->disabled_end(); ImGuiWrapper::pop_combo_style(); + m_imgui->disabled_end(); + m_imgui->disabled_begin(m_connector_type == CutConnectorType::Snap); + if (type_changed && m_connector_type == CutConnectorType::Snap) { + m_connector_shape_id = int(CutConnectorShape::Circle); + apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.shape = CutConnectorShape(m_connector_shape_id); }); + } ImGuiWrapper::push_combo_style(m_parent.get_scale()); if (render_combo(_u8L("Shape"), connector_shapes, m_connector_shape_id)) apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.shape = CutConnectorShape(m_connector_shape_id); }); ImGuiWrapper::pop_combo_style(); - + m_imgui->disabled_end(); if (render_slider_double_input(_u8L("Depth ratio"), m_connector_depth_ratio, m_connector_depth_ratio_tolerance)) apply_selected_connectors([this, &connectors](size_t idx) { if (m_connector_depth_ratio > 0) @@ -1611,7 +1620,21 @@ void GLGizmoAdvancedCut::render_connectors_input_window(float x, float y, float if (m_connector_size_tolerance >= 0) connectors[idx].radius_tolerance = m_connector_size_tolerance; }); - + if (m_connector_type == CutConnectorType::Snap) { + const std::string format = "%.0f %%"; + bool is_changed = false; + if (render_slider_double_input_show_percentage(_u8L("Bulge"), m_snap_bulge_proportion, 5.f, 100.f * m_snap_space_proportion)) { + is_changed = true; + apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].paras.snap_bulge_proportion = m_snap_bulge_proportion; }); + } + if (render_slider_double_input_show_percentage(_u8L("Gap"), m_snap_space_proportion, 10.f, 50.f)) { + is_changed = true; + apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].paras.snap_space_proportion = m_snap_space_proportion; }); + } + if (is_changed) { + update_connector_shape(); + } + } ImGui::Separator(); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f)); @@ -1678,7 +1701,7 @@ bool GLGizmoAdvancedCut::render_reset_button(const std::string &label_id, const bool GLGizmoAdvancedCut::render_connect_type_radio_button(CutConnectorType type) { - ImGui::SameLine(type == CutConnectorType::Plug ? m_label_width : 2 * m_label_width); + ImGui::SameLine(type == CutConnectorType::Plug ? m_label_width : (type == CutConnectorType::Dowel ? 2 * m_label_width : 3 * m_label_width)); ImGui::PushItemWidth(m_control_width); wxString radio_name; @@ -1689,13 +1712,15 @@ bool GLGizmoAdvancedCut::render_connect_type_radio_button(CutConnectorType type) case CutConnectorType::Dowel: radio_name = _L("Dowel"); break; + case CutConnectorType::Snap: + radio_name = _L("Snap"); + break; default: break; } if (m_imgui->radio_button(radio_name, m_connector_type == type)) { m_connector_type = type; - update_connector_shape(); return true; } return false; @@ -1728,8 +1753,6 @@ bool GLGizmoAdvancedCut::render_combo(const std::string &label, const std::vecto bool is_changed = selection_idx != selection_out; selection_idx = selection_out; - if (is_changed) update_connector_shape(); - return is_changed; } @@ -1787,17 +1810,51 @@ bool GLGizmoAdvancedCut::render_slider_double_input(const std::string &label, fl float min_tolerance = tolerance_in < 0.f ? UndefMinVal : 0.f; m_imgui->bbl_slider_float_style(("##tolerance_" + label).c_str(), &tolerance, min_tolerance, 2.f, format.c_str(), 1.f, true, _L("Tolerance")); - + left_width += (slider_with + item_in_gap); ImGui::SameLine(left_width); ImGui::PushItemWidth(second_input_width); ImGui::BBLDragFloat(("##tolerance_input_" + label).c_str(), &tolerance, 0.05f, min_tolerance, 2.f, format.c_str()); - + tolerance_in = tolerance * float(m_imperial_units ? units_in_to_mm : 1.0); return !is_approx(old_val, value) || !is_approx(old_tolerance, tolerance); } +bool GLGizmoAdvancedCut::render_slider_double_input_show_percentage(const std::string &label, float &value_in, float value_min, float value_max) +{ + // -------- [ ] + // slider_with + item_in_gap + first_input_width + item_out_gap + double slider_with = 0.24 * m_editing_window_width; // m_control_width * 0.35; + double item_in_gap = 0.01 * m_editing_window_width; + double item_out_gap = 0.01 * m_editing_window_width; + double first_input_width = 0.29 * m_editing_window_width; + + ImGui::AlignTextToFramePadding(); + m_imgui->text(label); + ImGui::SameLine(m_label_width); + ImGui::PushItemWidth(slider_with); + + double left_width = m_label_width + slider_with + item_in_gap; + + float old_val = value_in; + float value = value_in *100; + + const std::string format = "%.0f %%"; + if (m_imgui->bbl_slider_float_style(("##" + label).c_str(), &value, value_min, value_max, format.c_str())) { + value_in = value * 0.01f; + } + + ImGui::SameLine(left_width); + ImGui::PushItemWidth(first_input_width); + if (ImGui::BBLDragFloat(("##input_" + label).c_str(), &value, 0.05f, value_min, value_max, format.c_str())){ + value_in = value * 0.01f; + } + return !is_approx(old_val, value_in); +} + + + bool GLGizmoAdvancedCut::cut_line_processing() const { return m_cut_line_begin != Vec3d::Zero(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.hpp index 5cb9e765c..dc26f87be 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.hpp @@ -81,6 +81,9 @@ private: float m_connector_size{2.5f}; float m_connector_size_tolerance{0.f}; + // Input params for cut with snaps + float m_snap_space_proportion{0.3f}; + float m_snap_bulge_proportion{0.15f}; TriangleMesh m_connector_mesh; bool m_has_invalid_connector{false}; @@ -228,7 +231,7 @@ private: bool render_combo(const std::string &label, const std::vector &lines, size_t &selection_idx); bool render_slider_double_input(const std::string &label, float &value_in, float &tolerance_in); - + bool render_slider_double_input_show_percentage(const std::string &label, float &value_in, float value_min, float value_max); bool cut_line_processing() const; void discard_cut_line_processing(); bool process_cut_line(SLAGizmoEventType action, const Vec2d &mouse_position);