From 5bd5ac73a0c6431ecc2f0f01d8e7ff55330a9057 Mon Sep 17 00:00:00 2001 From: Hanno Witzleb <48943886+Xipit@users.noreply.github.com> Date: Mon, 18 May 2026 06:57:48 +0200 Subject: [PATCH 1/3] Feature: Multiple dovetail cuts (#12318) * adds UI and preview plane for multiple dovetail cuts * adds multiple dovetail cuts to perform_with_groove() * adds ui text info for spacing, adjusts max_val for gap to respect plate dimensions * adds spacing before multpile UI * adjusts wording, adjust gap max by count --------- Co-authored-by: Hanno Witzleb Co-authored-by: SoftFever --- src/libslic3r/CutUtils.cpp | 140 ++++-- src/libslic3r/CutUtils.hpp | 10 +- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 627 ++++++++++++++++++--------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 14 +- 4 files changed, 546 insertions(+), 245 deletions(-) diff --git a/src/libslic3r/CutUtils.cpp b/src/libslic3r/CutUtils.cpp index 58993130358..f8b14837737 100644 --- a/src/libslic3r/CutUtils.cpp +++ b/src/libslic3r/CutUtils.cpp @@ -554,7 +554,12 @@ const ModelObjectPtrs& Cut::perform_by_contour(const ModelObject* src_object, st } -const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove, const Transform3d& rotation_m, bool keep_as_parts/* = false*/) +const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove, + const Transform3d& rotation_m, + const int groove_count, + const float groove_gap, + const float m_radius, + bool keep_as_parts /* = false*/) { ModelObject* cut_mo = m_model.objects.front(); @@ -615,7 +620,7 @@ const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove, const Tran reset_instance_transformation(object, m_instance); }; - // cut by upper plane + // cut by upper plane (+Z) { const Transform3d cut_matrix_upper = translation_transform(rotation_m * (groove_half_depth * Vec3d::UnitZ())) * m_cut_matrix; @@ -623,7 +628,7 @@ const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove, const Tran add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); } - // cut by lower plane + // cut by lower plane (-Z) { const Transform3d cut_matrix_lower = translation_transform(rotation_m * (-groove_half_depth * Vec3d::UnitZ())) * m_cut_matrix; @@ -631,45 +636,92 @@ const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove, const Tran add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); } - // cut middle part with 2 angles and add parts to related upper/lower objects + // Compute same slot outer width used in preview plane + const float groove_width = calculate_groove_width(groove, m_radius); - const double h_side_shift = 0.5 * double(groove.width + groove.depth / tan(groove.flaps_angle)); + ModelObject* groove_object{nullptr}; - // cut by angle1 plane - { - const Transform3d cut_matrix_angle1 = translation_transform(rotation_m * (-h_side_shift * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, -groove.flaps_angle, -groove.angle)); + // multiple cuts + for (int i = 0; i < groove_count; i++) { + bool is_first_groove = i == 0; + bool is_last_groove = i == groove_count - 1; - cut(tmp_object, cut_matrix_angle1, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); - add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); + // Calculate the x-axis offset for this dovetail + float groove_offset_factor_start = -.5 * ((groove_count - 1)); + float groove_offset_factor = groove_offset_factor_start + i; + + float offset_x = groove_offset_factor * (groove_gap + groove_width); + + + tmp_object->clone_for_cut(&groove_object); + for (ModelVolume* volume : tmp_object->volumes) { + ModelVolume* new_vol = groove_object->add_volume(*volume); + new_vol->reset_from_upper(); + } + + // isolate area of current groove + if (!is_first_groove) { + float left_cut_position = (-groove_gap / 2.f) - (groove_width / 2.f) + offset_x; + + const Transform3d cut_matrix_left = translation_transform(rotation_m * (left_cut_position * Vec3d::UnitX())) * + m_cut_matrix * rotation_transform(Vec3d(0, M_PI / 2.0, 0)); + + cut(groove_object, cut_matrix_left, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); + } + if (!is_last_groove) { + float right_cut_position = (groove_gap / 2.f) + (groove_width / 2.f) + offset_x; + + const Transform3d cut_matrix_right = translation_transform(rotation_m * (right_cut_position * Vec3d::UnitX())) * + m_cut_matrix * rotation_transform(Vec3d(0, M_PI / 2.0, 0)); + cut(groove_object, cut_matrix_right, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); + } + + const Transform3d groove_translation = translation_transform(rotation_m * (offset_x * Vec3d::UnitX())); + // cut middle part with 2 angles and add parts to related upper/lower objects + const double h_side_shift = 0.5 * double(groove.width + groove.depth / tan(groove.flaps_angle)); + + // cut by angle1 plane + { + const Transform3d cut_matrix_angle1 = groove_translation * translation_transform(rotation_m * (-h_side_shift * Vec3d::UnitX())) * + m_cut_matrix * rotation_transform(Vec3d(0, -groove.flaps_angle, -groove.angle)); + + cut(groove_object, cut_matrix_angle1, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); + add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); + } + + // cut by angle2 plane + { + const Transform3d cut_matrix_angle2 = groove_translation * translation_transform(rotation_m * (h_side_shift * Vec3d::UnitX())) * + m_cut_matrix * rotation_transform(Vec3d(0, groove.flaps_angle, groove.angle)); + + cut(groove_object, cut_matrix_angle2, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); + add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); + } + + // apply tolerance to the middle part + { + const double h_groove_shift_tolerance = groove_half_depth - (double)groove.depth_tolerance; + + const Transform3d cut_matrix_lower_tolerance = groove_translation * translation_transform(rotation_m * (-h_groove_shift_tolerance * Vec3d::UnitZ())) * + m_cut_matrix; + cut(groove_object, cut_matrix_lower_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); + + const double h_side_shift_tolerance = h_side_shift - 0.5 * double(groove.width_tolerance); + + const Transform3d cut_matrix_angle1_tolerance = groove_translation * translation_transform(rotation_m * (-h_side_shift_tolerance * Vec3d::UnitX())) * + m_cut_matrix * rotation_transform(Vec3d(0, -groove.flaps_angle, -groove.angle)); + cut(groove_object, cut_matrix_angle1_tolerance, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); + + const Transform3d cut_matrix_angle2_tolerance = groove_translation * translation_transform(rotation_m * (h_side_shift_tolerance * Vec3d::UnitX())) * + m_cut_matrix * rotation_transform(Vec3d(0, groove.flaps_angle, groove.angle)); + cut(groove_object, cut_matrix_angle2_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); + } + + add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); + + groove_object->clear_volumes(); } - // cut by angle2 plane - { - const Transform3d cut_matrix_angle2 = translation_transform(rotation_m * (h_side_shift * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, groove.flaps_angle, groove.angle)); - - cut(tmp_object, cut_matrix_angle2, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); - add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); - } - - // apply tolerance to the middle part - { - const double h_groove_shift_tolerance = groove_half_depth - (double)groove.depth_tolerance; - - const Transform3d cut_matrix_lower_tolerance = translation_transform(rotation_m * (-h_groove_shift_tolerance * Vec3d::UnitZ())) * m_cut_matrix; - cut(tmp_object, cut_matrix_lower_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); - - const double h_side_shift_tolerance = h_side_shift - 0.5 * double(groove.width_tolerance); - - const Transform3d cut_matrix_angle1_tolerance = translation_transform(rotation_m * (-h_side_shift_tolerance * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, -groove.flaps_angle, -groove.angle)); - cut(tmp_object, cut_matrix_angle1_tolerance, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); - - const Transform3d cut_matrix_angle2_tolerance = translation_transform(rotation_m * (h_side_shift_tolerance * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, groove.flaps_angle, groove.angle)); - cut(tmp_object, cut_matrix_angle2_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); - } - - // this part can be added to the upper object now - add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); - ModelObjectPtrs cut_object_ptrs; if (keep_as_parts) { @@ -713,5 +765,19 @@ const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove, const Tran return m_model.objects; } +float Cut::calculate_groove_width (const Cut::Groove& groove, const float m_radius) +{ + // Compute same slot outer width used in preview plane + const double flap_width = is_approx(groove.flaps_angle, 0.f) ? groove.depth : groove.depth / sin(groove.flaps_angle); + const double total_flap_width = 2.0 * flap_width * cos(groove.flaps_angle); + const double slot_neck_half_width = 0.5f * (groove.width); + const double slot_mouth_half_width = 0.5 * (groove.width + total_flap_width); + const double plane_half_height = 0.5f* (1.5f * (1.5f *m_radius)); + const double flap_taper_offset = plane_half_height * tan(groove.angle); + const double slot_outer_x_max = std::max(slot_mouth_half_width + flap_taper_offset, slot_neck_half_width + flap_taper_offset); + + return float(2.0 * slot_outer_x_max); +} + } // namespace Slic3r diff --git a/src/libslic3r/CutUtils.hpp b/src/libslic3r/CutUtils.hpp index 99cdb9cd65b..b7f31d7b9a6 100644 --- a/src/libslic3r/CutUtils.hpp +++ b/src/libslic3r/CutUtils.hpp @@ -57,9 +57,15 @@ public: const ModelObjectPtrs& perform_with_plane(); const ModelObjectPtrs& perform_by_contour(const ModelObject* src_object, std::vector parts, int dowels_count); - const ModelObjectPtrs& perform_with_groove(const Groove& groove, const Transform3d& rotation_m, bool keep_as_parts = false); + const ModelObjectPtrs& perform_with_groove(const Groove& groove, + const Transform3d& rotation_m, + const int groove_count, + const float groove_gap, + const float m_radius, + bool keep_as_parts = false); -}; // namespace Cut + static float calculate_groove_width(const Cut::Groove& groove, const float m_radius); + }; // namespace Cut } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index f042656ab77..5fed9303211 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -227,6 +227,10 @@ GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, {"Groove Angle" , _u8L("Groove Angle")}, {"Cut position" , _u8L("Cut position")}, // ORCA {"Build Volume" , _u8L("Build Volume")}, // ORCA + {"Multiple" , _u8L("Multiple")}, // ORCA + {"Count" , _u8L("Count")}, // ORCA + {"Gap" , _u8L("Gap")}, // ORCA + {"Spacing" , _u8L("Spacing")} // ORCA }; // update_connector_shape(); @@ -543,7 +547,7 @@ bool GLGizmoCut3D::render_double_input(const std::string& label, double& value_i return !is_approx(old_val, value); } -bool GLGizmoCut3D::render_slider_double_input(const std::string& label, float& value_in, float& tolerance_in, float min_val/* = -0.1f*/, float max_tolerance/* = -0.1f*/) +bool GLGizmoCut3D::render_slider_two_input(const std::string& label, float& value_in, float& tolerance_in, float min_val/* = -0.1f*/, float max_tolerance/* = -0.1f*/) { // -------- [ ] -------- [ ] // slider_with + item_in_gap + first_input_width + item_out_gap + slider_with + item_in_gap + second_input_width @@ -613,6 +617,55 @@ bool GLGizmoCut3D::render_slider_double_input(const std::string& label, float& v return !is_approx(old_val, value) || !is_approx(old_tolerance, tolerance); } +bool GLGizmoCut3D::render_slider_input(const std::string& label, float& value_in, float min_val /* = -0.1f*/, float max_val /* = 100.f*/) +{ + // -------- [ ] + // slider_with + input_width + slider_with + item_in_gap + double slider_with = 0.42 * m_editing_window_width; // m_control_width * 0.35; + double item_in_gap = 0.01 * m_editing_window_width; + double input_width = 0.29 * m_editing_window_width; + + constexpr float UndefMinVal = -0.1f; + const float f_mm_to_in = static_cast(GizmoObjectManipulation::mm_to_in); + + 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; + + bool m_imperial_units = false; + + float value = value_in; + float max_value = max_val; + if (m_imperial_units) { + value *= f_mm_to_in; + max_value *= f_mm_to_in; + } + float old_val = value; + + const BoundingBoxf3 bbox = m_bounding_box; + //const float mean_size = float((bbox.size().x() + bbox.size().y() + bbox.size().z())) * (m_imperial_units ? f_mm_to_in : 1.f); + const float min_v = min_val > 0.f ? /*std::min(max_val, mean_size)*/ min_val : 1.f; + + float min_size = value_in < 0.f ? UndefMinVal : min_v; + if (m_imperial_units) { + min_size *= f_mm_to_in; + } + std::string format = value_in < 0.f ? " " : m_imperial_units ? "%.4f " + _u8L("in") : "%.2f " + _u8L("mm"); + + m_imgui->bbl_slider_float_style(("##" + label).c_str(), &value, min_size, max_value, format.c_str()); + + ImGui::SameLine(left_width); + ImGui::PushItemWidth(input_width); + ImGui::BBLDragFloat(("##input_" + label).c_str(), &value, 0.05f, min_size, max_value, format.c_str()); + + value_in = value * float(m_imperial_units ? GizmoObjectManipulation::in_to_mm : 1.0); + + return !is_approx(old_val, value); +} + void GLGizmoCut3D::render_move_center_input(int axis) { m_imgui->text(m_axis_names[axis]+":"); @@ -691,223 +744,299 @@ static double get_grabber_mean_size(const BoundingBoxf3& bb) #endif } +std::vector GLGizmoCut3D::offset_indices(const std::vector& base_indices, size_t vo) +{ + std::vector offset; + int vo_int = static_cast(vo); + for (const auto& tri : base_indices) { + offset.push_back({tri[0] + vo_int, tri[1] + vo_int, tri[2] + vo_int}); + } + return offset; +} + indexed_triangle_set GLGizmoCut3D::its_make_groove_plane() { - // values for calculation + // This function generates a dovetail slot in a wall, viewed from above (top-down). + // The slot has a wide "mouth" (closest to the wall surface) and a narrow "neck" (deepest into the wall). + // The flaps are the tapered sides connecting the mouth to the neck. - const float side_width = is_approx(m_groove.flaps_angle, 0.f) ? m_groove.depth : (m_groove.depth / sin(m_groove.flaps_angle)); - const float flaps_width = 2.f * side_width * cos(m_groove.flaps_angle); + const float flap_taper_width = is_approx(m_groove.flaps_angle, 0.f) ? m_groove.depth : (m_groove.depth / sin(m_groove.flaps_angle)); + const float total_flap_taper_width = 2.f * flap_taper_width * cos(m_groove.flaps_angle); - const float groove_half_width_upper = 0.5f * (m_groove.width); - const float groove_half_width_lower = 0.5f * (m_groove.width + flaps_width); + const float slot_neck_half_width = 0.5f * (m_groove.width); + const float slot_mouth_half_width = 0.5f * (m_groove.width + total_flap_taper_width); const float cut_plane_radius = 1.5f * float(m_radius); const float cut_plane_length = 1.5f * cut_plane_radius; - const float groove_half_depth = 0.5f * m_groove.depth; + const float plane_half_width = 0.5f * cut_plane_radius; // x + const float plane_half_height = 0.5f * cut_plane_length; // y - const float x = 0.5f * cut_plane_radius; - const float y = 0.5f * cut_plane_length; - float z_upper = groove_half_depth; - float z_lower = -groove_half_depth; + const float slot_half_depth = 0.5f * m_groove.depth; // z + float slot_front_z = slot_half_depth; + float slot_back_z = -slot_half_depth; - const float proj = y * tan(m_groove.angle); + const float flap_taper_offset = plane_half_height * tan(m_groove.angle); - float ext_upper_x = groove_half_width_upper + proj; // upper_x extension - float ext_lower_x = groove_half_width_lower + proj; // lower_x extension + float slot_mouth_outer_x = slot_neck_half_width + flap_taper_offset; // upper_x extension + float slot_neck_outer_x = slot_mouth_half_width + flap_taper_offset; // lower_x extension + float slot_outer_x_max = Max(slot_neck_outer_x, slot_mouth_outer_x); // max x extension - float nar_upper_x = groove_half_width_upper - proj; // upper_x narrowing - float nar_lower_x = groove_half_width_lower - proj; // lower_x narrowing + float slot_neck_inner_x = slot_neck_half_width - flap_taper_offset; // upper_x narrowing + float slot_mouth_inner_x = slot_mouth_half_width - flap_taper_offset; // lower_x narrowing - const float cut_plane_thiknes = 0.02f;// 0.02f * (float)get_grabber_mean_size(m_bounding_box); // cut_plane_thiknes + const float wall_thickness = 0.02f; // 0.02f * (float)get_grabber_mean_size(m_bounding_box); // cut_plane_thiknes - // Vertices of the groove used to detection if groove is valid - // They are written as: - // {left_ext_lower, left_nar_lower, left_ext_upper, left_nar_upper, - // right_ext_lower, right_nar_lower, right_ext_upper, right_nar_upper } - { - m_groove_vertices.clear(); - m_groove_vertices.reserve(8); - - m_groove_vertices.emplace_back(Vec3f(-ext_lower_x, -y, z_lower).cast()); - m_groove_vertices.emplace_back(Vec3f(-nar_lower_x, y, z_lower).cast()); - m_groove_vertices.emplace_back(Vec3f(-ext_upper_x, -y, z_upper).cast()); - m_groove_vertices.emplace_back(Vec3f(-nar_upper_x, y, z_upper).cast()); - m_groove_vertices.emplace_back(Vec3f( ext_lower_x, -y, z_lower).cast()); - m_groove_vertices.emplace_back(Vec3f( nar_lower_x, y, z_lower).cast()); - m_groove_vertices.emplace_back(Vec3f( ext_upper_x, -y, z_upper).cast()); - m_groove_vertices.emplace_back(Vec3f( nar_upper_x, y, z_upper).cast()); - } - - // Different cases of groove plane: - - // groove is open - - if (groove_half_width_upper > proj && groove_half_width_lower > proj) { - indexed_triangle_set mesh; - - auto get_vertices = [x, y](float z_upper, float z_lower, float nar_upper_x, float nar_lower_x, float ext_upper_x, float ext_lower_x) { - return std::vector({ - // upper left part vertices - {-x, -y, z_upper}, {-x, y, z_upper}, {-nar_upper_x, y, z_upper}, {-ext_upper_x, -y, z_upper}, - // lower part vertices - {-ext_lower_x, -y, z_lower}, {-nar_lower_x, y, z_lower}, {nar_lower_x, y, z_lower}, {ext_lower_x, -y, z_lower}, - // upper right part vertices - {ext_upper_x, -y, z_upper}, {nar_upper_x, y, z_upper}, {x, y, z_upper}, {x, -y, z_upper} - }); - }; - - mesh.vertices = get_vertices(z_upper, z_lower, nar_upper_x, nar_lower_x, ext_upper_x, ext_lower_x); - mesh.vertices.reserve(2 * mesh.vertices.size()); - - z_upper -= cut_plane_thiknes; - z_lower -= cut_plane_thiknes; - - const float under_x_shift = cut_plane_thiknes / tan(0.5f * m_groove.flaps_angle); - - nar_upper_x += under_x_shift; - nar_lower_x += under_x_shift; - ext_upper_x += under_x_shift; - ext_lower_x += under_x_shift; - - std::vector vertices = get_vertices(z_upper, z_lower, nar_upper_x, nar_lower_x, ext_upper_x, ext_lower_x); - mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end()); - - mesh.indices = { - // above view - {5,4,7}, {5,7,6}, // lower part - {3,4,5}, {3,5,2}, // left side - {9,6,8}, {8,6,7}, // right side - {1,0,2}, {2,0,3}, // upper left part - {9,8,10}, {10,8,11}, // upper right part - // under view - {20,21,22}, {20,22,23}, // upper right part - {12,13,14}, {12,14,15}, // upper left part - {18,21,20}, {18,20,19}, // right side - {16,15,14}, {16,14,17}, // left side - {16,17,18}, {16,18,19}, // lower part - // left edge - {1,13,0}, {0,13,12}, - // front edge - {0,12,3}, {3,12,15}, {3,15,4}, {4,15,16}, {4,16,7}, {7,16,19}, {7,19,20}, {7,20,8}, {8,20,11}, {11,20,23}, - // right edge - {11,23,10}, {10,23,22}, - // back edge - {1,13,2}, {2,13,14}, {2,14,17}, {2,17,5}, {5,17,6}, {6,17,18}, {6,18,9}, {9,18,21}, {9,21,10}, {10,21,22} - }; - return mesh; - } - - float cross_pt_upper_y = groove_half_width_upper / tan(m_groove.angle); - - // groove is closed - - if (groove_half_width_upper < proj && groove_half_width_lower < proj) { - float cross_pt_lower_y = groove_half_width_lower / tan(m_groove.angle); - - indexed_triangle_set mesh; - - auto get_vertices = [x, y](float z_upper, float z_lower, float cross_pt_upper_y, float cross_pt_lower_y, float ext_upper_x, float ext_lower_x) { - return std::vector({ - // upper part vertices - {-x, -y, z_upper}, {-x, y, z_upper}, {x, y, z_upper}, {x, -y, z_upper}, - {ext_upper_x, -y, z_upper}, {0.f, cross_pt_upper_y, z_upper}, {-ext_upper_x, -y, z_upper}, - // lower part vertices - {-ext_lower_x, -y, z_lower}, {0.f, cross_pt_lower_y, z_lower}, {ext_lower_x, -y, z_lower} - }); - }; - - mesh.vertices = get_vertices(z_upper, z_lower, cross_pt_upper_y, cross_pt_lower_y, ext_upper_x, ext_lower_x); - mesh.vertices.reserve(2 * mesh.vertices.size()); - - z_upper -= cut_plane_thiknes; - z_lower -= cut_plane_thiknes; - - const float under_x_shift = cut_plane_thiknes / tan(0.5f * m_groove.flaps_angle); - - cross_pt_upper_y += cut_plane_thiknes; - cross_pt_lower_y += cut_plane_thiknes; - ext_upper_x += under_x_shift; - ext_lower_x += under_x_shift; - - std::vector vertices = get_vertices(z_upper, z_lower, cross_pt_upper_y, cross_pt_lower_y, ext_upper_x, ext_lower_x); - mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end()); - - mesh.indices = { - // above view - {8,7,9}, // lower part - {5,8,6}, {6,8,7}, // left side - {4,9,8}, {4,8,5}, // right side - {1,0,6}, {1,6,5},{1,5,2}, {2,5,4}, {2,4,3}, // upper part - // under view - {10,11,16}, {16,11,15}, {15,11,12}, {15,12,14}, {14,12,13}, // upper part - {18,15,14}, {14,18,19}, // right side - {17,16,15}, {17,15,18}, // left side - {17,18,19}, // lower part - // left edge - {1,11,0}, {0,11,10}, - // front edge - {0,10,6}, {6,10,16}, {6,17,16}, {6,7,17}, {7,17,19}, {7,19,9}, {4,14,19}, {4,19,9}, {4,14,13}, {4,13,3}, - // right edge - {3,13,12}, {3,12,2}, - // back edge - {2,12,11}, {2,11,1} - }; - - return mesh; - } - - // groove is closed from the roof + int groove_count = this->m_groove_count; + float groove_gap = this->m_groove_gap; indexed_triangle_set mesh; - mesh.vertices = { - // upper part vertices - {-x, -y, z_upper}, {-x, y, z_upper}, {x, y, z_upper}, {x, -y, z_upper}, - {ext_upper_x, -y, z_upper}, {0.f, cross_pt_upper_y, z_upper}, {-ext_upper_x, -y, z_upper}, - // lower part vertices - {-ext_lower_x, -y, z_lower}, {-nar_lower_x, y, z_lower}, {nar_lower_x, y, z_lower}, {ext_lower_x, -y, z_lower} - }; - mesh.vertices.reserve(2 * mesh.vertices.size() + 1); + // handle multiple dovetails/grooves + for (int i = 0; i < groove_count; ++i) { + bool is_first_groove = i == 0; // when a groove is not the last groove, then limit the extent of the right plane so that it doesnt overlap the next groove + bool is_last_groove = i == groove_count - 1; // do the same in reverse if a groove is not the first groove + size_t vertex_index_offset = mesh.vertices.size(); - z_upper -= cut_plane_thiknes; - z_lower -= cut_plane_thiknes; + // Calculate the x-axis offset for this dovetail + float groove_offset_factor_start = -.5 * ((groove_count - 1)); + float groove_offset_factor = groove_offset_factor_start + i; + // Recalculate x with offset (only X-axis offset) + float offset_x = groove_offset_factor * (groove_gap + (2 * slot_outer_x_max)); - const float under_x_shift = cut_plane_thiknes / tan(0.5f * m_groove.flaps_angle); + // Vertices of the groove used to detection if groove is valid (not used in mesh) + { + m_groove_vertices.clear(); + m_groove_vertices.reserve(8); - nar_lower_x += under_x_shift; - ext_upper_x += under_x_shift; - ext_lower_x += under_x_shift; + m_groove_vertices.emplace_back(Vec3f(-slot_neck_outer_x, -plane_half_height, slot_front_z).cast()); + m_groove_vertices.emplace_back(Vec3f(-slot_mouth_inner_x, plane_half_height, slot_front_z).cast()); + m_groove_vertices.emplace_back(Vec3f(-slot_mouth_outer_x, -plane_half_height, slot_back_z).cast()); + m_groove_vertices.emplace_back(Vec3f(-slot_neck_outer_x, plane_half_height, slot_back_z).cast()); + m_groove_vertices.emplace_back(Vec3f(slot_neck_outer_x, -plane_half_height, slot_front_z).cast()); + m_groove_vertices.emplace_back(Vec3f(slot_mouth_inner_x, plane_half_height, slot_front_z).cast()); + m_groove_vertices.emplace_back(Vec3f(slot_mouth_outer_x, -plane_half_height, slot_back_z).cast()); + m_groove_vertices.emplace_back(Vec3f(slot_neck_outer_x, plane_half_height, slot_back_z).cast()); + } - std::vector vertices = { - // upper part vertices - {-x, -y, z_upper}, {-x, y, z_upper}, {x, y, z_upper}, {x, -y, z_upper}, - {ext_upper_x, -y, z_upper}, {under_x_shift, cross_pt_upper_y, z_upper}, {-under_x_shift, cross_pt_upper_y, z_upper}, {-ext_upper_x, -y, z_upper}, - // lower part vertices - {-ext_lower_x, -y, z_lower}, {-nar_lower_x, y, z_lower}, {nar_lower_x, y, z_lower}, {ext_lower_x, -y, z_lower} - }; - mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end()); + // ___ + // Case: Groove is open (Top&bottom: __\ /__ ) + if (slot_neck_half_width > flap_taper_offset && slot_mouth_half_width > flap_taper_offset) { + auto get_vertices = [plane_half_width, plane_half_height] + (float slot_front_z, float slot_back_z, float slot_neck_inner_x, float slot_mouth_inner_x, float slot_mouth_outer_x, float slot_neck_outer_x, + float slot_outer_x_max, bool is_first_groove, bool is_last_groove, float groove_gap, float offset_x) + { - mesh.indices = { - // above view - {8,7,10}, {8,10,9}, // lower part - {5,8,7}, {5,7,6}, // left side - {4,10,9}, {4,9,5}, // right side - {1,0,6}, {1,6,5},{1,5,2}, {2,5,4}, {2,4,3}, // upper part - // under view - {11,12,18}, {18,12,17}, {17,12,16}, {16,12,13}, {16,13,15}, {15,13,14}, // upper part - {21,16,15}, {21,15,22}, // right side - {19,18,17}, {19,17,20}, // left side - {19,20,21}, {19,21,22}, // lower part - // left edge - {1,12,11}, {1,11,0}, - // front edge - {0,11,18}, {0,18,6}, {7,19,18}, {7,18,6}, {7,19,22}, {7,22,10}, {10,22,15}, {10,15,4}, {4,15,14}, {4,14,3}, - // right edge - {3,14,13}, {3,14,2}, - // back edge - {2,13,12}, {2,12,1}, {5,16,21}, {5,21,9}, {9,21,20}, {9,20,8}, {5,17,20}, {5,20,8} - }; + return std::vector({ + // front left part vertices + {is_first_groove ? -plane_half_width + offset_x : -(groove_gap / 2.f) - slot_outer_x_max + offset_x, -plane_half_height, slot_front_z}, // *__/ \__ + {is_first_groove ? -plane_half_width + offset_x : -(groove_gap / 2.f) - slot_outer_x_max + offset_x, plane_half_height, slot_front_z}, // *__/ \__ + {-slot_neck_inner_x + offset_x, plane_half_height, slot_front_z}, // __*/ \__ + {-slot_mouth_outer_x + offset_x, -plane_half_height, slot_front_z}, // __/* \__ + // back part vertices + {-slot_neck_outer_x + offset_x, -plane_half_height, slot_back_z}, // __*/ \__ + {-slot_mouth_inner_x + offset_x, plane_half_height, slot_back_z}, // __/* \__ + {slot_mouth_inner_x + offset_x, plane_half_height, slot_back_z}, // __/ *\__ + {slot_neck_outer_x + offset_x, -plane_half_height, slot_back_z}, // __/ \*__ + // front right part vertices + {slot_mouth_outer_x + offset_x, -plane_half_height, slot_front_z}, // __/ \*__ + {slot_neck_inner_x + offset_x, plane_half_height, slot_front_z}, // __/ *\__ + {is_last_groove ? plane_half_width + offset_x : (groove_gap / 2.f) + slot_outer_x_max + offset_x, plane_half_height, slot_front_z}, // __/ \__* + {is_last_groove ? plane_half_width + offset_x : (groove_gap / 2.f) + slot_outer_x_max + offset_x, -plane_half_height, slot_front_z}}); // __/ \__* + }; + + std::vector vertices = get_vertices(slot_front_z, slot_back_z, slot_neck_inner_x, slot_mouth_inner_x, slot_mouth_outer_x, slot_neck_outer_x, slot_outer_x_max, + is_first_groove, is_last_groove, groove_gap, offset_x); + mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end()); + + // Back face + slot_front_z -= wall_thickness; + slot_back_z -= wall_thickness; + + const float slot_back_face_x_offset = wall_thickness / tan(0.5f * m_groove.flaps_angle); + slot_neck_inner_x += slot_back_face_x_offset; + slot_mouth_inner_x += slot_back_face_x_offset; + slot_mouth_outer_x += slot_back_face_x_offset; + slot_neck_outer_x += slot_back_face_x_offset; + + vertices = get_vertices(slot_front_z, slot_back_z, slot_neck_inner_x, slot_mouth_inner_x, slot_mouth_outer_x, slot_neck_outer_x, + slot_outer_x_max, is_first_groove, + is_last_groove, groove_gap, offset_x); + mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end()); + + std::vector base_indices; + + base_indices = { + // above view + {5,4,7}, {5,7,6}, // lower part + {3,4,5}, {3,5,2}, // left side + {9,6,8}, {8,6,7}, // right side + {1,0,2}, {2,0,3}, // upper left part + {9,8,10}, {10,8,11}, // upper right part + // under view + {20,21,22}, {20,22,23}, // upper right part + {12,13,14}, {12,14,15}, // upper left part + {18,21,20}, {18,20,19}, // right side + {16,15,14}, {16,14,17}, // left side + {16,17,18}, {16,18,19}, // lower part + // left edge + {1,13,0}, {0,13,12}, + // front edge + {0,12,3}, {3,12,15}, {3,15,4}, {4,15,16}, {4,16,7}, {7,16,19}, {7,19,20}, {7,20,8}, {8,20,11}, {11,20,23}, + // right edge + {11,23,10}, {10,23,22}, + // back edge + {1,13,2}, {2,13,14}, {2,14,17}, {2,17,5}, {5,17,6}, {6,17,18}, {6,18,9}, {9,18,21}, {9,21,10}, {10,21,22} + }; + + std::vector indices = offset_indices(base_indices, vertex_index_offset); + mesh.indices.insert(mesh.indices.end(), indices.begin(), indices.end()); + } + + // __ + // CASE: Groove is closed (Top&Bottom: ___/ \___) + else if (slot_neck_half_width < flap_taper_offset && slot_mouth_half_width < flap_taper_offset) { + float slot_neck_apex_y = slot_neck_half_width / tan(m_groove.angle); + float slot_mouth_apex_y = slot_mouth_half_width / tan(m_groove.angle); + + auto get_vertices = [plane_half_width, plane_half_height] + (float slot_front_z, float slot_back_z, float slot_neck_apex_y, float slot_mouth_apex_y, float slot_mouth_outer_x, float slot_neck_outer_x, + float slot_outer_x_max, bool is_first_groove, bool is_last_groove, float groove_gap, float offset_x) + { + return std::vector({ + // front part vertices + {is_first_groove ? -plane_half_width + offset_x : -(groove_gap / 2.f) - slot_outer_x_max + offset_x, -plane_half_height, slot_front_z}, // *__/\__ + {is_first_groove ? -plane_half_width + offset_x : -(groove_gap / 2.f) - slot_outer_x_max + offset_x, plane_half_height, slot_front_z}, // *__/\__ + {is_last_groove ? plane_half_width + offset_x : (groove_gap / 2.f) + slot_outer_x_max + offset_x, plane_half_height, slot_front_z}, // __/\__* + {is_last_groove ? plane_half_width + offset_x : (groove_gap / 2.f) + slot_outer_x_max + offset_x, -plane_half_height, slot_front_z}, // __/\__* + {slot_mouth_outer_x + offset_x, -plane_half_height, slot_front_z}, // __*/\__ + {offset_x, slot_neck_apex_y, slot_front_z}, // __/*\__ + {-slot_mouth_outer_x + offset_x, -plane_half_height, slot_front_z}, // __/\*__ + // back part vertices + {-slot_neck_outer_x + offset_x, -plane_half_height, slot_back_z}, // __*/\__ + {offset_x, slot_mouth_apex_y, slot_back_z}, // __/*\__ + {slot_neck_outer_x + offset_x, -plane_half_height, slot_back_z}}); // __/\*__ + }; + + std::vector vertices = get_vertices(slot_front_z, slot_back_z, slot_neck_apex_y, slot_mouth_apex_y, + slot_mouth_outer_x, slot_neck_outer_x, slot_outer_x_max, + is_first_groove, is_last_groove, groove_gap, offset_x); + mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end()); + + // Back face + slot_front_z -= wall_thickness; + slot_back_z -= wall_thickness; + slot_neck_apex_y += wall_thickness; + slot_mouth_apex_y += wall_thickness; + + const float slot_back_face_x_offset = wall_thickness / tan(0.5f * m_groove.flaps_angle); + slot_mouth_outer_x += slot_back_face_x_offset; + slot_neck_outer_x += slot_back_face_x_offset; + + vertices = get_vertices(slot_front_z, slot_back_z, slot_neck_apex_y, slot_mouth_apex_y, slot_mouth_outer_x, slot_neck_outer_x, + slot_outer_x_max, is_first_groove, + is_last_groove, groove_gap, offset_x); + mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end()); + + std::vector base_indices = { + // above view + {8,7,9}, // lower part + {5,8,6}, {6,8,7}, // left side + {4,9,8}, {4,8,5}, // right side + {1,0,6}, {1,6,5},{1,5,2}, + {2,5,4}, {2,4,3}, // upper part + // under view + {10,11,16}, {16,11,15}, + {15,11,12}, {15,12,14}, {14,12,13}, // upper part + {18,15,14}, {14,18,19}, // right side + {17,16,15}, {17,15,18}, // left side + {17,18,19}, // lower part + // left edge + {1,11,0}, {0,11,10}, + // front edge + {0,10,6}, {6,10,16}, {6,17,16}, {6,7,17}, {7,17,19}, {7,19,9}, {4,14,19}, {4,19,9}, {4,14,13}, {4,13,3}, + // right edge + {3,13,12}, {3,12,2}, + // back edge + {2,12,11}, {2,11,1} + }; + std::vector indices = offset_indices(base_indices, vertex_index_offset); + + mesh.indices.insert(mesh.indices.end(), indices.begin(), indices.end()); + } + + // Case: Groove is closed from the roof (TOP&Bottom: __/\__ ) + else { + float slot_neck_apex_y = slot_neck_half_width / tan(m_groove.angle); + + std::vector vertices = { + // front part vertices + {is_first_groove ? -plane_half_width + offset_x : -(groove_gap / 2.f) - slot_outer_x_max + offset_x, -plane_half_height, slot_front_z}, + {is_first_groove ? -plane_half_width + offset_x : -(groove_gap / 2.f) - slot_outer_x_max + offset_x, plane_half_height, slot_front_z}, + {is_last_groove ? plane_half_width + offset_x : (groove_gap / 2.f) + slot_outer_x_max + offset_x, plane_half_height, slot_front_z}, + {is_last_groove ? plane_half_width + offset_x : (groove_gap / 2.f) + slot_outer_x_max + offset_x, -plane_half_height, slot_front_z}, + {slot_mouth_outer_x + offset_x, -plane_half_height, slot_front_z}, + {offset_x, slot_neck_apex_y, slot_front_z}, + {-slot_mouth_outer_x + offset_x, -plane_half_height, slot_front_z}, + // back part vertices + {-slot_neck_outer_x + offset_x, -plane_half_height, slot_back_z}, + {-slot_mouth_inner_x + offset_x, plane_half_height, slot_back_z}, + {slot_mouth_inner_x + offset_x, plane_half_height, slot_back_z}, + {slot_neck_outer_x + offset_x, -plane_half_height, slot_back_z}}; + mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end()); + + // Back face + slot_front_z -= wall_thickness; + slot_back_z -= wall_thickness; + + const float slot_back_face_x_offset = wall_thickness / tan(0.5f * m_groove.flaps_angle); + slot_mouth_inner_x += slot_back_face_x_offset; + slot_mouth_outer_x += slot_back_face_x_offset; + slot_neck_outer_x += slot_back_face_x_offset; + + vertices = { + // upper part vertices + {is_first_groove ? -plane_half_width + offset_x : -(groove_gap / 2.f) - slot_outer_x_max + offset_x, -plane_half_height, slot_front_z}, + {is_first_groove ? -plane_half_width + offset_x : -(groove_gap / 2.f) - slot_outer_x_max + offset_x, plane_half_height, slot_front_z}, + {is_last_groove ? plane_half_width + offset_x : (groove_gap / 2.f) + slot_outer_x_max + offset_x, plane_half_height, slot_front_z}, + {is_last_groove ? plane_half_width + offset_x : (groove_gap / 2.f) + slot_outer_x_max + offset_x, -plane_half_height, slot_front_z}, + {slot_mouth_outer_x + offset_x, -plane_half_height, slot_front_z}, + {slot_back_face_x_offset + offset_x, slot_neck_apex_y, slot_front_z}, + {-slot_back_face_x_offset + offset_x, slot_neck_apex_y, slot_front_z}, + {-slot_mouth_outer_x + offset_x, -plane_half_height, slot_front_z}, + // lower part vertices + {-slot_neck_outer_x + offset_x, -plane_half_height, slot_back_z}, + {-slot_mouth_inner_x + offset_x, plane_half_height, slot_back_z}, + {slot_mouth_inner_x + offset_x, plane_half_height, slot_back_z}, + {slot_neck_outer_x + offset_x, -plane_half_height, slot_back_z}}; + mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end()); + + // Indices for this dovetail + std::vector base_indices = { + // above view + {8,7,10}, {8,10,9}, // lower part + {5,8,7}, {5,7,6}, // left side + {4,10,9}, {4,9,5}, // right side + {1,0,6}, {1,6,5},{1,5,2}, {2,5,4}, {2,4,3}, // upper part + // under view + {11,12,18}, {18,12,17}, {17,12,16}, {16,12,13}, {16,13,15}, {15,13,14}, // upper part + {21,16,15}, {21,15,22}, // right side + {19,18,17}, {19,17,20}, // left side + {19,20,21}, {19,21,22}, // lower part + // left edge + {1,12,11}, {1,11,0}, + // front edge + {0,11,18}, {0,18,6}, {7,19,18}, {7,18,6}, {7,19,22}, {7,22,10}, {10,22,15}, {10,15,4}, {4,15,14}, {4,14,3}, + // right edge + {3,14,13}, {3,14,2}, + // back edge + {2,13,12}, {2,12,1}, {5,16,21}, {5,21,9}, {9,21,20}, {9,20,8}, {5,17,20}, {5,20,8} + }; + std::vector indices = offset_indices(base_indices, vertex_index_offset); + + mesh.indices.insert(mesh.indices.end(), indices.begin(), indices.end()); + } + } return mesh; } @@ -2290,7 +2419,7 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors, flo m_imgui->disabled_end(); const float depth_min_value = m_connector_type == CutConnectorType::Snap ? m_connector_size : -0.1f; - if (render_slider_double_input(m_labels_map["Depth"], m_connector_depth_ratio, m_connector_depth_ratio_tolerance, depth_min_value)) + if (render_slider_two_input(m_labels_map["Depth"], m_connector_depth_ratio, m_connector_depth_ratio_tolerance, depth_min_value)) apply_selected_connectors([this, &connectors](size_t idx) { if (m_connector_depth_ratio > 0) connectors[idx].height = m_connector_depth_ratio; @@ -2298,7 +2427,7 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors, flo connectors[idx].height_tolerance = m_connector_depth_ratio_tolerance; }); - if (render_slider_double_input(m_labels_map["Size"], m_connector_size, m_connector_size_tolerance)) + if (render_slider_two_input(m_labels_map["Size"], m_connector_size, m_connector_size_tolerance)) apply_selected_connectors([this, &connectors](size_t idx) { if (m_connector_size > 0) connectors[idx].radius = 0.5f * m_connector_size; @@ -2430,7 +2559,7 @@ void GLGizmoCut3D::process_contours() if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { if (has_valid_groove()) { Cut cut(model_objects[object_idx], instance_idx, get_cut_matrix(selection)); - const ModelObjectPtrs& new_objects = cut.perform_with_groove(m_groove, m_rotation_m, true); + const ModelObjectPtrs& new_objects = cut.perform_with_groove(m_groove, m_rotation_m, m_groove_count, m_groove_gap, m_radius, true); if (!new_objects.empty()) m_part_selection = PartSelection(new_objects.front(), instance_idx); } @@ -2485,13 +2614,13 @@ void GLGizmoCut3D::render_color_marker(float size, const ImU32& color) ImGui::SameLine(); } -void GLGizmoCut3D::render_groove_float_input(const std::string& label, float& in_val, const float& init_val, float& in_tolerance) +void GLGizmoCut3D::render_groove_two_float_input(const std::string& label, float& in_val, const float& init_val, float& in_tolerance) { bool is_changed{false}; float val = in_val; float tolerance = in_tolerance; - if (render_slider_double_input(label, val, tolerance, -0.1f, std::min(0.3f*in_val, 1.5f))) { + if (render_slider_two_input(label, val, tolerance, -0.1f, std::min(0.3f*in_val, 1.5f))) { if (m_imgui->get_last_slider_status().can_take_snapshot) { Plater::TakeSnapshot snapshot(wxGetApp().plater(), GUI::format("%1%: %2%", _u8L("Groove change"), label), UndoRedo::SnapshotType::GizmoAction); m_imgui->get_last_slider_status().invalidate_snapshot(); @@ -2525,6 +2654,82 @@ void GLGizmoCut3D::render_groove_float_input(const std::string& label, float& in } } +void GLGizmoCut3D::render_groove_float_input(const std::string& label, float& in_val, const float& init_val, const bool disabled) +{ + if (disabled) + m_imgui->disabled_begin(true); + + bool is_changed{false}; + Vec2d plate_size = wxGetApp().plater()->get_partplate_list().get_plate(0)->get_size(); + float max_val = std::max(plate_size.x(), plate_size.y()) / (std::max(m_groove_count - 1, 1)); + + float val = in_val; + if (render_slider_input(label, val, -0.1f, max_val)) { + if (m_imgui->get_last_slider_status().can_take_snapshot) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), GUI::format("%1%: %2%", _u8L("Groove change"), label), + UndoRedo::SnapshotType::GizmoAction); + m_imgui->get_last_slider_status().invalidate_snapshot(); + m_groove_editing = true; + } + in_val = val; + is_changed = true; + } + + ImGui::SameLine(); + + if (!disabled) m_imgui->disabled_begin(is_approx(in_val, init_val) ); + const std::string act_name = _u8L("Reset"); + if (render_reset_button("##groove_" + label + act_name, act_name)) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), GUI::format("%1%: %2%", act_name, label), UndoRedo::SnapshotType::GizmoAction); + in_val = init_val; + is_changed = true; + } + if (!disabled) m_imgui->disabled_end(); + + if (is_changed) { + update_plane_model(); + reset_cut_by_contours(); + } + + if (m_is_slider_editing_done) { + m_groove_editing = false; + reset_cut_by_contours(); + } + + if (disabled) + m_imgui->disabled_end(); +} + +void GLGizmoCut3D::render_groove_int_input(const std::string& label, int& in_val, const int& init_val, int min_val, int max_val) +{ + bool is_changed{false}; + + m_imgui->text(label); + ImGui::AlignTextToFramePadding(); + ImGui::SameLine(m_label_width); + ImGui::PushItemWidth(m_control_width); + if (ImGui::InputInt("##int_input", &in_val, 1, 5)) { + in_val = std::clamp(in_val, min_val, max_val); + + is_changed = true; + } + + ImGui::SameLine(); + m_imgui->disabled_begin(in_val == init_val); + const std::string act_name = _u8L("Reset"); + if (render_reset_button("##int_" + label + act_name, act_name)) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), GUI::format("%1%: %2%", act_name, label), UndoRedo::SnapshotType::GizmoAction); + in_val = init_val; + is_changed = true; + } + m_imgui->disabled_end(); + + if (is_changed) { + update_plane_model(); + reset_cut_by_contours(); + } +} + bool GLGizmoCut3D::render_angle_input(const std::string& label, float& in_val, const float& init_val, float min_val, float max_val) { // -------- [ ] @@ -2693,10 +2898,24 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors, floa m_is_slider_editing_done = false; ImGui::Separator(); m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, m_labels_map["Groove"] + ": "); - render_groove_float_input(m_labels_map["Depth"], m_groove.depth, m_groove.depth_init, m_groove.depth_tolerance); - render_groove_float_input(m_labels_map["Width"], m_groove.width, m_groove.width_init, m_groove.width_tolerance); + render_groove_two_float_input(m_labels_map["Depth"], m_groove.depth, m_groove.depth_init, m_groove.depth_tolerance); + render_groove_two_float_input(m_labels_map["Width"], m_groove.width, m_groove.width_init, m_groove.width_tolerance); render_groove_angle_input(m_labels_map["Flap Angle"], m_groove.flaps_angle, m_groove.flaps_angle_init, 30.f, 120.f); render_groove_angle_input(m_labels_map["Groove Angle"], m_groove.angle, m_groove.angle_init, 0.f, 15.f); + + ImGui::Spacing(); + m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, m_labels_map["Multiple"] + ": "); + render_groove_int_input(m_labels_map["Count"], m_groove_count, m_groove_count_init , 1, 100); + render_groove_float_input(m_labels_map["Gap"], m_groove_gap, m_groove_gap_init, m_groove_count == 1); + + m_imgui->disabled_begin(true); + const float groove_width = Cut::calculate_groove_width(m_groove, m_radius); + m_imgui->text(m_labels_map["Spacing"]); + ImGui::SameLine(m_label_width); + std::ostringstream oss; + oss << std::fixed << std::setprecision(2) << (m_groove_gap + groove_width); + m_imgui->text(oss.str() + "mm"); + m_imgui->disabled_end(); } ImGui::Separator(); @@ -3338,7 +3557,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) Cut cut(cut_mo, instance_idx, get_cut_matrix(selection), attributes); const ModelObjectPtrs& new_objects = cut_by_contour ? cut.perform_by_contour(mo, m_part_selection.get_cut_parts(), dowels_count): - cut_with_groove ? cut.perform_with_groove(m_groove, m_rotation_m) : + cut_with_groove ? cut.perform_with_groove(m_groove, m_rotation_m, m_groove_count, m_groove_gap, m_radius) : cut.perform_with_plane(); // fix_non_manifold_edges diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 73838f43c36..e6cbb7d0ef4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -124,6 +124,12 @@ class GLGizmoCut3D : public GLGizmoBase bool m_is_slider_editing_done { false }; + // Multiple Dovetail cuts + int m_groove_count_init { 1 }; + int m_groove_count { 1 }; + float m_groove_gap { 10.f }; // distance between multiple dovetail cuts + float m_groove_gap_init { 10.f }; + // Input params for cut with snaps float m_snap_bulge_proportion{ 0.15f }; float m_snap_space_proportion{ 0.3f }; @@ -301,8 +307,10 @@ protected: void add_horizontal_scaled_interval(float interval); void add_horizontal_shift(float shift); void render_color_marker(float size, const ImU32& color); - void render_groove_float_input(const std::string &label, float &in_val, const float &init_val, float &in_tolerance); + void render_groove_two_float_input(const std::string &label, float &in_val, const float &init_val, float &in_tolerance); + void render_groove_float_input(const std::string &label, float &in_val, const float &init_val, const bool disabled); void render_groove_angle_input(const std::string &label, float &in_val, const float &init_val, float min_val, float max_val); + void render_groove_int_input(const std::string& label, int& in_val, const int& init_val, int min_val, int max_val); bool render_angle_input(const std::string& label, float& in_val, const float& init_val, float min_val, float max_val); void render_snap_specific_input(const std::string& label, const wxString& tooltip, float& in_val, const float& init_val, const float min_val, const float max_val); void render_cut_plane_input_window(CutConnectors &connectors, float x, float y, float bottom_limit); @@ -338,7 +346,8 @@ private: void switch_to_mode(size_t new_mode); bool render_cut_mode_combo(); bool render_double_input(const std::string& label, double& value_in); - bool render_slider_double_input(const std::string& label, float& value_in, float& tolerance_in, float min_val = -0.1f, float max_tolerance = -0.1f); + bool render_slider_two_input(const std::string& label, float& value_in, float& tolerance_in, float min_val = -0.1f, float max_tolerance = -0.1f); + bool render_slider_input(const std::string& label, float& value_in, float min_val = -0.1f, float max_val = 100.f); void render_move_center_input(int axis); void render_connect_mode_radio_button(CutConnectorMode mode); bool render_reset_button(const std::string& label_id, const std::string& tooltip) const; @@ -378,6 +387,7 @@ private: void toggle_model_objects_visibility(); + std::vector offset_indices(const std::vector& base_indices, size_t vo); indexed_triangle_set its_make_groove_plane(); indexed_triangle_set get_connector_mesh(CutConnectorAttributes connector_attributes); From b168ad9c8779b3e208ed69e623bce4c5e0fa9413 Mon Sep 17 00:00:00 2001 From: Hanno Witzleb <48943886+Xipit@users.noreply.github.com> Date: Mon, 18 May 2026 06:58:58 +0200 Subject: [PATCH 2/3] Unify Tool UI Footer (#12850) * unify footer adds "Done" Buttons, standardize "Reset" Buttons, adds disabled logic to "Reset" Buttons * adds ImGuiWrapper::COL_WARNING = ColorRGB:Warning, replaces ColorRGB::Warning * consistently display warnings, moves assermbly warning after footer, make cut warnings warning color * adds separator above cut warnings * adds tooltip entry in move, rotate & scale for auto-drop * use wrapper tooltip in wrapper->button for consistent text color * restructures brim ears button, renames "Reset" button in painting tools to "Erase All", small styling fixes * small adjustments, moves "Edge detection" checkbox in painting tool underneath, moves "Section View" in Brim ears tool after buttons, moves warnings in Assemble tool under footer * small brim ear tool structure changes, adds separator between "Head Diameter" and "Max angle", adds tooltip to "Auto-generate" button, rewords shortcut descriptions to match other tools --------- Co-authored-by: Hanno Witzleb --- src/slic3r/GUI/GLCanvas3D.cpp | 23 ++- src/slic3r/GUI/GLCanvas3D.hpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmoAssembly.cpp | 49 +++-- src/slic3r/GUI/Gizmos/GLGizmoBrimEars.cpp | 176 +++++++++++------- src/slic3r/GUI/Gizmos/GLGizmoBrimEars.hpp | 1 + src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 98 +++++----- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 101 +++++++--- src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp | 3 + src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 65 ++++--- src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp | 24 ++- src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp | 23 ++- .../GUI/Gizmos/GLGizmoMmuSegmentation.cpp | 57 +++--- src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp | 20 +- src/slic3r/GUI/Gizmos/GLGizmoUtils.cpp | 115 +++++++++--- src/slic3r/GUI/Gizmos/GLGizmoUtils.hpp | 13 +- .../GUI/Gizmos/GizmoObjectManipulation.cpp | 54 +++++- src/slic3r/GUI/ImGuiWrapper.cpp | 11 +- src/slic3r/GUI/ImGuiWrapper.hpp | 1 + 18 files changed, 549 insertions(+), 289 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 3ef5eaf0a9f..011231a8f85 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -226,7 +226,7 @@ bool GLCanvas3D::LayersEditing::is_allowed() const float GLCanvas3D::LayersEditing::s_overlay_window_width; -void GLCanvas3D::LayersEditing::render_variable_layer_height_dialog(const GLCanvas3D& canvas) { +void GLCanvas3D::LayersEditing::render_variable_layer_height_dialog(GLCanvas3D& canvas) { if (!m_enabled) return; @@ -336,8 +336,23 @@ void GLCanvas3D::LayersEditing::render_variable_layer_height_dialog(const GLCanv GLGizmoUtils::render_tooltip_button(&imgui, canvas, shortcuts, x, y); ImGui::SameLine(); - if (imgui.button(_L("Reset"))) - wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), SimpleEvent(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE)); + imgui.disabled_begin(check_object_layers_fixed(*m_slicing_parameters, m_layer_height_profile)); + if (imgui.button(_L("Reset"))) { + wxPostEvent((wxEvtHandler*) canvas.get_wxglcanvas(), SimpleEvent(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE)); + } + imgui.disabled_end(); + + ImGui::SameLine(); + GLGizmoUtils::begin_right_aligned_buttons({_L("Done")}); + if (imgui.button(_L("Done"))) { + m_enabled = false; + + GLToolbarItem* item = canvas.m_main_toolbar.get_item("layersediting"); + item->set_state(GLToolbarItem::Normal); + + canvas.set_as_dirty(); + canvas.request_extra_frame(); + } GLCanvas3D::LayersEditing::s_overlay_window_width = ImGui::GetWindowSize().x; imgui.end(); @@ -345,7 +360,7 @@ void GLCanvas3D::LayersEditing::render_variable_layer_height_dialog(const GLCanv imgui.pop_toolbar_style(); } -void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) +void GLCanvas3D::LayersEditing::render_overlay(GLCanvas3D& canvas) { render_variable_layer_height_dialog(canvas); render_active_object_annotations(canvas); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 9e81e5c4f9c..6c3e4573e1b 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -290,8 +290,8 @@ class GLCanvas3D bool is_enabled() const { return m_enabled; } void set_enabled(bool enabled) { m_enabled = is_allowed() && enabled; } - void render_variable_layer_height_dialog(const GLCanvas3D& canvas); - void render_overlay(const GLCanvas3D& canvas); + void render_variable_layer_height_dialog(GLCanvas3D& canvas); + void render_overlay(GLCanvas3D& canvas); void render_volumes(const GLCanvas3D& canvas, const GLVolumeCollection& volumes); void adjust_layer_height_profile(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoAssembly.cpp b/src/slic3r/GUI/Gizmos/GLGizmoAssembly.cpp index f6ce2533f3d..2413ddc38d7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoAssembly.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoAssembly.cpp @@ -104,20 +104,24 @@ void GLGizmoAssembly::on_render_input_window(float x, float y, float bottom_limi } show_selection_ui(); show_face_face_assembly_common(); + ImGui::Separator(); show_face_face_assembly_senior(); show_distance_xyz_ui(); - render_input_window_warning(m_same_model_object); + ImGui::Separator(); - - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f)); - - GLGizmoUtils::render_tooltip_button(m_imgui, m_parent, m_shortcuts, x, y); - float f_scale =m_parent.get_gizmos_manager().get_layout_scale(); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); - ImGui::PopStyleVar(2); + GLGizmoUtils::render_tooltip_button(m_imgui, m_parent, m_shortcuts, x, y); + + ImGui::SameLine(); + GLGizmoUtils::begin_right_aligned_buttons({ _L("Done") }); + if (m_imgui->button(_L("Done"))) { + m_parent.reset_all_gizmos(); + } + + render_input_window_warning(m_same_model_object); if (last_feature != m_curr_feature || last_mode != m_mode || last_selected_features != m_selected_features) { // the dialog may have changed its size, ask for an extra frame to render it properly @@ -127,21 +131,40 @@ void GLGizmoAssembly::on_render_input_window(float x, float y, float bottom_limi m_imgui->set_requires_extra_frame(); } m_last_active_item_imgui = m_current_active_imgui_id; + GizmoImguiEnd(); // Orca + ImGui::PopStyleVar(1); // ImGuiStyleVar_FramePadding ImGuiWrapper::pop_toolbar_style(); } void GLGizmoAssembly::render_input_window_warning(bool same_model_object) { - if (wxGetApp().plater()->canvas3D()->get_canvas_type() == GLCanvas3D::ECanvasType::CanvasView3D) { - if (m_hit_different_volumes.size() == 2) { - if (same_model_object == false) { - m_imgui->warning_text(_L("Warning") + ": " + - _L("It is recommended to assemble the objects first,\nbecause the objects is restriced to bed \nand only parts can be lifted.")); - } + const bool same_mesh_warning = m_hit_different_volumes.size() == 1; + const bool wrong_feature_warning = m_selected_wrong_feature_waring_tip; + const bool not_assembled_warning = m_hit_different_volumes.size() == 2 && same_model_object == false && + wxGetApp().plater()->canvas3D()->get_canvas_type() == GLCanvas3D::ECanvasType::CanvasView3D; + + if (same_mesh_warning || not_assembled_warning || wrong_feature_warning) { + ImGui::Separator(); + } + + if (same_mesh_warning) { + m_imgui->warning_text(_L("Warning: please select two different meshes.")); + } + if (wrong_feature_warning) { + if (m_assembly_mode == AssemblyMode::FACE_FACE) { + m_imgui->warning_text(_L("Warning: please select Plane's feature.")); + } else if (m_assembly_mode == AssemblyMode::POINT_POINT) { + m_imgui->warning_text(_L("Warning: please select Point's or Circle's feature.")); } } + if (not_assembled_warning) { + m_imgui->warning_text( + _L("Warning") + ": " + + _L("It is recommended to assemble the objects first,\nbecause the objects is restriced to bed \nand only parts can be lifted.") + ); + } } bool GLGizmoAssembly::render_assembly_mode_combo(double label_width, float item_width) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.cpp index 682be933ad5..45aaf37a863 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.cpp @@ -48,19 +48,22 @@ bool GLGizmoBrimEars::on_init() const wxString ctrl = GUI::shortkey_ctrl_prefix(); const wxString alt = GUI::shortkey_alt_prefix(); - m_desc["head_diameter"] = _L("Head diameter"); - m_desc["max_angle"] = _L("Max angle"); - m_desc["detection_radius"] = _L("Detection radius"); - m_desc["remove_selected"] = _L("Remove selected points"); - m_desc["remove_all"] = _L("Remove all"); - m_desc["auto_generate"] = _L("Auto-generate points"); - m_desc["section_view"] = _L("Section view"); + m_desc["head_diameter"] = _L("Head diameter"); + m_desc["max_angle"] = _L("Max angle"); + m_desc["detection_radius"] = _L("Detection radius"); + m_desc["remove"] = _L("Remove"); + m_desc["remove_selected"] = _L("Selected"); + m_desc["remove_all"] = _L("All"); + m_desc["create"] = _L("Create"); + m_desc["auto_generate"] = _L("Auto-generate"); + m_desc["auto-generate-tooltip"] = _L("Generate brim ears using Max angle and Detection radius"); + m_desc["section_view"] = _L("Section view"); m_shortcuts = { - {_L("Left mouse button"), _L("Add a brim ear")}, - {_L("Right mouse button"), _L("Delete a brim ear")}, - {ctrl + _L("Mouse wheel"), _L("Adjust head diameter")}, - {alt + _L("Mouse wheel"), _L("Adjust section view")}, + {_L("Left mouse button"), _L("Add or Select")}, + {_L("Right mouse button"), _L("Remove")}, + {ctrl + _L("Mouse wheel"), m_desc["head_diameter"]}, + {alt + _L("Mouse wheel"), m_desc["section_view"]}, }; return true; @@ -545,6 +548,15 @@ void GLGizmoBrimEars::delete_selected_points() update_model_object(); } +bool GLGizmoBrimEars::has_selected_points() const +{ + for (const auto& entry : m_editing_cache) { + if (entry.selected) + return true; + } + return false; +} + void GLGizmoBrimEars::on_dragging(const UpdateData& data) { if (m_hover_id != -1) { @@ -634,21 +646,21 @@ void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limi y = std::min(y, bottom_limit - win_h); GizmoImguiSetNextWIndowPos(x, y, ImGuiCond_Always, 0.0f, 0.0f); - const float currt_scale = m_parent.get_scale(); - ImGuiWrapper::push_toolbar_style(currt_scale); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0 * currt_scale, 5.0 * currt_scale)); - ImGui::PushStyleVar(ImGuiStyleVar_ScrollbarSize, 4.0f * currt_scale); + const float f_scale = m_parent.get_scale(); + ImGuiWrapper::push_toolbar_style(f_scale); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); GizmoImguiBegin(get_name(), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar); float space_size = m_imgui->get_style_scaling() * 8; - std::vector text_list = {m_desc["head_diameter"], m_desc["max_angle"], m_desc["detection_radius"], m_desc["clipping_of_view"]}; + std::vector text_list = {m_desc["head_diameter"], m_desc["max_angle"], m_desc["detection_radius"], m_desc["clipping_of_view"], + m_desc["create"], m_desc["remove"]}; float widest_text = m_imgui->find_widest_text(text_list); float caption_size = widest_text + space_size + ImGui::GetStyle().WindowPadding.x; float input_text_size = m_imgui->scaled(10.0f); float button_size = ImGui::GetFrameHeight(); - float list_width = input_text_size + ImGui::GetStyle().ScrollbarSize + 2 * currt_scale; + float list_width = input_text_size + ImGui::GetStyle().ScrollbarSize + 2 * f_scale; const float slider_icon_width = m_imgui->get_slider_icon_size().x; const float slider_width = list_width - space_size; @@ -662,12 +674,12 @@ void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limi if (last_y != y) last_y = y; } - ImGui::AlignTextToFramePadding(); // Following is a nasty way to: // - save the initial value of the slider before one starts messing with it // - keep updating the head radius during sliding so it is continuosly refreshed in 3D scene // - take correct undo/redo snapshot after the user is done with moving the slider + ImGui::AlignTextToFramePadding(); float initial_value = m_new_point_head_diameter; m_imgui->text(m_desc["head_diameter"]); ImGui::SameLine(caption_size); @@ -684,8 +696,10 @@ void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limi ImGui::SameLine(drag_left_width); ImGui::PushItemWidth(1.5 * slider_icon_width); ImGui::BBLDragFloat("##head_diameter_input", &m_new_point_head_diameter, 0.05f, 0.0f, 0.0f, "%.1f"); - ImGui::AlignTextToFramePadding(); + ImGui::Separator(); + + ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc["max_angle"]); ImGui::SameLine(caption_size); ImGui::PushItemWidth(slider_width); @@ -693,8 +707,8 @@ void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limi ImGui::SameLine(drag_left_width); ImGui::PushItemWidth(1.5 * slider_icon_width); ImGui::BBLDragFloat("##max_angle_input", &m_max_angle, 0.05f, 0.0f, 180.0f, "%.1f"); - ImGui::AlignTextToFramePadding(); + ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc["detection_radius"]); ImGui::SameLine(caption_size); ImGui::PushItemWidth(slider_width); @@ -702,8 +716,37 @@ void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limi ImGui::SameLine(drag_left_width); ImGui::PushItemWidth(1.5 * slider_icon_width); ImGui::BBLDragFloat("##detection_radius_input", &m_detection_radius, 0.05f, 0.0f, static_cast(m_detection_radius_max), "%.1f"); + ImGui::Separator(); + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc["create"]); + ImGui::SameLine(caption_size); + if (m_imgui->button(m_desc["auto_generate"], m_desc["auto-generate-tooltip"])) { + auto_generate(); + } + + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc["remove"]); + ImGui::SameLine(caption_size); + m_imgui->disabled_begin(has_selected_points() == false); + if (m_imgui->button(m_desc["remove_selected"])) { + delete_selected_points(); + } + m_imgui->disabled_end(); + ImGui::SameLine(); + m_imgui->disabled_begin(m_editing_cache.empty()); + if (m_imgui->button(m_desc["remove_all"])) { + if (m_editing_cache.size() > 0) { + select_point(AllPoints); + delete_selected_points(); + } + } + m_imgui->disabled_end(); + + ImGui::Separator(); + + ImGui::AlignTextToFramePadding(); float clp_dist = float(m_c->object_clipper()->get_position()); m_imgui->text(m_desc["section_view"]); ImGui::SameLine(caption_size); @@ -712,79 +755,72 @@ void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limi ImGui::SameLine(drag_left_width); ImGui::PushItemWidth(1.5 * slider_icon_width); bool b_clp_dist_input = ImGui::BBLDragFloat("##section_view_input", &clp_dist, 0.05f, 0.0f, 0.0f, "%.2f"); - if (slider_clp_dist || b_clp_dist_input) { m_c->object_clipper()->set_position_by_ratio(clp_dist, false, true); } - ImGui::Separator(); - - // ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f)); - - float f_scale = m_parent.get_gizmos_manager().get_layout_scale(); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); - if (m_imgui->button(m_desc["auto_generate"])) { auto_generate(); } - - if (m_imgui->button(m_desc["remove_selected"])) { delete_selected_points(); } - float font_size = ImGui::GetFontSize(); - //ImGui::Dummy(ImVec2(font_size * 1, font_size * 1.3)); - ImGui::SameLine(); - if (m_imgui->button(m_desc["remove_all"])) { - if (m_editing_cache.size() > 0) { - select_point(AllPoints); - delete_selected_points(); - } + if (slider_clp_dist || b_clp_dist_input) { + m_c->object_clipper()->set_position_by_ratio(clp_dist, false, true); } - ImGui::PopStyleVar(1); - float get_cur_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y; + ImGui::Separator(); GLGizmoUtils::render_tooltip_button(m_imgui, m_parent, m_shortcuts, x, y); - if (glb_cfg.opt_enum("brim_type") != btPainted) { - ImGui::SameLine(); - auto link_text = [&]() { - ImColor HyperColor = ImGuiWrapper::COL_ORCA; - ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::to_ImVec4(ColorRGB::WARNING())); - float parent_width = ImGui::GetContentRegionAvail().x; + ImGui::SameLine(); + GLGizmoUtils::begin_right_aligned_buttons({_L("Done")}); + if (m_imgui->button(_L("Done"))) { + m_parent.reset_all_gizmos(); + } + + bool brim_not_painted = (obj_cfg.option("brim_type")) ? (obj_cfg.opt_enum("brim_type") != btPainted) : + (glb_cfg.opt_enum("brim_type") != btPainted); + bool has_invalid_ears = !m_single_brim.empty(); + + if (brim_not_painted || has_invalid_ears) { + ImGui::Separator(); + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_WARNING); + + float parent_width = ImGui::GetContentRegionAvail().x; + float font_size = ImGui::GetFontSize(); + + if (brim_not_painted) { m_imgui->text_wrapped(_L("Warning: The brim type is not set to \"painted\", the brim ears will not take effect!"), parent_width); - ImGui::PopStyleColor(); + + ImColor HyperColor = ImGuiWrapper::COL_ORCA; ImGui::PushStyleColor(ImGuiCol_Text, HyperColor.Value); - ImGui::Dummy(ImVec2(font_size * 1.8, font_size * 1.3)); + + ImGui::Dummy(ImVec2(font_size * 1.8f, 0.0f)); // Horizontal indent ImGui::SameLine(); m_imgui->bold_text(_u8L("Set the brim type of this object to \"painted\"")); - ImGui::PopStyleColor(); - // underline - ImVec2 lineEnd = ImGui::GetItemRectMax(); - lineEnd.y -= 2.0f; - ImVec2 lineStart = lineEnd; - lineStart.x = ImGui::GetItemRectMin().x; - ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, HyperColor); - if (ImGui::IsMouseHoveringRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), true)) { + + ImVec2 min = ImGui::GetItemRectMin(); + ImVec2 max = ImGui::GetItemRectMax(); + ImGui::GetWindowDrawList()->AddLine(ImVec2(min.x, max.y - 2.0f), ImVec2(max.x, max.y - 2.0f), HyperColor); + + if (ImGui::IsMouseHoveringRect(min, max)) { m_link_text_hover = true; + ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { DynamicPrintConfig new_conf = obj_cfg; new_conf.set_key_value("brim_type", new ConfigOptionEnum(btPainted)); mo->config.assign_config(new_conf); } - }else { + } else { m_link_text_hover = false; } - }; - - if (obj_cfg.option("brim_type")) { - if (obj_cfg.opt_enum("brim_type") != btPainted) { - link_text(); - } - }else { - link_text(); + ImGui::PopStyleColor(1); // Pop HyperColor } - } + if (has_invalid_ears) { + wxString out = _L("Warning") + ": " + std::to_string(m_single_brim.size()) + " " + _L("invalid brim ears"); + m_imgui->text_wrapped(out, parent_width); + } - if (!m_single_brim.empty()) { - wxString out = _L("Warning") + ": " + std::to_string(m_single_brim.size()) + _L(" invalid brim ears"); - m_imgui->warning_text(out); + ImGui::PopStyleColor(1); // Pop Warning Color + } else { + // Reset hover state if no warnings are active + m_link_text_hover = false; } GizmoImguiEnd(); - ImGui::PopStyleVar(2); + ImGui::PopStyleVar(1); // ImGuiStyleVar_FramePadding ImGuiWrapper::pop_toolbar_style(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.hpp index 8f50859130b..deb6525bfaf 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.hpp @@ -80,6 +80,7 @@ public: bool on_mouse(const wxMouseEvent& mouse_event) override; bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); void delete_selected_points(); + bool has_selected_points() const; void update_model_object(); //ClippingPlane get_sla_clipping_plane() const; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 5fed9303211..ea8d3c374f5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -2369,7 +2369,10 @@ void GLGizmoCut3D::apply_selected_connectors(std::function app void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors, float x, float y, float bottom_limit) { // Connectors section - + + float f_scale = m_parent.get_gizmos_manager().get_layout_scale(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); + ImGui::Separator(); // WIP : Auto : Need to implement @@ -2447,26 +2450,24 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors, flo ImGui::Separator(); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f)); render_tooltip_button(x, y); - float f_scale = m_parent.get_gizmos_manager().get_layout_scale(); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); - ImGui::SameLine(); + GLGizmoUtils::begin_right_aligned_buttons({_L("Confirm connectors"), _L("Cancel")}); + GLGizmoUtils::push_orca_button_style(); if (m_imgui->button(_L("Confirm connectors"))) { unselect_all_connectors(); set_connectors_editing(false); } + GLGizmoUtils::pop_orca_button_style(); - ImGui::SameLine(m_label_width + m_editing_window_width - m_imgui->calc_text_size(_L("Cancel")).x - m_imgui->get_style_scaling() * 8); - + ImGui::SameLine(); if (m_imgui->button(_L("Cancel"))) { reset_connectors(); set_connectors_editing(false); } - ImGui::PopStyleVar(2); + ImGui::PopStyleVar(1); // ImGuiStyleVar_FramePadding } void GLGizmoCut3D::render_build_size() @@ -2846,10 +2847,14 @@ void GLGizmoCut3D::render_snap_specific_input(const std::string& label, const wx void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors, float x, float y, float bottom_limit) { -// if (m_mode == size_t(CutMode::cutPlanar)) { + const bool has_connectors = !connectors.empty(); + const bool is_cut_plane_init = m_rotation_m.isApprox(Transform3d::Identity()) && m_bb_center.isApprox(m_plane_center); + + float f_scale = m_parent.get_gizmos_manager().get_layout_scale(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); + CutMode mode = CutMode(m_mode); if (mode == CutMode::cutPlanar || mode == CutMode::cutTongueAndGroove) { - const bool has_connectors = !connectors.empty(); m_imgui->disabled_begin(has_connectors); if (render_cut_mode_combo()) @@ -2864,7 +2869,6 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors, floa render_move_center_input(Z); ImGui::SameLine(); - const bool is_cut_plane_init = m_rotation_m.isApprox(Transform3d::Identity()) && m_bb_center.isApprox(m_plane_center); m_imgui->disabled_begin(is_cut_plane_init); std::string act_name = _u8L("Reset cutting plane"); if (render_reset_button("cut_plane", act_name)) { @@ -2873,26 +2877,13 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors, floa } m_imgui->disabled_end(); -// render_flip_plane_button(); - if (mode == CutMode::cutPlanar) { - add_vertical_scaled_interval(0.75f); + ImGui::Separator(); 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(); - - ImGui::SameLine(1.5f * m_control_width); - - m_imgui->disabled_begin(is_cut_plane_init && !has_connectors); - act_name = _u8L("Reset cut"); - if (m_imgui->button(wxString::FromUTF8(act_name), _L("Reset cutting plane and remove connectors"))) { - Plater::TakeSnapshot snapshot(wxGetApp().plater(), act_name, UndoRedo::SnapshotType::GizmoAction); - reset_cut_plane(); - reset_connectors(); - } - m_imgui->disabled_end(); } else if (mode == CutMode::cutTongueAndGroove) { m_is_slider_editing_done = false; @@ -2978,21 +2969,35 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors, floa } ImGui::Separator(); + + render_tooltip_button(x, y); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f)); - float get_cur_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y; - render_tooltip_button(x, get_cur_y); - - float f_scale = m_parent.get_gizmos_manager().get_layout_scale(); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); + if (mode == CutMode::cutPlanar) { + ImGui::SameLine(); + m_imgui->disabled_begin(is_cut_plane_init && !has_connectors); + if (m_imgui->button(_L("Reset"), _L("Reset cutting plane and remove connectors"))) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Reset Cut", UndoRedo::SnapshotType::GizmoAction); + reset_cut_plane(); + reset_connectors(); + } + m_imgui->disabled_end(); + } ImGui::SameLine(); + GLGizmoUtils::begin_right_aligned_buttons({_L("Perform cut"), _L("Cancel")}); m_imgui->disabled_begin(!can_perform_cut()); - if(m_imgui->button(_L("Perform cut"))) - perform_cut(m_parent.get_selection()); + GLGizmoUtils::push_orca_button_style(); + if(m_imgui->button(_L("Perform cut"))) + perform_cut(m_parent.get_selection()); + GLGizmoUtils::pop_orca_button_style(); m_imgui->disabled_end(); - ImGui::PopStyleVar(2); + ImGui::SameLine(); + if (m_imgui->button(_L("Cancel"))) { + m_parent.reset_all_gizmos(); + } + + ImGui::PopStyleVar(1); // ImGuiStyleVar_FramePadding } void GLGizmoCut3D::validate_connector_settings() @@ -3095,7 +3100,16 @@ void GLGizmoCut3D::init_input_window_data(CutConnectors &connectors) void GLGizmoCut3D::render_input_window_warning() const { - if (! m_invalid_connectors_idxs.empty()) { + const bool invalid_connector_warning = !m_invalid_connectors_idxs.empty(); + const bool keep_after_cut_warning = !m_keep_upper && !m_keep_lower; + const bool invalid_contour_warning = !has_valid_contour(); + const bool invalid_groove_warning = !has_valid_groove(); + + if (invalid_connector_warning || keep_after_cut_warning || invalid_contour_warning || invalid_groove_warning) { + ImGui::Separator(); + } + + if (invalid_connector_warning) { wxString out = /*wxString(ImGui::WarningMarkerSmall)*/ _L("Warning") + ": " + _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), @@ -3105,14 +3119,14 @@ void GLGizmoCut3D::render_input_window_warning() const m_info_stats.outside_bb); if (m_info_stats.is_overlap) out += "\n - " + _L("Some connectors are overlapped"); - m_imgui->text(out); + m_imgui->warning_text(out); } - if (!m_keep_upper && !m_keep_lower) - m_imgui->text(/*wxString(ImGui::WarningMarkerSmall)*/ _L("Warning") + ": " + _L("Select at least one object to keep after cutting.")); - if (!has_valid_contour()) - m_imgui->text(/*wxString(ImGui::WarningMarkerSmall)*/ _L("Warning") + ": " + _L("Cut plane is placed out of object")); - else if (!has_valid_groove()) - m_imgui->text(/*wxString(ImGui::WarningMarkerSmall)*/ _L("Warning") + ": " + _L("Cut plane with groove is invalid")); + if (keep_after_cut_warning) + m_imgui->warning_text(/*wxString(ImGui::WarningMarkerSmall)*/ _L("Warning") + ": " + _L("Select at least one object to keep after cutting.")); + if (invalid_contour_warning) + m_imgui->warning_text(/*wxString(ImGui::WarningMarkerSmall)*/ _L("Warning") + ": " + _L("Cut plane is placed out of object")); + else if (invalid_groove_warning) + m_imgui->warning_text(/*wxString(ImGui::WarningMarkerSmall)*/ _L("Warning") + ": " + _L("Cut plane with groove is invalid")); } void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 8766ad7b13a..b7c963cc633 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -1267,6 +1267,59 @@ void GLGizmoEmboss::reset_volume() remove_notification_not_valid_font(); } +namespace { +// FIX IT: It should not change volume position before successfull change volume by process +void fix_transformation(const StyleManager::Style& from, const StyleManager::Style& to, GLCanvas3D& canvas) +{ + // fix Z rotation when exists difference in styles + const std::optional& f_angle_opt = from.angle; + const std::optional& t_angle_opt = to.angle; + if (!is_approx(f_angle_opt, t_angle_opt)) { + // fix rotation + float f_angle = f_angle_opt.value_or(.0f); + float t_angle = t_angle_opt.value_or(.0f); + do_local_z_rotate(canvas.get_selection(), t_angle - f_angle); + std::string no_snapshot; + canvas.do_rotate(no_snapshot); + } + + // fix distance (Z move) when exists difference in styles + const std::optional& f_move_opt = from.distance; + const std::optional& t_move_opt = to.distance; + if (!is_approx(f_move_opt, t_move_opt)) { + float f_move = f_move_opt.value_or(.0f); + float t_move = t_move_opt.value_or(.0f); + do_local_z_move(canvas.get_selection(), t_move - f_move); + std::string no_snapshot; + canvas.do_move(no_snapshot); + } +} +} // namespace + +bool GLGizmoEmboss::is_changed_from_default_style() +{ + int default_index = 0; + const StyleManager::Style& default_style = m_style_manager.get_styles()[default_index]; + const StyleManager::Style& current_style = m_style_manager.get_style(); + + return default_style == current_style; +} + +void GLGizmoEmboss::reset_to_default_style() +{ + int default_index = 0; + const StyleManager::Style& current_style = m_style_manager.get_style(); + const StyleManager::Style& default_style = m_style_manager.get_styles()[default_index]; + + StyleManager::Style cur_s = current_style; // copy + StyleManager::Style new_s = default_style; // copy + + if (m_style_manager.load_style(default_index)) { + ::fix_transformation(cur_s, new_s, m_parent); + process(); + } +} + void GLGizmoEmboss::calculate_scale() { Transform3d to_world = m_parent.get_selection().get_first_volume()->world_matrix(); auto to_world_linear = to_world.linear(); @@ -1406,9 +1459,29 @@ void GLGizmoEmboss::draw_window(float x, float y) draw_model_type(); } + ImGui::Spacing(); ImGui::Separator(); + ImGui::Spacing(); + float f_scale = m_parent.get_gizmos_manager().get_layout_scale(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); GLGizmoUtils::render_tooltip_button(m_imgui, m_parent, m_shortcuts, x, y); + + ImGui::SameLine(); + m_imgui->disabled_begin(is_changed_from_default_style()); + if (m_imgui->button(_L("Reset"), _L("Reset all options except the text and operation"))) { + reset_to_default_style(); + } + m_imgui->disabled_end(); + + ImGui::SameLine(); + GLGizmoUtils::begin_right_aligned_buttons({_L("Done")}); + + if (m_imgui->button(_L("Done"))) { + m_parent.reset_all_gizmos(); + } + + ImGui::PopStyleVar(1); // ImGuiStyleVar_FramePadding #ifdef SHOW_WX_FONT_DESCRIPTOR if (is_selected_style) @@ -2155,34 +2228,6 @@ void GLGizmoEmboss::draw_delete_style_button() { } } -namespace { -// FIX IT: It should not change volume position before successfull change volume by process -void fix_transformation(const StyleManager::Style &from, const StyleManager::Style &to, GLCanvas3D &canvas) { - // fix Z rotation when exists difference in styles - const std::optional &f_angle_opt = from.angle; - const std::optional &t_angle_opt = to.angle; - if (!is_approx(f_angle_opt, t_angle_opt)) { - // fix rotation - float f_angle = f_angle_opt.value_or(.0f); - float t_angle = t_angle_opt.value_or(.0f); - do_local_z_rotate(canvas.get_selection(), t_angle - f_angle); - std::string no_snapshot; - canvas.do_rotate(no_snapshot); - } - - // fix distance (Z move) when exists difference in styles - const std::optional &f_move_opt = from.distance; - const std::optional &t_move_opt = to.distance; - if (!is_approx(f_move_opt, t_move_opt)) { - float f_move = f_move_opt.value_or(.0f); - float t_move = t_move_opt.value_or(.0f); - do_local_z_move(canvas.get_selection(), t_move - f_move); - std::string no_snapshot; - canvas.do_move(no_snapshot); - } -} -} // namesapce - void GLGizmoEmboss::draw_style_list() { if (!m_style_manager.is_active_font()) return; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index f5916620f82..4ad41483ac7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -109,6 +109,9 @@ private: void set_volume_by_selection(); void reset_volume(); + bool is_changed_from_default_style(); + void reset_to_default_style(); + // create volume from text - main functionality bool process(bool make_snapshot = true); void close(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 7af55915d57..2f69ed64faf 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -82,7 +82,7 @@ bool GLGizmoFdmSupports::on_init() m_desc["perform"] = _L("Perform"); m_desc["on_overhangs_only"] = _L("On highlighted overhangs only"); - m_desc["remove_all"] = _L("Erase all painting"); + m_desc["remove_all"] = _L("Erase all"); m_desc["highlight_by_angle"] = _L("Highlight overhang areas"); m_desc["tool_type"] = _L("Tool type"); m_desc["gap_fill"] = _L("Gap fill"); @@ -215,6 +215,9 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l //BBS ImGuiWrapper::push_toolbar_style(m_parent.get_scale()); + float f_scale = m_parent.get_gizmos_manager().get_layout_scale(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); + GizmoImguiBegin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar); // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: @@ -225,11 +228,10 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l const float highlight_slider_left = m_imgui->calc_text_size(m_desc.at("highlight_by_angle")).x + m_imgui->scaled(1.5f); const float reset_button_slider_left = m_imgui->calc_text_size(m_desc.at("reset_direction")).x + m_imgui->scaled(1.5f) + ImGui::GetStyle().FramePadding.x * 2; const float on_overhangs_only_width = m_imgui->calc_text_size(m_desc["on_overhangs_only"]).x + m_imgui->scaled(1.5f); - const float remove_btn_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.5f); const float filter_btn_width = m_imgui->calc_text_size(m_desc.at("perform")).x + m_imgui->scaled(1.5f); const float gap_area_txt_width = m_imgui->calc_text_size(m_desc.at("gap_area")).x + m_imgui->scaled(1.5f); const float smart_fill_angle_txt_width = m_imgui->calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.5f); - const float buttons_width = remove_btn_width + filter_btn_width + m_imgui->scaled(1.5f); + const float buttons_width = filter_btn_width + m_imgui->scaled(1.5f); const float empty_button_width = m_imgui->calc_button_size("").x; const float tips_width = m_imgui->calc_text_size(_L("Auto support threshold angle: ") + " 90 ").x + m_imgui->scaled(1.5f); @@ -358,12 +360,27 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::SameLine(drag_left_width + sliders_left_width); ImGui::PushItemWidth(1.5 * slider_icon_width); ImGui::BBLDragFloat("##gap_area_input", &TriangleSelectorPatch::gap_area, 0.05f, 0.0f, 0.0f, "%.2f"); + + // Apply gap fill button + if (m_imgui->button(m_desc.at("perform"))) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Reset selection", UndoRedo::SnapshotType::GizmoAction); + + for (int i = 0; i < m_triangle_selectors.size(); i++) { + TriangleSelectorPatch* ts_mm = dynamic_cast(m_triangle_selectors[i].get()); + ts_mm->update_selector_triangles(); + ts_mm->request_update_render_data(true); + } + update_model_object(); + m_parent.set_as_dirty(); + } } - m_imgui->bbl_checkbox(m_desc["on_overhangs_only"], m_paint_on_overhangs_only); - if (ImGui::IsItemHovered()) - m_imgui->tooltip(format_wxstr(_L("Allows painting only on facets selected by: \"%1%\""), m_desc["highlight_by_angle"]), - max_tooltip_width); + if (m_current_tool != ImGui::GapFillIcon) { + m_imgui->bbl_checkbox(m_desc["on_overhangs_only"], m_paint_on_overhangs_only); + if (ImGui::IsItemHovered()) + m_imgui->tooltip(format_wxstr(_L("Allows painting only on facets selected by: \"%1%\""), m_desc["highlight_by_angle"]), + max_tooltip_width); + } ImGui::Separator(); @@ -427,33 +444,12 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l if (b_bbl_slider_float || b_drag_input) m_c->object_clipper()->set_position_by_ratio(clp_dist, true); - ImGui::Separator(); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f)); + render_tooltip_button(x, y); - float f_scale =m_parent.get_gizmos_manager().get_layout_scale(); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); - ImGui::SameLine(); - - // Perform button is for gap fill - if (m_current_tool == ImGui::GapFillIcon) { - if (m_imgui->button(m_desc.at("perform"))) { - Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Reset selection", UndoRedo::SnapshotType::GizmoAction); - - for (int i = 0; i < m_triangle_selectors.size(); i++) { - TriangleSelectorPatch* ts_mm = dynamic_cast(m_triangle_selectors[i].get()); - ts_mm->update_selector_triangles(); - ts_mm->request_update_render_data(true); - } - update_model_object(); - m_parent.set_as_dirty(); - } - } - - ImGui::SameLine(); - + m_imgui->disabled_begin(m_c->selection_info()->model_object()->is_fdm_support_painted() == false); if (m_imgui->button(m_desc.at("remove_all"))) { Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Reset selection", UndoRedo::SnapshotType::GizmoAction); ModelObject * mo = m_c->selection_info()->model_object(); @@ -468,11 +464,18 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l update_model_object(); m_parent.set_as_dirty(); } - ImGui::PopStyleVar(2); + m_imgui->disabled_end(); + + ImGui::SameLine(); + GLGizmoUtils::begin_right_aligned_buttons({_L("Done")}); + if (m_imgui->button(_L("Done"))) { + m_parent.reset_all_gizmos(); + } GizmoImguiEnd(); // BBS + ImGui::PopStyleVar(1); // ImGuiStyleVar_FramePadding ImGuiWrapper::pop_toolbar_style(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp index d80687544ee..6689fcdcead 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp @@ -37,7 +37,7 @@ bool GLGizmoFuzzySkin::on_init() const wxString shift = GUI::shortkey_shift_prefix(); m_desc["reset_direction"] = _L("Reset direction"); - m_desc["remove_all"] = _L("Erase all painting"); + m_desc["remove_all"] = _L("Erase all"); m_desc["circle"] = _L("Circle"); m_desc["sphere"] = _L("Sphere"); m_desc["pointer"] = _L("Triangles"); @@ -137,6 +137,8 @@ void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_lim // BBS ImGuiWrapper::push_toolbar_style(m_parent.get_scale()); + float f_scale = m_parent.get_gizmos_manager().get_layout_scale(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); GizmoImguiBegin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar); @@ -151,7 +153,6 @@ void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_lim const float cursor_type_radio_sphere = m_imgui->calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f); const float cursor_type_radio_pointer = m_imgui->calc_text_size(m_desc["pointer"]).x + m_imgui->scaled(2.5f); - const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f); const float buttons_width = m_imgui->scaled(0.5f); const float minimal_slider_width = m_imgui->scaled(4.f); @@ -176,7 +177,6 @@ void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_lim const float empty_button_width = m_imgui->calc_button_size("").x; window_width = std::max(window_width, total_text_max); - window_width = std::max(window_width, button_width); window_width = std::max(window_width, cursor_type_radio_circle + cursor_type_radio_sphere + cursor_type_radio_pointer); window_width = std::max(window_width, tool_type_radio_left + tool_type_radio_brush + tool_type_radio_smart_fill); window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f)); @@ -299,14 +299,10 @@ void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_lim ImGui::Separator(); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f)); render_tooltip_button(x, y); - float f_scale = m_parent.get_gizmos_manager().get_layout_scale(); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); - ImGui::SameLine(); - + m_imgui->disabled_begin(mo->is_fuzzy_skin_painted() == false); if (m_imgui->button(m_desc.at("remove_all"))) { Plater::TakeSnapshot snapshot(wxGetApp().plater(), _u8L("Reset selection"), UndoRedo::SnapshotType::GizmoAction); int idx = -1; @@ -320,6 +316,13 @@ void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_lim update_model_object(); m_parent.set_as_dirty(); } + m_imgui->disabled_end(); + + ImGui::SameLine(); + GLGizmoUtils::begin_right_aligned_buttons({_L("Done")}); + if (m_imgui->button(_L("Done"))) { + m_parent.reset_all_gizmos(); + } const DynamicPrintConfig &glb_cfg = wxGetApp().preset_bundle->prints.get_edited_preset().config; const bool has_object_fuzzy_override = obj_cfg.option("fuzzy_skin"); @@ -329,7 +332,7 @@ void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_lim float font_size = ImGui::GetFontSize(); auto link_text = [&]() { ImColor HyperColor = ImGuiWrapper::COL_ORCA; - ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::to_ImVec4(ColorRGB::WARNING())); + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_WARNING); float parent_width = ImGui::GetContentRegionAvail().x; m_imgui->text_wrapped(_L("Warning: Fuzzy skin is disabled, painted fuzzy skin will not take effect!"), parent_width); ImGui::PopStyleColor(); @@ -350,10 +353,11 @@ void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_lim mo->config.assign_config(new_conf); } }; + ImGui::Separator(); link_text(); } - ImGui::PopStyleVar(2); + ImGui::PopStyleVar(1); // ImGuiStyleVar_FramePadding GizmoImguiEnd(); // BBS diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp index efc7b82fae4..6c6f9132760 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp @@ -1917,18 +1917,6 @@ void GLGizmoMeasure::show_selection_ui() if (m_show_reset_first_tip) { m_imgui->text(_L("Feature 1 has been reset, \nfeature 2 has been feature 1")); } - if (m_selected_wrong_feature_waring_tip) { - if (m_measure_mode == EMeasureMode::ONLY_ASSEMBLY) { - if (m_assembly_mode == AssemblyMode::FACE_FACE) { - m_imgui->warning_text(_L("Warning: please select Plane's feature.")); - } else if (m_assembly_mode == AssemblyMode::POINT_POINT) { - m_imgui->warning_text(_L("Warning: please select Point's or Circle's feature.")); - } - } - } - if (m_measure_mode == EMeasureMode::ONLY_ASSEMBLY && m_hit_different_volumes.size() == 1) { - m_imgui->warning_text(_L("Warning: please select two different meshes.")); - } } void GLGizmoMeasure::show_distance_xyz_ui() @@ -2175,10 +2163,21 @@ void GLGizmoMeasure::on_render_input_window(float x, float y, float bottom_limit ImGui::Separator(); show_distance_xyz_ui(); + ImGui::Separator(); + float f_scale = m_parent.get_gizmos_manager().get_layout_scale(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); GLGizmoUtils::render_tooltip_button(m_imgui, m_parent, m_shortcuts, x, y); + ImGui::SameLine(); + GLGizmoUtils::begin_right_aligned_buttons({ _L("Done") }); + if (m_imgui->button(_L("Done"))) { + m_parent.reset_all_gizmos(); + } + + ImGui::PopStyleVar(1); // ImGuiStyleVar_FramePadding + if (last_feature != m_curr_feature || last_mode != m_mode || last_selected_features != m_selected_features) { // the dialog may have changed its size, ask for an extra frame to render it properly last_feature = m_curr_feature; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index 72e880f85d3..213e5db9a4b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -103,7 +103,7 @@ bool GLGizmoMmuSegmentation::on_init() m_desc["edge_detection"] = _L("Edge detection"); m_desc["gap_area"] = _L("Gap area"); m_desc["perform"] = _L("Perform"); - m_desc["remove_all"] = _L("Erase all painting"); + m_desc["remove_all"] = _L("Erase all"); m_desc["circle"] = _L("Circle"); m_desc["sphere"] = _L("Sphere"); m_desc["pointer"] = _L("Triangles"); @@ -362,6 +362,9 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott // BBS ImGuiWrapper::push_toolbar_style(m_parent.get_scale()); + float f_scale = m_parent.get_gizmos_manager().get_layout_scale(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); + GizmoImguiBegin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar); // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: @@ -374,10 +377,9 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott const float gap_area_slider_left = m_imgui->calc_text_size(m_desc.at("gap_area")).x + m_imgui->scaled(1.5f) + space_size; const float height_range_slider_left = m_imgui->calc_text_size(m_desc.at("height_range")).x + m_imgui->scaled(2.f); - const float remove_btn_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f); const float filter_btn_width = m_imgui->calc_text_size(m_desc.at("perform")).x + m_imgui->scaled(1.f); const float remap_btn_width = m_imgui->calc_text_size(m_desc.at("perform_remap")).x + m_imgui->scaled(1.f); - const float buttons_width = remove_btn_width + filter_btn_width + remap_btn_width + m_imgui->scaled(2.f); + const float buttons_width = filter_btn_width + remap_btn_width + m_imgui->scaled(2.f); const float minimal_slider_width = m_imgui->scaled(4.f); const float color_button_width = m_imgui->calc_text_size(std::string_view{""}).x + m_imgui->scaled(1.75f); @@ -566,8 +568,6 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott m_cursor_type = TriangleSelector::CursorType::POINTER; m_tool_type = ToolType::BUCKET_FILL; - m_imgui->bbl_checkbox(m_desc["edge_detection"], m_detect_geometry_edge); - if (m_detect_geometry_edge) { ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc["smart_fill_angle"]); @@ -587,6 +587,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott // set to negative value to disable edge detection m_smart_fill_angle = -1.f; } + + m_imgui->bbl_checkbox(m_desc["edge_detection"], m_detect_geometry_edge); } else if (m_current_tool == ImGui::HeightRangeIcon) { m_tool_type = ToolType::BRUSH; @@ -613,6 +615,19 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott ImGui::SameLine(drag_left_width + sliders_left_width); ImGui::PushItemWidth(1.5 * slider_icon_width); ImGui::BBLDragFloat("##gap_area_input", &TriangleSelectorPatch::gap_area, 0.05f, 0.0f, 0.0f, "%.2f"); + + // Apply Gap fill button + if (m_imgui->button(m_desc.at("perform"))) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Gap fill", UndoRedo::SnapshotType::GizmoAction); + + for (int i = 0; i < m_triangle_selectors.size(); i++) { + TriangleSelectorPatch* ts_mm = dynamic_cast(m_triangle_selectors[i].get()); + ts_mm->update_selector_triangles(); + ts_mm->request_update_render_data(true); + } + update_model_object(); + m_parent.set_as_dirty(); + } } ImGui::Separator(); @@ -639,30 +654,10 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott ImGui::Separator(); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f)); render_tooltip_button(x, y); - float f_scale =m_parent.get_gizmos_manager().get_layout_scale(); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); - ImGui::SameLine(); - - if (m_current_tool == ImGui::GapFillIcon) { - if (m_imgui->button(m_desc.at("perform"))) { - Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Gap fill", UndoRedo::SnapshotType::GizmoAction); - - for (int i = 0; i < m_triangle_selectors.size(); i++) { - TriangleSelectorPatch* ts_mm = dynamic_cast(m_triangle_selectors[i].get()); - ts_mm->update_selector_triangles(); - ts_mm->request_update_render_data(true); - } - update_model_object(); - m_parent.set_as_dirty(); - } - - ImGui::SameLine(); - } - + m_imgui->disabled_begin(m_c->selection_info()->model_object()->is_mm_painted() == false); if (m_imgui->button(m_desc.at("remove_all"))) { Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Reset selection", UndoRedo::SnapshotType::GizmoAction); ModelObject * mo = m_c->selection_info()->model_object(); @@ -677,7 +672,15 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott update_model_object(); m_parent.set_as_dirty(); } - ImGui::PopStyleVar(2); + m_imgui->disabled_end(); + + ImGui::SameLine(); + GLGizmoUtils::begin_right_aligned_buttons({_L("Done")}); + if (m_imgui->button(_L("Done"))) { + m_parent.reset_all_gizmos(); + } + + ImGui::PopStyleVar(1); // ImGuiStyleVar_FramePadding GizmoImguiEnd(); // BBS diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp index 1051c5ab3ce..b211a18fb2d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp @@ -41,7 +41,7 @@ bool GLGizmoSeam::on_init() m_desc["enforce"] = _L("Enforce seam"); m_desc["block"] = _L("Block seam"); m_desc["remove"] = _L("Erase"); - m_desc["remove_all"] = _L("Erase all painting"); + m_desc["remove_all"] = _L("Erase all"); m_desc["circle"] = _L("Circle"); m_desc["sphere"] = _L("Sphere"); @@ -140,6 +140,8 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) wchar_t old_tool = m_current_tool; //BBS ImGuiWrapper::push_toolbar_style(m_parent.get_scale()); + float f_scale = m_parent.get_gizmos_manager().get_layout_scale(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); GizmoImguiBegin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar); @@ -256,14 +258,10 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) ImGui::Separator(); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f)); GLGizmoUtils::render_tooltip_button(m_imgui, m_parent, m_shortcuts, x, y); - float f_scale =m_parent.get_gizmos_manager().get_layout_scale(); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); - ImGui::SameLine(); - + m_imgui->disabled_begin(m_c->selection_info()->model_object()->is_seam_painted() == false); if (m_imgui->button(m_desc.at("remove_all"))) { Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Reset selection", UndoRedo::SnapshotType::GizmoAction); ModelObject *mo = m_c->selection_info()->model_object(); @@ -278,7 +276,15 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) update_model_object(); m_parent.set_as_dirty(); } - ImGui::PopStyleVar(2); + m_imgui->disabled_end(); + + ImGui::SameLine(); + GLGizmoUtils::begin_right_aligned_buttons({_L("Done")}); + if (m_imgui->button(_L("Done"))) { + m_parent.reset_all_gizmos(); + } + + ImGui::PopStyleVar(1); // ImGuiStyleVar_FramePadding GizmoImguiEnd(); //BBS diff --git a/src/slic3r/GUI/Gizmos/GLGizmoUtils.cpp b/src/slic3r/GUI/Gizmos/GLGizmoUtils.cpp index 3566bccf12a..00d608b80ae 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoUtils.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoUtils.cpp @@ -26,6 +26,7 @@ - [Confirm], [Cancel], [Done], ... are buttons that close the Tool Dialog - [Reset], [Button1], ... are buttons that do not! - Non-consequential buttons like [Cancel] and [Done] are always the right-most buttons + - [Confirm] buttons should use the orca_button_style to differentiate them from other buttons - Multiple warnings can show, but should only have one ImGui::Separator above - If no warnings is shown, dont render the ImGui::Separator @@ -33,43 +34,101 @@ namespace Slic3r::GUI::GLGizmoUtils { -void render_tooltip_button( - ImGuiWrapper* imgui_wrapper, const GLCanvas3D& canvas, const std::vector>& shortcuts, float x, float y) -{ - float caption_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y; - float caption_x_max = 0.f; - for (const auto& item : shortcuts) { - caption_x_max = std::max(caption_x_max, imgui_wrapper->calc_text_size(item.first).x); - } - caption_x_max += imgui_wrapper->calc_text_size(": "sv).x + 35.f; + void render_tooltip_button( + ImGuiWrapper* imgui_wrapper, const GLCanvas3D& canvas, const std::vector>& shortcuts, float x, float y) + { + float caption_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y; + float caption_x_max = 0.f; + for (const auto& item : shortcuts) { + caption_x_max = std::max(caption_x_max, imgui_wrapper->calc_text_size(item.first).x); + } + caption_x_max += imgui_wrapper->calc_text_size(": "sv).x + 35.f; - auto& gizmos_manager = canvas.get_gizmos_manager(); - ImTextureID normal_id = gizmos_manager.get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP); - ImTextureID hover_id = gizmos_manager.get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP_HOVER); + auto& gizmos_manager = canvas.get_gizmos_manager(); + ImTextureID normal_id = gizmos_manager.get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP); + ImTextureID hover_id = gizmos_manager.get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP_HOVER); - float scale = canvas.get_scale(); + float scale = canvas.get_scale(); #ifdef WIN32 - int dpi = get_dpi_for_window(wxGetApp().GetTopWindow()); - scale *= (float) dpi / (float) DPI_DEFAULT; + int dpi = get_dpi_for_window(wxGetApp().GetTopWindow()); + scale *= (float)dpi / (float)DPI_DEFAULT; #endif - ImVec2 button_size = ImVec2(25 * scale, 25 * scale); + ImVec2 button_size = ImVec2(25 * scale, 25 * scale); - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {0, 0}); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, { 0, 0 }); - ImGui::ImageButton3(normal_id, hover_id, button_size); + ImGui::ImageButton3(normal_id, hover_id, button_size); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip2(ImVec2(x, caption_y)); - for (const auto& item : shortcuts) { - imgui_wrapper->text_colored(ImGuiWrapper::COL_ACTIVE, item.first + ": "); - ImGui::SameLine(caption_x_max); - imgui_wrapper->text_colored(ImGuiWrapper::COL_WINDOW_BG, item.second); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip2(ImVec2(x, caption_y)); + for (const auto& item : shortcuts) { + imgui_wrapper->text_colored(ImGuiWrapper::COL_ACTIVE, item.first + ": "); + ImGui::SameLine(caption_x_max); + imgui_wrapper->text_colored(ImGuiWrapper::COL_WINDOW_BG, item.second); + } + ImGui::EndTooltip(); } - ImGui::EndTooltip(); + ImGui::PopStyleVar(2); + } + + void begin_right_aligned_buttons(const std::vector& labels) + { + float total_width = 0.0f; + ImGuiStyle& style = ImGui::GetStyle(); + float spacing = style.ItemSpacing.x; + float padding = style.FramePadding.x * 2.0f; + + // Calculate width + for (size_t i = 0; i < labels.size(); ++i) { + total_width += ImGuiWrapper::calc_text_size(labels[i]).x + padding; + if (i < labels.size() - 1) + total_width += spacing; + } + + float avail = ImGui::GetContentRegionAvail().x; + + // Handle Overlap: If the total width of the buttons exceeds available space, move to a new line + if (total_width > avail) { + ImGui::NewLine(); + avail = ImGui::GetContentRegionAvail().x; // Reset to full window width + } + + float posX = ImGui::GetCursorPosX() + std::max(0.0f, avail - total_width); + ImGui::SetCursorPosX(posX); + } + + void push_orca_button_style() + { + ImVec4 base_orca = ImGuiWrapper::COL_ORCA; + + float h, s, v; + ImGui::ColorConvertRGBtoHSV(base_orca.x, base_orca.y, base_orca.z, h, s, v); + + ImVec4 hover, active; + + // Lighter variant for Hover (Increase Value by ~12%) + ImGui::ColorConvertHSVtoRGB(h, s, std::min(v + 0.12f, 1.0f), hover.x, hover.y, hover.z); + hover.w = base_orca.w; + + // Darker variant for Active (Decrease Value by ~12%) + ImGui::ColorConvertHSVtoRGB(h, s, std::max(v - 0.12f, 0.0f), active.x, active.y, active.z); + active.w = base_orca.w; + + ImGui::PushStyleColor(ImGuiCol_Button, base_orca); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, hover); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, active); + + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_WINDOW_BG); + + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); + } + + void pop_orca_button_style() + { + ImGui::PopStyleVar(1); + ImGui::PopStyleColor(4); } - ImGui::PopStyleVar(2); -} } // namespace Slic3r::GUI::GLGizmoUtils \ No newline at end of file diff --git a/src/slic3r/GUI/Gizmos/GLGizmoUtils.hpp b/src/slic3r/GUI/Gizmos/GLGizmoUtils.hpp index acdff4ddfe9..1eed68b2cdb 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoUtils.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoUtils.hpp @@ -15,9 +15,16 @@ class GLCanvas3D; namespace GLGizmoUtils { -// Renders a tooltip button using the provided shortcuts -void render_tooltip_button( - ImGuiWrapper* imgui_wrapper, const GLCanvas3D& canvas, const std::vector>& shortcuts, float x, float y); + // Renders a tooltip button using the provided shortcuts + void render_tooltip_button( + ImGuiWrapper* imgui_wrapper, const GLCanvas3D& canvas, const std::vector>& shortcuts, float x, float y); + + // Sets up ImGui to render buttons that are right-aligned within the current window, using the provided labels to calculate spacing. + void begin_right_aligned_buttons(const std::vector& labels); + + void push_orca_button_style(); + + void pop_orca_button_style(); } // namespace GLGizmoUtils } // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.cpp b/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.cpp index c40bc7d0ad1..5eb4f44280b 100644 --- a/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.cpp +++ b/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.cpp @@ -62,16 +62,21 @@ GizmoObjectManipulation::GizmoObjectManipulation(GLCanvas3D& glcanvas) m_shortcuts_move = { {alt + _L("Left mouse button"), _L("Part selection")}, - {shift + _L("Left mouse button"), _L("Fixed step drag")} + {shift + _L("Left mouse button"), _L("Fixed step drag")}, + {_L("Context Menu"), _L("Toggle Auto-Drop")} }; m_shortcuts_rotate = { - {alt + _L("Left mouse button"), _L("Part selection")}}; + {alt + _L("Left mouse button"), _L("Part selection")}, + {_L("Context Menu"), _L("Toggle Auto-Drop")} + }; m_shortcuts_scale = { {alt + _L("Left mouse button"), _L("Part selection")}, {shift + _L("Left mouse button"), _L("Fixed step drag")}, - {ctrl + _L("Left mouse button"), _L("Single sided scaling")}}; + {ctrl + _L("Left mouse button"), _L("Single sided scaling")}, + {_L("Context Menu"), _L("Toggle Auto-Drop")} + }; } void GizmoObjectManipulation::UpdateAndShow(const bool show) @@ -860,13 +865,25 @@ void GizmoObjectManipulation::do_render_move_window(ImGuiWrapper *imgui_wrapper, } } if (!focued_on_text) m_glcanvas.handle_sidebar_focus_event("", false); - + + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); + float f_scale = m_glcanvas.get_gizmos_manager().get_layout_scale(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); + GLGizmoUtils::render_tooltip_button(imgui_wrapper, m_glcanvas, m_shortcuts_move, x, y); + ImGui::SameLine(); + GLGizmoUtils::begin_right_aligned_buttons({ _L("Done") }); + if (imgui_wrapper->button(_L("Done"))) { + m_glcanvas.reset_all_gizmos(); + } + m_last_active_item = current_active_id; last_move_input_window_width = ImGui::GetWindowWidth(); imgui_wrapper->end(); - ImGui::PopStyleVar(1); + ImGui::PopStyleVar(2); ImGuiWrapper::pop_toolbar_style(); } @@ -1052,14 +1069,26 @@ void GizmoObjectManipulation::do_render_rotate_window(ImGuiWrapper *imgui_wrappe if (!focued_on_text && !absolute_focued_on_text) m_glcanvas.handle_sidebar_focus_event("", false); + ImGui::Spacing(); // needed after Text + ImGui::Separator(); + ImGui::Spacing(); + float f_scale = m_glcanvas.get_gizmos_manager().get_layout_scale(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); + GLGizmoUtils::render_tooltip_button(imgui_wrapper, m_glcanvas, m_shortcuts_rotate, x, y); + + ImGui::SameLine(); + GLGizmoUtils::begin_right_aligned_buttons({ _L("Done") }); + if (imgui_wrapper->button(_L("Done"))) { + m_glcanvas.reset_all_gizmos(); + } m_last_active_item = current_active_id; last_rotate_input_window_width = ImGui::GetWindowWidth(); imgui_wrapper->end(); // BBS - ImGui::PopStyleVar(1); + ImGui::PopStyleVar(2); ImGuiWrapper::pop_toolbar_style(); } @@ -1214,7 +1243,7 @@ void GizmoObjectManipulation::do_render_scale_input_window(ImGuiWrapper* imgui_w if (display_size.x() > 0 && display_size.y() > 0 && display_size.z() > 0) { m_buffered_size = display_size; } - ImGui::Separator(); + ImGui::AlignTextToFramePadding(); bool is_avoid_one_update{false}; if (combox_changed) { @@ -1286,15 +1315,26 @@ void GizmoObjectManipulation::do_render_scale_input_window(ImGuiWrapper* imgui_w } if (!focued_on_text) m_glcanvas.handle_sidebar_focus_event("", false); + + ImGui::Separator(); + float f_scale = m_glcanvas.get_gizmos_manager().get_layout_scale(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); GLGizmoUtils::render_tooltip_button(imgui_wrapper, m_glcanvas, m_shortcuts_scale, x, y); + ImGui::SameLine(); + GLGizmoUtils::begin_right_aligned_buttons({ _L("Done") }); + if (imgui_wrapper->button(_L("Done"))) { + m_glcanvas.reset_all_gizmos(); + } + m_last_active_item = current_active_id; last_scale_input_window_width = ImGui::GetWindowWidth(); imgui_wrapper->end(); //BBS + ImGui::PopStyleVar(1); ImGuiWrapper::pop_toolbar_style(); } diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 85f59d31e5d..7743ff8e005 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -185,6 +185,7 @@ const ImVec4 ImGuiWrapper::COL_TOOLBAR_BG = { 250 / 255.f, 250 / 255.f, 2 const ImVec4 ImGuiWrapper::COL_TOOLBAR_BG_DARK = { 57 / 255.f, 60 / 255.f, 66 / 255.f, 1.f }; // ORCA color matches with toolbar_background_dark.png const ImVec4 ImGuiWrapper::COL_ORCA = to_ImVec4(ColorRGBA::ORCA()); const ImVec4 ImGuiWrapper::COL_MODIFIED = { 253.f / 255.f, 111.f / 255.f, 40.f / 255.f, 1}; // ORCA same color with m_color_label_modified +const ImVec4 ImGuiWrapper::COL_WARNING = to_ImVec4(ColorRGB::WARNING()); int ImGuiWrapper::TOOLBAR_WINDOW_FLAGS = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove @@ -871,8 +872,8 @@ bool ImGuiWrapper::button(const wxString &label, const wxString& tooltip) const bool ret = ImGui::Button(label_utf8.c_str()); if (!tooltip.IsEmpty() && ImGui::IsItemHovered()) { - auto tooltip_utf8 = into_u8(tooltip); - ImGui::SetTooltip(tooltip_utf8.c_str(), nullptr); + const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; + this->tooltip(tooltip, max_tooltip_width); } return ret; @@ -884,8 +885,8 @@ bool ImGuiWrapper::bbl_button(const wxString &label, const wxString& tooltip) const bool ret = ImGui::BBLButton(label_utf8.c_str()); if (!tooltip.IsEmpty() && ImGui::IsItemHovered()) { - auto tooltip_utf8 = into_u8(tooltip); - ImGui::SetTooltip(tooltip_utf8.c_str(), nullptr); + const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; + this->tooltip(tooltip, max_tooltip_width); } return ret; @@ -1055,7 +1056,7 @@ void ImGuiWrapper::text(const wxString &label) void ImGuiWrapper::warning_text(const char *label) { - ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::to_ImVec4(ColorRGB::WARNING())); + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_WARNING); this->text(label); ImGui::PopStyleColor(); } diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index a2b8476c716..8855a38405a 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -349,6 +349,7 @@ public: static const ImVec4 COL_SEPARATOR_DARK; static const ImVec4 COL_ORCA; static const ImVec4 COL_MODIFIED; + static const ImVec4 COL_WARNING; //BBS static void on_change_color_mode(bool is_dark); From 892e46e2af4b89ce627da65bb039cb47043ce4c1 Mon Sep 17 00:00:00 2001 From: yw4z Date: Mon, 18 May 2026 08:59:22 +0300 Subject: [PATCH 3/3] Show filament edit button or action menu dynamically depends on single / multi materilal usage (#12651) * init * fix build error * add guards * Update Plater.cpp --- src/slic3r/GUI/GUI_Factories.cpp | 25 +++++----- src/slic3r/GUI/Plater.cpp | 79 ++++++++++++++++++-------------- src/slic3r/GUI/Plater.hpp | 2 +- src/slic3r/GUI/Tab.cpp | 6 +-- 4 files changed, 61 insertions(+), 51 deletions(-) diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 8aa227ebdcd..2a47fe1aad7 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -1644,17 +1644,6 @@ void MenuFactory::create_filament_action_menu(bool init, int active_filament_men []() { return true; }, m_parent); } - if (init) { - append_menu_item( - menu, wxID_ANY, _L("Delete"), _L("Delete this filament"), [](wxCommandEvent&) { - plater()->sidebar().delete_filament(-2); }, "", nullptr, - []() { - return plater()->sidebar().combos_filament().size() > 1 - // Orca: only show delete filament option for SEMM machines unless is BBL - && Sidebar::should_show_SEMM_buttons(); - }, m_parent); - } - const int item_id = menu->FindItem(_L("Merge with")); if (item_id != wxNOT_FOUND) menu->Destroy(item_id); @@ -1675,6 +1664,20 @@ void MenuFactory::create_filament_action_menu(bool init, int active_filament_men } append_submenu(menu, sub_menu, wxID_ANY, _L("Merge with"), "", "", [filaments_cnt]() { return filaments_cnt > 1; }, m_parent); + + // ORCA use delete item on end of menu to prevent accidental clicks. clicking to submenus(merge) already not allowed by OS + const int delete_id = menu->FindItem(_L("Delete")); + if (delete_id != wxNOT_FOUND) + menu->Destroy(delete_id); + + append_menu_item( + menu, wxID_ANY, _L("Delete"), _L("Delete this filament"), [](wxCommandEvent&) { + plater()->sidebar().delete_filament(-2); }, "", nullptr, + []() { + return plater()->sidebar().combos_filament().size() > 1 + // Orca: only show delete filament option for SEMM machines unless is BBL + && Sidebar::should_show_SEMM_buttons(); + }, m_parent); } //BBS: add part plate related logic diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 48d8c6c8c1f..c3f62f90d47 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2403,13 +2403,23 @@ void Sidebar::init_filament_combo(PlaterPresetComboBox **combo, const int filame edit_btn->SetToolTip(_L("Click to edit preset")); PlaterPresetComboBox* combobox = (*combo); - edit_btn->Bind(wxEVT_BUTTON, [this, edit_btn, filament_idx](wxCommandEvent) { - auto menu = p->plater->filament_action_menu(filament_idx); - wxPoint pt { 0, edit_btn->GetSize().GetHeight() + 10 }; - pt = edit_btn->ClientToScreen(pt); - pt = wxGetApp().mainframe->ScreenToClient(pt); - p->m_menu_filament_id = filament_idx; - p->plater->PopupMenu(menu, (int) pt.x, pt.y); + edit_btn->Bind(wxEVT_BUTTON, [this, edit_btn, combobox, filament_idx](wxCommandEvent) { + bool single_or_bbl = should_show_SEMM_buttons(); + bool is_multi_material = p->combos_filament.size() > 1; + if(single_or_bbl && is_multi_material) { + // MULTI MATERIAL Show menu + auto menu = p->plater->filament_action_menu(filament_idx); + wxPoint pt { 0, edit_btn->GetSize().GetHeight() + FromDIP(2) }; + pt = edit_btn->ClientToScreen(pt); + pt = wxGetApp().mainframe->ScreenToClient(pt); + p->m_menu_filament_id = filament_idx; + p->plater->PopupMenu(menu, (int) pt.x, pt.y); + } + else { + // SINGLE MATERIAL / MULTI EXTRUDER / TOOLCHANGER / IDEX Opens Dialog directly + p->editing_filament = filament_idx; + combobox->switch_to_tab(); + } }); combobox->edit_btn = edit_btn; @@ -2510,7 +2520,7 @@ void Sidebar::update_all_preset_comboboxes() p->m_filament_icon->SetBitmap_("filament"); } - show_SEMM_buttons(should_show_SEMM_buttons()); + show_SEMM_buttons(); //p->m_staticText_filament_settings->Update(); @@ -3112,16 +3122,7 @@ void Sidebar::on_filament_count_change(size_t num_filaments) // remove unused choices if any remove_unused_filament_combos(num_filaments); - auto sizer = p->m_panel_filament_title->GetSizer(); - if (p->m_flushing_volume_btn != nullptr && sizer != nullptr) { - if (num_filaments > 1) { - sizer->Show(p->m_flushing_volume_btn); - sizer->Show(p->m_bpButton_del_filament); // ORCA: Show delete filament button if multiple filaments - } else { - sizer->Hide(p->m_flushing_volume_btn); - sizer->Hide(p->m_bpButton_del_filament); // ORCA: Hide delete filament button if there is only one filament - } - } + show_SEMM_buttons(); // ORCA update_filaments_area_height(); // ORCA @@ -3168,16 +3169,7 @@ void Sidebar::on_filaments_delete(size_t filament_id) } } - auto sizer = p->m_panel_filament_title->GetSizer(); - if (p->m_flushing_volume_btn != nullptr && sizer != nullptr) { - if (p->combos_filament.size() > 1) { - sizer->Show(p->m_flushing_volume_btn); - sizer->Show(p->m_bpButton_del_filament); // ORCA: Show delete filament button if multiple filaments - } else { - sizer->Hide(p->m_flushing_volume_btn); - sizer->Hide(p->m_bpButton_del_filament); // ORCA: Hide delete filament button if there is only one filament - } - } + show_SEMM_buttons(); // ORCA for (size_t idx = filament_id ; idx < p->combos_filament.size(); ++idx) { p->combos_filament[idx]->update(); @@ -3736,14 +3728,31 @@ bool Sidebar::should_show_SEMM_buttons() return cfg.opt_bool("single_extruder_multi_material") || is_bbl_vendor; } -void Sidebar::show_SEMM_buttons(bool bshow) +void Sidebar::show_SEMM_buttons() { - if(p->m_bpButton_add_filament) - p->m_bpButton_add_filament->Show(bshow); - if (p->m_bpButton_del_filament && p->combos_filament.size() > 1) // ORCA add filament count as condition to prevent showing Flushing volumes and Del Filament icon visible while only 1 filament exist - p->m_bpButton_del_filament->Show(bshow); - if (p->m_flushing_volume_btn && p->combos_filament.size() > 1) // ORCA add filament count as condition to prevent showing Flushing volumes and Del Filament icon visible while only 1 filament exist - p->m_flushing_volume_btn->Show(bshow); + // ORCA + if (!p || p->combos_filament.empty() || !p->m_bpButton_add_filament || !p->m_bpButton_del_filament || !p->m_flushing_volume_btn) + return; + + bool is_multi_material = p->combos_filament.size() > 1; + bool single_or_bbl = should_show_SEMM_buttons(); + bool is_single = single_or_bbl && !is_multi_material; // SINGLE EXTRUDER / BBL WITH 1 MATERIAL + bool is_multi = single_or_bbl && is_multi_material; // MULTI MATERIAL WITH SINGLE EXTRUDER + bool is_fixed = !is_single && !is_multi; // MULTI EXTRUDER / TOOLCHANGER / IDEX WITH FIXED MATERIAL + + p->m_bpButton_add_filament->Show(single_or_bbl); + p->m_bpButton_del_filament->Show(is_multi); + p->m_flushing_volume_btn->Show( is_multi); + + if (is_multi) { + for (auto &c : p->combos_filament) + c->edit_btn->SetBitmap_("menu_filament"); + } + else if (is_single || is_fixed) { + for (auto &c : p->combos_filament) + c->edit_btn->SetBitmap_("edit"); + } + Layout(); } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 31e0e2f0e1c..f2089c1c4fa 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -203,7 +203,7 @@ public: void get_small_btn_sync_pos_size(wxPoint &pt, wxSize &size); // Orca static bool should_show_SEMM_buttons(); - void show_SEMM_buttons(bool bshow); + void show_SEMM_buttons(); void update_dynamic_filament_list(); PlaterPresetComboBox * printer_combox(); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 9e58d7f9f56..ccf595b22ab 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1554,8 +1554,7 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) if (opt_key == "single_extruder_multi_material" ){ - const auto bSEMM = m_config->opt_bool("single_extruder_multi_material"); - wxGetApp().sidebar().show_SEMM_buttons(bSEMM); + wxGetApp().sidebar().show_SEMM_buttons(); wxGetApp().get_tab(Preset::TYPE_PRINT)->update(); } @@ -1606,8 +1605,7 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) if (opt_key == "single_extruder_multi_material" ){ - const auto bSEMM = m_config->opt_bool("single_extruder_multi_material"); - wxGetApp().sidebar().show_SEMM_buttons(bSEMM); + wxGetApp().sidebar().show_SEMM_buttons(); wxGetApp().get_tab(Preset::TYPE_PRINT)->update(); }