diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index dd13fd5353..0af1f2f2e1 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -174,6 +174,8 @@ set(SLIC3R_GUI_SOURCES GUI/Gizmos/GLGizmoAssembly.hpp GUI/Gizmos/GLGizmoBase.cpp GUI/Gizmos/GLGizmoBase.hpp + GUI/Gizmos/GLGizmoAlignment.cpp + GUI/Gizmos/GLGizmoAlignment.hpp GUI/Gizmos/GLGizmoBrimEars.cpp GUI/Gizmos/GLGizmoBrimEars.hpp GUI/Gizmos/GLGizmoCut.cpp diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index de838f5a2b..3ac4fc2f78 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -5520,7 +5520,7 @@ void GLCanvas3D::set_tooltip(const std::string& tooltip) m_tooltip.set_text(tooltip); } -void GLCanvas3D::do_move(const std::string& snapshot_type) +void GLCanvas3D::do_move(const std::string& snapshot_type, bool force_volume_move) { if (m_model == nullptr) return; @@ -5552,10 +5552,18 @@ void GLCanvas3D::do_move(const std::string& snapshot_type) // Move instances/volumes ModelObject* model_object = m_model->objects[object_idx]; - if (model_object == nullptr) + if (model_object == nullptr) continue; - if (selection_mode == Selection::Instance) { + if (force_volume_move || selection_mode == Selection::Volume) { + auto cur_mv = model_object->volumes[volume_idx]; + if (cur_mv->get_transformation() != v->get_volume_transformation()) { + cur_mv->set_transformation(v->get_volume_transformation()); + // BBS: backup + Slic3r::save_object_mesh(*model_object); + } + } + else if (selection_mode == Selection::Instance) { if (m_canvas_type == GLCanvas3D::ECanvasType::CanvasAssembleView) { if ((model_object->instances[instance_idx]->get_assemble_offset() - v->get_instance_offset()).norm() > 1e-2) { model_object->instances[instance_idx]->set_assemble_transformation(v->get_instance_transformation()); @@ -5564,14 +5572,6 @@ void GLCanvas3D::do_move(const std::string& snapshot_type) model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); } } - else if (selection_mode == Selection::Volume) { - auto cur_mv = model_object->volumes[volume_idx]; - if (cur_mv->get_transformation() != v->get_volume_transformation()) { - cur_mv->set_transformation(v->get_volume_transformation()); - // BBS: backup - Slic3r::save_object_mesh(*model_object); - } - } object_moved = true; model_object->invalidate_bounding_box(); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 2cb6bcea4f..df265bb080 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -1305,7 +1305,7 @@ public: void set_tooltip(const std::string& tooltip); // the following methods add a snapshot to the undo/redo stack, unless the given string is empty - void do_move(const std::string& snapshot_type); + void do_move(const std::string& snapshot_type, bool force_volume_move = false); void do_rotate(const std::string& snapshot_type); void do_scale(const std::string& snapshot_type); void do_center(); diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index f4508f47ac..ca487ec3a5 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -16,6 +16,7 @@ #include "Selection.hpp" //BBS: add partplate related logic #include "PartPlate.hpp" +#include "Gizmos/GLGizmoAlignment.hpp" #include "Gizmos/GLGizmoEmboss.hpp" #include "Gizmos/GLGizmoSVG.hpp" @@ -1672,6 +1673,8 @@ void MenuFactory::create_extra_object_menu() append_menu_item_merge_parts_to_single_part(&m_object_menu); // Object Center append_menu_item_center(&m_object_menu); + // Object Align/Distribute + append_menu_item_align_distribute(&m_object_menu); // Object Drop append_menu_item_drop(&m_object_menu); // Object Split @@ -1779,6 +1782,7 @@ void MenuFactory::create_text_part_menu() append_menu_item_fix_through_cgal(menu); append_menu_item_simplify(menu); append_menu_item_center(menu); + append_menu_item_align_distribute(menu); append_menu_items_mirror(menu); menu->AppendSeparator(); append_menu_item_per_object_process(menu); @@ -1811,6 +1815,7 @@ void MenuFactory::create_bbl_part_menu() append_menu_item_simplify(menu); append_menu_item_smooth_mesh(menu); append_menu_item_center(menu); + append_menu_item_align_distribute(menu); append_menu_item_drop(menu); append_menu_items_mirror(menu); wxMenu* split_menu = new wxMenu(); @@ -2136,6 +2141,7 @@ wxMenu* MenuFactory::multi_selection_menu() index++; } append_menu_item_center(menu); + append_menu_item_align_distribute(menu); append_menu_item_drop(menu); append_menu_item_fix_through_cgal(menu); //append_menu_item_simplify(menu); @@ -2144,13 +2150,13 @@ wxMenu* MenuFactory::multi_selection_menu() append_menu_item_set_printable(menu); menu->AppendSeparator(); - + append_menu_item_set_auto_drop(menu); menu->AppendSeparator(); - + append_menu_item_per_object_process(menu); menu->AppendSeparator(); - + append_menu_items_convert_unit(menu); append_menu_item_replace_all_with_stl(menu); //BBS @@ -2161,6 +2167,7 @@ wxMenu* MenuFactory::multi_selection_menu() } else { append_menu_item_center(menu); + append_menu_item_align_distribute(menu); append_menu_item_drop(menu); append_menu_item_fix_through_cgal(menu); //append_menu_item_simplify(menu); @@ -2325,6 +2332,247 @@ void MenuFactory::append_menu_item_center(wxMenu* menu) }, m_parent); } +void MenuFactory::append_menu_item_align_distribute(wxMenu *menu) +{ + wxMenu* align_distribute_menu = new wxMenu(); + if (!align_distribute_menu) + return; + + append_menu_item( + align_distribute_menu, wxID_ANY, _L("Distribute left-right") + " (X)", "", + [](wxCommandEvent &) { + auto canvas3d = plater()->get_view3D_canvas3D(); + canvas3d->get_gizmos_manager().check_object_located_outside_plate(true); + plater()->distribute_selection_x(); + }, + "", nullptr, + []() { + if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D) + return false; + auto canvas3d = plater()->get_view3D_canvas3D(); + GLGizmoAlignment alignment_helper(*canvas3d); + return alignment_helper.can_distribute(GLGizmoAlignment::AlignType::DISTRIBUTE_X); + }, + m_parent); + + append_menu_item( + align_distribute_menu, wxID_ANY, _L("Distribute front-back") + " (Y)", "", + [](wxCommandEvent &) { + auto canvas3d = plater()->get_view3D_canvas3D(); + canvas3d->get_gizmos_manager().check_object_located_outside_plate(true); + plater()->distribute_selection_y(); + }, + "", nullptr, + []() { + if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D) + return false; + auto canvas3d = plater()->get_view3D_canvas3D(); + GLGizmoAlignment alignment_helper(*canvas3d); + return alignment_helper.can_distribute(GLGizmoAlignment::AlignType::DISTRIBUTE_Y); + }, + m_parent); + + append_menu_item( + align_distribute_menu, wxID_ANY, _L("Distribute top-bottom") + " (Z)", "", + [](wxCommandEvent &) { + auto canvas3d = plater()->get_view3D_canvas3D(); + canvas3d->get_gizmos_manager().check_object_located_outside_plate(true); + plater()->distribute_selection_z(); + }, + "", nullptr, + []() { + if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D) + return false; + auto canvas3d = plater()->get_view3D_canvas3D(); + GLGizmoAlignment alignment_helper(*canvas3d); + return alignment_helper.can_distribute(GLGizmoAlignment::AlignType::DISTRIBUTE_Z); + }, + m_parent); + + align_distribute_menu->AppendSeparator(); + + append_menu_item( + align_distribute_menu, wxID_ANY, _L("Align left") + " (-X)", "", + [](wxCommandEvent &) { + auto canvas3d = plater()->get_view3D_canvas3D(); + canvas3d->get_gizmos_manager().check_object_located_outside_plate(true); + plater()->align_selection_x_min(); + }, + "", nullptr, + []() { + if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D) + return false; + auto canvas3d = plater()->get_view3D_canvas3D(); + GLGizmoAlignment alignment_helper(*canvas3d); + return alignment_helper.can_align(GLGizmoAlignment::AlignType::X_MIN); + }, + m_parent); + + append_menu_item( + align_distribute_menu, wxID_ANY, _L("Align left-right center") + " (X)", "", + [](wxCommandEvent &) { + auto canvas3d = plater()->get_view3D_canvas3D(); + canvas3d->get_gizmos_manager().check_object_located_outside_plate(true); + plater()->align_selection_x_center(); + }, + "", nullptr, + []() { + if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D) + return false; + auto canvas3d = plater()->get_view3D_canvas3D(); + GLGizmoAlignment alignment_helper(*canvas3d); + return alignment_helper.can_align(GLGizmoAlignment::AlignType::CENTER_X); + }, + m_parent); + + append_menu_item( + align_distribute_menu, wxID_ANY, _L("Align right") + " (+X)", "", + [](wxCommandEvent &) { + auto canvas3d = plater()->get_view3D_canvas3D(); + canvas3d->get_gizmos_manager().check_object_located_outside_plate(true); + plater()->align_selection_x_max(); + }, + "", nullptr, + []() { + if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D) + return false; + auto canvas3d = plater()->get_view3D_canvas3D(); + GLGizmoAlignment alignment_helper(*canvas3d); + return alignment_helper.can_align(GLGizmoAlignment::AlignType::X_MAX); + }, + m_parent); + + align_distribute_menu->AppendSeparator(); + + append_menu_item( + align_distribute_menu, wxID_ANY, _L("Align front") + " (-Y)", "", + [](wxCommandEvent &) { + auto canvas3d = plater()->get_view3D_canvas3D(); + canvas3d->get_gizmos_manager().check_object_located_outside_plate(true); + plater()->align_selection_y_min(); + }, + "", nullptr, + []() { + if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D) + return false; + auto canvas3d = plater()->get_view3D_canvas3D(); + GLGizmoAlignment alignment_helper(*canvas3d); + return alignment_helper.can_align(GLGizmoAlignment::AlignType::Y_MIN); + }, + m_parent); + + append_menu_item( + align_distribute_menu, wxID_ANY, _L("Align front-back center") + " (Y)", "", + [](wxCommandEvent &) { + auto canvas3d = plater()->get_view3D_canvas3D(); + canvas3d->get_gizmos_manager().check_object_located_outside_plate(true); + plater()->align_selection_y_center(); + }, + "", nullptr, + []() { + if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D) + return false; + auto canvas3d = plater()->get_view3D_canvas3D(); + GLGizmoAlignment alignment_helper(*canvas3d); + return alignment_helper.can_align(GLGizmoAlignment::AlignType::CENTER_Y); + }, + m_parent); + + append_menu_item( + align_distribute_menu, wxID_ANY, _L("Align back") + " (+Y)", "", + [](wxCommandEvent &) { + auto canvas3d = plater()->get_view3D_canvas3D(); + canvas3d->get_gizmos_manager().check_object_located_outside_plate(true); + plater()->align_selection_y_max(); + }, + "", nullptr, + []() { + if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D) + return false; + auto canvas3d = plater()->get_view3D_canvas3D(); + GLGizmoAlignment alignment_helper(*canvas3d); + return alignment_helper.can_align(GLGizmoAlignment::AlignType::Y_MAX); + }, + m_parent); + + align_distribute_menu->AppendSeparator(); + + append_menu_item( + align_distribute_menu, wxID_ANY, _L("Align bottom") + " (-Z)", "", + [](wxCommandEvent &) { + auto canvas3d = plater()->get_view3D_canvas3D(); + canvas3d->get_gizmos_manager().check_object_located_outside_plate(true); + plater()->align_selection_z_min(); + }, + "", nullptr, + []() { + if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D) + return false; + auto canvas3d = plater()->get_view3D_canvas3D(); + GLGizmoAlignment alignment_helper(*canvas3d); + return alignment_helper.can_align(GLGizmoAlignment::AlignType::Z_MIN); + }, + m_parent); + + append_menu_item( + align_distribute_menu, wxID_ANY, _L("Align top-bottom center") + " (Z)", "", + [](wxCommandEvent &) { + auto canvas3d = plater()->get_view3D_canvas3D(); + canvas3d->get_gizmos_manager().check_object_located_outside_plate(true); + plater()->align_selection_z_center(); + }, + "", nullptr, + []() { + if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D) + return false; + auto canvas3d = plater()->get_view3D_canvas3D(); + GLGizmoAlignment alignment_helper(*canvas3d); + return alignment_helper.can_align(GLGizmoAlignment::AlignType::CENTER_Z); + }, + m_parent); + + append_menu_item( + align_distribute_menu, wxID_ANY, _L("Align top") + " (+Z)", "", + [](wxCommandEvent &) { + auto canvas3d = plater()->get_view3D_canvas3D(); + canvas3d->get_gizmos_manager().check_object_located_outside_plate(true); + plater()->align_selection_z_max(); + }, + "", nullptr, + []() { + if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D) + return false; + auto canvas3d = plater()->get_view3D_canvas3D(); + GLGizmoAlignment alignment_helper(*canvas3d); + return alignment_helper.can_align(GLGizmoAlignment::AlignType::Z_MAX); + }, + m_parent); + + append_submenu(menu, align_distribute_menu, wxID_ANY, _L("Align/Distribute"), _L("Align and distribute objects"), "", + []() { + if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D) + return false; + auto canvas3d = plater()->get_view3D_canvas3D(); + GLGizmoAlignment alignment_helper(*canvas3d); + + bool has_any_align = alignment_helper.can_align(GLGizmoAlignment::AlignType::CENTER_X) || + alignment_helper.can_align(GLGizmoAlignment::AlignType::CENTER_Y) || + alignment_helper.can_align(GLGizmoAlignment::AlignType::CENTER_Z) || + alignment_helper.can_align(GLGizmoAlignment::AlignType::X_MAX) || + alignment_helper.can_align(GLGizmoAlignment::AlignType::X_MIN) || + alignment_helper.can_align(GLGizmoAlignment::AlignType::Y_MAX) || + alignment_helper.can_align(GLGizmoAlignment::AlignType::Y_MIN) || + alignment_helper.can_align(GLGizmoAlignment::AlignType::Z_MAX) || + alignment_helper.can_align(GLGizmoAlignment::AlignType::Z_MIN); + + bool has_any_distribute = alignment_helper.can_distribute(GLGizmoAlignment::AlignType::DISTRIBUTE_X) || + alignment_helper.can_distribute(GLGizmoAlignment::AlignType::DISTRIBUTE_Y) || + alignment_helper.can_distribute(GLGizmoAlignment::AlignType::DISTRIBUTE_Z); + + return has_any_align || has_any_distribute; + }, m_parent); +} + void MenuFactory::append_menu_item_drop(wxMenu* menu) { append_menu_item(menu, wxID_ANY, _L("Drop") , "", diff --git a/src/slic3r/GUI/GUI_Factories.hpp b/src/slic3r/GUI/GUI_Factories.hpp index 4202cce5bc..65526c5c04 100644 --- a/src/slic3r/GUI/GUI_Factories.hpp +++ b/src/slic3r/GUI/GUI_Factories.hpp @@ -175,6 +175,7 @@ private: void append_menu_item_merge_to_single_object(wxMenu* menu); void append_menu_item_merge_parts_to_single_part(wxMenu *menu); void append_menu_items_mirror(wxMenu *menu); + void append_menu_item_align_distribute(wxMenu *menu); void append_menu_item_precise_seam_submenu(wxMenu* menu); void append_menu_item_invalidate_cut_info(wxMenu *menu); void append_menu_item_edit_text(wxMenu *menu); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoAlignment.cpp b/src/slic3r/GUI/Gizmos/GLGizmoAlignment.cpp new file mode 100644 index 0000000000..7e97d64940 --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoAlignment.cpp @@ -0,0 +1,694 @@ +#include "GLGizmoAlignment.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/Plater.hpp" +#include "libslic3r/Model.hpp" + +namespace Slic3r { +namespace GUI { + +GLGizmoAlignment::GLGizmoAlignment(GLCanvas3D& canvas) : m_canvas(canvas) +{ +} + +bool GLGizmoAlignment::align_objects(AlignType type, bool align_parent) +{ + if (!validate_selection_for_align()) { + return false; + } + + switch (type) { + case AlignType::CENTER_X: return align_to_center(type, align_parent); + case AlignType::CENTER_Y: return align_to_center(type, align_parent); + case AlignType::CENTER_Z: return align_to_center(type, align_parent); + case AlignType::Y_MAX: return align_to_y_max(align_parent); + case AlignType::Y_MIN: return align_to_y_min(align_parent); + case AlignType::X_MAX: return align_to_x_max(align_parent); + case AlignType::X_MIN: return align_to_x_min(align_parent); + case AlignType::Z_MAX: return align_to_z_max(align_parent); + case AlignType::Z_MIN: return align_to_z_min(align_parent); + default: + return false; + } +} + +bool GLGizmoAlignment::distribute_objects(AlignType type) +{ + if (!can_distribute(type)) { + return false; + } + + switch (type) { + case AlignType::DISTRIBUTE_X: + return distribute_x(); + case AlignType::DISTRIBUTE_Y: + return distribute_y(); + case AlignType::DISTRIBUTE_Z: + return distribute_z(); + default: + return false; + } +} + +bool GLGizmoAlignment::align_to_y_max(bool align_parent) +{ + return align_objects_generic( + [](const ObjectInfo& obj) { return obj.bbox.max.y(); }, + [](Vec3d& displacement, double target, double current_max) { + displacement.y() = target - current_max; + }, "Align Y Max", align_parent + ); +} + +bool GLGizmoAlignment::align_to_y_min(bool align_parent) +{ + return align_objects_generic( + [](const ObjectInfo& obj) { return obj.bbox.min.y(); }, + [](Vec3d& displacement, double target, double current_min) { + displacement.y() = target - current_min; + }, "Align Y Min", align_parent + ); +} + +bool GLGizmoAlignment::align_to_x_max(bool align_parent) +{ + return align_objects_generic( + [](const ObjectInfo& obj) { return obj.bbox.max.x(); }, + [](Vec3d& displacement, double target, double current_max) { + displacement.x() = target - current_max; + }, "Align X Max", align_parent + ); +} + +bool GLGizmoAlignment::align_to_x_min(bool align_parent) +{ + return align_objects_generic( + [](const ObjectInfo& obj) { return obj.bbox.min.x(); }, + [](Vec3d& displacement, double target, double current_min) { + displacement.x() = target - current_min; + }, "Align X Min", align_parent + ); +} + +bool GLGizmoAlignment::align_to_center(AlignType type,bool align_parent) +{ + const Selection& selection = get_selection(); + std::string type_str = type == AlignType::CENTER_X ? "X" : (type == AlignType::CENTER_Y ? "Y" : "Z"); + std::string operation_name = type == AlignType::CENTER_X ? "X Center" : (type == AlignType::CENTER_Y ? "Y Center" : "Z Center"); + double selection_center = 0.f; + if (align_parent) { + selection_center = get_current_coord(operation_name, m_parent_box); + + // Handle multiple volume selection + if (is_part_align_parent()) { + if (!take_snapshot("Align to parent center " + type_str)) { + return false; + } + + get_selection().setup_cache(); + + auto selection_bbox = selection.get_bounding_box(); + double current_coord = get_current_coord(operation_name, selection_bbox); + Vec3d displacement = generate_displacement(operation_name, selection_center - current_coord); + // Move each selected volume to align to the center of their collective bounding box + for (unsigned int idx : selection.get_volume_idxs()) { + const GLVolume *volume = selection.get_volume(idx); + if (volume) { + get_selection().translate(volume->object_idx(), volume->instance_idx(), volume->volume_idx(), displacement); + } + } + finish_operation("Align to parent center " + type_str); + return true; + } + BoundingBoxf3 big_bb; + auto objects = get_selected_objects_info(big_bb); + if (objects.empty()) return false; + // Handle multiple objects or parts selection + + BoundingBoxf3 selection_bbox = selection.get_bounding_box(); + if (!take_snapshot("Align to parent center " + type_str)) { + return false; + } + + get_selection().setup_cache(); + double current_coord = get_current_coord(operation_name, big_bb); + Vec3d displacement = generate_displacement(operation_name, selection_center - current_coord); + // Multiple objects: use selection bounding box for alignment + for (const auto &obj : objects) { + apply_transformation(obj.object_idx, obj.instance_idx, displacement); + } + + finish_operation("Align to parent center " + type_str); + return true; + } + // Handle single object selection// Handle multiple volume selection + if (selection.is_single_full_object() || + selection.is_multiple_volume() || selection.is_multiple_modifier() || selection.is_single_volume() || selection.is_single_modifier()) { + if (!take_snapshot("Align Parts Center " + type_str)){ + return false; + } + + get_selection().setup_cache(); + + BoundingBoxf3 selection_bbox = selection.get_bounding_box(); + selection_center = get_current_coord(operation_name, selection_bbox); + + // Move each selected volume to align to the center of their collective bounding box + for (unsigned int idx : selection.get_volume_idxs()) { + const GLVolume* volume = selection.get_volume(idx); + if (volume) { + auto temp_box = volume->transformed_convex_hull_bounding_box(); + double current = get_current_coord(operation_name, temp_box); + double offset = selection_center - current; + Vec3d displacement = generate_displacement(operation_name, offset); + get_selection().translate(volume->object_idx(), volume->instance_idx(), volume->volume_idx(), displacement); + } + } + + finish_operation("Align Parts Center" + type_str, true); + return true; + } + BoundingBoxf3 big_bb; + auto objects = get_selected_objects_info(big_bb); + if (objects.empty()) return false; + // Handle multiple objects or parts selection + if (selection.is_multiple_full_object()) { + BoundingBoxf3 selection_bbox = selection.get_bounding_box(); + double selection_center = get_current_coord(operation_name, selection_bbox); + + if (!take_snapshot("Align Objects Center" + type_str)) { + return false; + } + + get_selection().setup_cache(); + // Multiple objects: use selection bounding box for alignment + for (const auto& obj : objects) { + BoundingBoxf3 temp_bbox = obj.bbox; + double current_coord = get_current_coord(operation_name, temp_bbox); + Vec3d displacement = generate_displacement(operation_name, selection_center - current_coord); + apply_transformation(obj.object_idx, obj.instance_idx, displacement); + } + finish_operation("Align Objects Center" + type_str); + return true; + } + return false; +} + +bool GLGizmoAlignment::align_to_z_max(bool align_parent) +{ + return align_objects_generic( + [](const ObjectInfo& obj) { return obj.bbox.max.z(); }, + [](Vec3d& displacement, double target, double current_max) { + displacement.z() = target - current_max; + }, "Align Z Max", align_parent + ); +} + +bool GLGizmoAlignment::align_to_z_min(bool align_parent) +{ + return align_objects_generic( + [](const ObjectInfo& obj) { return obj.bbox.min.z(); }, + [](Vec3d& displacement, double target, double current_min) { + displacement.z() = target - current_min; + }, "Align Z Min", align_parent + ); +} + +bool GLGizmoAlignment::distribute_y() +{ + return distribute_objects_generic( + [](const ObjectInfo& obj) { return obj.center.y(); }, + 1, + "Distribute Y" + ); +} + +bool GLGizmoAlignment::distribute_x() +{ + return distribute_objects_generic( + [](const ObjectInfo& obj) { return obj.center.x(); }, + 0, + "Distribute X" + ); +} + +bool GLGizmoAlignment::distribute_z() +{ + return distribute_objects_generic( + [](const ObjectInfo& obj) { return obj.center.z(); }, + 2, + "Distribute Z" + ); +} + +template +bool GLGizmoAlignment::align_objects_generic(GetCoordFunc get_coord, SetCoordFunc set_coord, const std::string &operation_name ,bool align_parent) +{ + const Selection& selection = get_selection(); + if (align_parent) { + // Find reference coordinate + double reference_coord = get_current_coord(operation_name, m_parent_box); + // Handle parts selection differently + if (is_part_align_parent()) { + if (!take_snapshot("align to parent node " + operation_name)) { + return false; + } + + get_selection().setup_cache(); + + // Get all selected volumes + std::vector volumes; + for (unsigned int idx : selection.get_volume_idxs()) { + const GLVolume *volume = selection.get_volume(idx); + if (volume) { volumes.push_back(volume); } + } + + if (volumes.empty()) + return false; + BoundingBoxf3 selection_bbox = selection.get_bounding_box(); + double current_coord = get_current_coord(operation_name, selection_bbox); + Vec3d displacement = generate_displacement(operation_name, reference_coord - current_coord); + for (const GLVolume *volume : volumes) { + if (displacement.norm() > 1e-6) { + get_selection().translate(volume->object_idx(), volume->instance_idx(), volume->volume_idx(), displacement); + } + } + + finish_operation("align to parent node " + operation_name); + return true; + } + + // Handle objects selection (original logic) + BoundingBoxf3 big_bb; + auto objects = get_selected_objects_info(big_bb); + if (objects.empty()) return false; + + if (!take_snapshot("align to parent node " + operation_name)) { return false; } + + get_selection().setup_cache(); + + double current_coord = get_current_coord(operation_name, big_bb); + Vec3d displacement = generate_displacement(operation_name, reference_coord - current_coord); + + for (const auto &obj : objects) { + if (displacement.norm() > 1e-6) { + apply_transformation(obj.object_idx, obj.instance_idx, displacement); + } + } + + finish_operation("align to parent node " + operation_name); + return true; + } + // Handle parts selection differently + if (selection.is_single_full_object() || selection.is_multiple_volume() || selection.is_multiple_modifier()) { + if (!take_snapshot(operation_name)) { + return false; + } + + get_selection().setup_cache(); + + // Get all selected volumes + std::vector volumes; + for (unsigned int idx : selection.get_volume_idxs()) { + const GLVolume* volume = selection.get_volume(idx); + if (volume) { + volumes.push_back(volume); + } + } + + if (volumes.empty()) return false; + + // Find reference coordinate + double reference_coord = 0.0; + bool first = true; + + for (const GLVolume* volume : volumes) { + BoundingBoxf3 bbox = volume->transformed_convex_hull_bounding_box(); + double coord = 0.0; + + if (operation_name.find("X Max") != std::string::npos) { + coord = bbox.max.x(); + } else if (operation_name.find("X Min") != std::string::npos) { + coord = bbox.min.x(); + } else if (operation_name.find("Y Max") != std::string::npos) { + coord = bbox.max.y(); + } else if (operation_name.find("Y Min") != std::string::npos) { + coord = bbox.min.y(); + } else if (operation_name.find("Z Max") != std::string::npos) { + coord = bbox.max.z(); + } else if (operation_name.find("Z Min") != std::string::npos) { + coord = bbox.min.z(); + } + + if (first) { + reference_coord = coord; + first = false; + } else { + if (operation_name.find("Max") != std::string::npos) { + reference_coord = std::max(reference_coord, coord); + } else if (operation_name.find("Min") != std::string::npos) { + reference_coord = std::min(reference_coord, coord); + } + } + } + + for (const GLVolume* volume : volumes) { + BoundingBoxf3 bbox = volume->transformed_convex_hull_bounding_box(); + double current_coord = 0.0; + + if (operation_name.find("X Max") != std::string::npos) { + current_coord = bbox.max.x(); + } else if (operation_name.find("X Min") != std::string::npos) { + current_coord = bbox.min.x(); + } else if (operation_name.find("Y Max") != std::string::npos) { + current_coord = bbox.max.y(); + } else if (operation_name.find("Y Min") != std::string::npos) { + current_coord = bbox.min.y(); + } else if (operation_name.find("Z Max") != std::string::npos) { + current_coord = bbox.max.z(); + } else if (operation_name.find("Z Min") != std::string::npos) { + current_coord = bbox.min.z(); + } + + Vec3d displacement = Vec3d::Zero(); + + if (operation_name.find("X") != std::string::npos) { + displacement.x() = reference_coord - current_coord; + } else if (operation_name.find("Y") != std::string::npos) { + displacement.y() = reference_coord - current_coord; + } else if (operation_name.find("Z") != std::string::npos) { + displacement.z() = reference_coord - current_coord; + } + + if (displacement.norm() > 1e-6) { + get_selection().translate(volume->object_idx(), volume->instance_idx(), volume->volume_idx(), displacement); + } + } + + finish_operation(operation_name,true); + return true; + } + + // Handle objects selection (original logic) + BoundingBoxf3 big_bb; + auto objects = get_selected_objects_info(big_bb); + if (objects.empty()) return false; + + if (!take_snapshot(operation_name)) { + return false; + } + + get_selection().setup_cache(); + + double reference_coord = get_coord(objects[0]); + for (const auto& obj : objects) { + double coord = get_coord(obj); + if (operation_name.find("Max") != std::string::npos) { + reference_coord = std::max(reference_coord, coord); + } else if (operation_name.find("Min") != std::string::npos) { + reference_coord = std::min(reference_coord, coord); + } + } + + for (const auto& obj : objects) { + double current_coord = get_coord(obj); + Vec3d displacement = Vec3d::Zero(); + set_coord(displacement, reference_coord, current_coord); + + if (displacement.norm() > 1e-6) { + apply_transformation(obj.object_idx, obj.instance_idx, displacement); + } + } + + finish_operation(operation_name); + return true; +} + +template +bool GLGizmoAlignment::distribute_objects_generic(GetCoordFunc get_coord, int axis, const std::string& operation_name) +{ + const Selection& selection = get_selection(); + + if (selection.is_single_full_object() || selection.is_multiple_volume() || selection.is_multiple_modifier()) { + std::vector volumes; + for (unsigned int idx : selection.get_volume_idxs()) { + const GLVolume* v = selection.get_volume(idx); + if (v) volumes.push_back(v); + } + + if (volumes.size() < 3) return false; + if (!take_snapshot(operation_name)) { + return false; + } + get_selection().setup_cache(); + + struct VolEntry { const GLVolume* v; double coord; }; + std::vector entries; + entries.reserve(volumes.size()); + for (const GLVolume* v : volumes) { + BoundingBoxf3 bbox = v->transformed_convex_hull_bounding_box(); + Vec3d c = bbox.center(); + double coord = (axis == 0 ? c.x() : axis == 1 ? c.y() : c.z()); + entries.push_back({v, coord}); + } + + std::sort(entries.begin(), entries.end(), [](const VolEntry& a, const VolEntry& b){ return a.coord < b.coord; }); + + BoundingBoxf3 selection_bbox = selection.get_bounding_box(); + BoundingBoxf3 left_bbox = entries.front().v->transformed_convex_hull_bounding_box(); + BoundingBoxf3 right_bbox = entries.back().v->transformed_convex_hull_bounding_box(); + auto axis_extent = [&](const BoundingBoxf3 &bb) { return (axis == 0 ? (bb.max.x() - bb.min.x()) : axis == 1 ? (bb.max.y() - bb.min.y()) : (bb.max.z() - bb.min.z())); }; + double left_width = axis_extent(left_bbox); + double right_width = axis_extent(right_bbox); + double bbox_min = (axis == 0 ? selection_bbox.min.x() : axis == 1 ? selection_bbox.min.y() : selection_bbox.min.z()); + double bbox_max = (axis == 0 ? selection_bbox.max.x() : axis == 1 ? selection_bbox.max.y() : selection_bbox.max.z()); + double min_center = bbox_min + left_width * 0.5; + double max_center = bbox_max - right_width * 0.5; + double total_distance = max_center - min_center; + double interval = (entries.size() > 1) ? total_distance / (entries.size() - 1) : 0; + for (size_t i = 1; i < entries.size() - 1; ++i) { + double target_coord = min_center + i * interval; + double current_coord = entries[i].coord; + double offset = target_coord - current_coord; + + if (std::abs(offset) > 1e-6) { + Vec3d displacement = Vec3d::Zero(); + displacement[axis] = offset; + const GLVolume *v = entries[i].v; + get_selection().translate(v->object_idx(), v->instance_idx(), v->volume_idx(), displacement); + } + } + + finish_operation(operation_name,true); + return true; + } + BoundingBoxf3 big_bb; + auto objects = get_selected_objects_info(big_bb); + if (objects.size() < 3) return false; + + if (!take_snapshot(operation_name)) { + return false; + } + + get_selection().setup_cache(); + + std::sort(objects.begin(), objects.end(), + [&get_coord](const ObjectInfo& a, const ObjectInfo& b) { + return get_coord(a) < get_coord(b); + }); + + double min_coord = get_coord(objects.front()); + double max_coord = get_coord(objects.back()); + double total_distance = max_coord - min_coord; + double interval = (objects.size() > 1) ? total_distance / (objects.size() - 1) : 0; + for (size_t i = 1; i < objects.size() - 1; ++i) { + double target_coord = min_coord + i * interval; + double current_coord = get_coord(objects[i]); + double offset = target_coord - current_coord; + + if (std::abs(offset) > 1e-6) { + Vec3d displacement = Vec3d::Zero(); + displacement[axis] = offset; + apply_transformation(objects[i].object_idx, objects[i].instance_idx, displacement); + } + } + + finish_operation(operation_name); + return true; +} + +bool GLGizmoAlignment::can_align(AlignType type) const +{ + const Selection& selection = get_selection(); + + bool is_single_object = selection.is_single_full_object(); + bool is_multiple_objects = selection.is_multiple_full_object(); + bool is_single_part = selection.is_single_volume() || selection.is_single_modifier(); + bool is_multiple_parts = selection.is_multiple_volume() || selection.is_multiple_modifier(); + + + return validate_selection_for_align(); +} + +bool GLGizmoAlignment::can_distribute(AlignType type) const +{ + const Selection& selection = get_selection(); + if (selection.is_single_full_object()) { + size_t count = selection.get_volume_idxs().size(); + if (count >= 3) + return true; + } + if (selection.is_single_volume() || + selection.is_single_modifier()) { + return false; + } + + if (selection.is_multiple_volume() || selection.is_multiple_modifier()) { + size_t count = selection.get_volume_idxs().size(); + if (count < 3) return false; + return (type == AlignType::DISTRIBUTE_X || + type == AlignType::DISTRIBUTE_Y || + type == AlignType::DISTRIBUTE_Z); + } + + if (selection.is_multiple_full_object()) { + BoundingBoxf3 big_bb; + auto objects = get_selected_objects_info(big_bb); + if (objects.size() < 3) return false; + return (type == AlignType::DISTRIBUTE_X || type == AlignType::DISTRIBUTE_Y || type == AlignType::DISTRIBUTE_Z); + } + + return validate_selection_for_distribute(); +} + +double GLGizmoAlignment::get_current_coord(std::string operation_name, BoundingBoxf3 &bbox) +{ + double current_coord = 0.0; + if (operation_name.find("X Max") != std::string::npos) { + current_coord = bbox.max.x(); + } else if (operation_name.find("X Min") != std::string::npos) { + current_coord = bbox.min.x(); + } else if (operation_name.find("Y Max") != std::string::npos) { + current_coord = bbox.max.y(); + } else if (operation_name.find("Y Min") != std::string::npos) { + current_coord = bbox.min.y(); + } else if (operation_name.find("Z Max") != std::string::npos) { + current_coord = bbox.max.z(); + } else if (operation_name.find("Z Min") != std::string::npos) { + current_coord = bbox.min.z(); + } else if (operation_name.find("X Center") != std::string::npos) { + current_coord = bbox.center().x(); + } else if (operation_name.find("Y Center") != std::string::npos) { + current_coord = bbox.center().y(); + } else if (operation_name.find("Z Center") != std::string::npos) { + current_coord = bbox.center().z(); + } + return current_coord; +} + +Vec3d GLGizmoAlignment::generate_displacement(std::string operation_name, double offset) { + Vec3d displacement = Vec3d::Zero(); + if (operation_name.find("X") != std::string::npos) { + displacement.x() = offset; + } else if (operation_name.find("Y") != std::string::npos) { + displacement.y() = offset; + } else if (operation_name.find("Z") != std::string::npos) { + displacement.z() = offset; + } + return displacement; +} + +bool GLGizmoAlignment::is_part_align_parent() +{ + Selection &selection = get_selection(); + if (selection.is_multiple_volume() || selection.is_multiple_modifier() || selection.is_single_volume() || selection.is_single_modifier()) { + return true; + } + return false; +} + +std::vector GLGizmoAlignment::get_selected_objects_info(BoundingBoxf3 &big_bb) const +{ + std::vector objects; + Selection& selection = get_selection(); + + if (selection.is_empty()) return objects; + + const auto& content = selection.get_content(); + for (const auto& obj_it : content) { + for (const auto& inst_idx : obj_it.second) { + int obj_idx = obj_it.first; + + if (obj_idx >= 0 && obj_idx < selection.get_model()->objects.size()) { + ModelObject* object = selection.get_model()->objects[obj_idx]; + if (inst_idx >= 0 && inst_idx < object->instances.size()) { + BoundingBoxf3 bbox = object->instance_bounding_box(inst_idx); + big_bb.merge(bbox); + objects.emplace_back(obj_idx, inst_idx, bbox); + } + } + } + } + + return objects; +} + +void GLGizmoAlignment::set_parent_box(const BoundingBoxf3 &bb) { + m_parent_box = bb; +} + +Selection& GLGizmoAlignment::get_selection() const +{ + return m_canvas.get_selection(); +} + +bool GLGizmoAlignment::take_snapshot(const std::string& name) +{ + wxGetApp().plater()->take_snapshot(name); + return true; +} + +void GLGizmoAlignment::apply_transformation(int obj_idx, int inst_idx, const Vec3d& displacement) +{ + get_selection().translate(obj_idx, inst_idx, displacement); +} + +void GLGizmoAlignment::finish_operation(const std::string &operation_name, bool force_volume_move) +{ + get_selection().notify_instance_update(-1, -1); + m_canvas.do_move(operation_name, force_volume_move); +} + +bool GLGizmoAlignment::validate_selection_for_align() const +{ + const Selection& selection = get_selection(); + + if (selection.is_single_full_object() || + selection.is_single_volume() || + selection.is_single_modifier()) { + return true; + } + + return selection.get_volume_idxs().size() >= 2; +} + +bool GLGizmoAlignment::validate_selection_for_distribute() const +{ + const Selection& selection = get_selection(); + + if (selection.is_single_full_object() || selection.is_multiple_full_object()) { + std::set object_indices; + for (int volume_idx : selection.get_volume_idxs()) { + const GLVolume* volume = selection.get_volume(volume_idx); + if (volume) { + object_indices.insert(volume->object_idx()); + } + } + return object_indices.size() >= 3; + } + + return selection.get_volume_idxs().size() >= 3; +} + +} // namespace GUI +} // namespace Slic3r \ No newline at end of file diff --git a/src/slic3r/GUI/Gizmos/GLGizmoAlignment.hpp b/src/slic3r/GUI/Gizmos/GLGizmoAlignment.hpp new file mode 100644 index 0000000000..5bbeceb7a6 --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoAlignment.hpp @@ -0,0 +1,95 @@ +#ifndef slic3r_GLGizmoAlignment_hpp_ +#define slic3r_GLGizmoAlignment_hpp_ + +#include "libslic3r/Point.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "slic3r/GUI/Selection.hpp" +#include +#include + +namespace Slic3r { +namespace GUI { + +class GLCanvas3D; + +class GLGizmoAlignment +{ +public: + enum class AlignType { + NONE = -1, + CENTER_X, + CENTER_Y, + CENTER_Z, + Y_MAX, + Y_MIN, + X_MAX, + X_MIN, + Z_MAX, + Z_MIN, + DISTRIBUTE_X, + DISTRIBUTE_Y, + DISTRIBUTE_Z + }; + struct ObjectInfo { + int object_idx; + int instance_idx; + BoundingBoxf3 bbox; + Vec3d center; + + ObjectInfo(int obj_idx, int inst_idx, const BoundingBoxf3& bb) + : object_idx(obj_idx), instance_idx(inst_idx), bbox(bb), center(bb.center()) {} + }; + + explicit GLGizmoAlignment(GLCanvas3D& canvas); + ~GLGizmoAlignment() = default; + + bool align_objects(AlignType type,bool align_parent = false); + bool distribute_objects(AlignType type); + + bool align_to_center(AlignType type,bool align_parent = false); + bool align_to_y_max(bool align_parent = false); + bool align_to_y_min(bool align_parent = false); + bool align_to_x_max(bool align_parent = false); + bool align_to_x_min(bool align_parent = false); + bool align_to_z_max(bool align_parent = false); + bool align_to_z_min(bool align_parent = false); + + bool distribute_x(); + bool distribute_y(); + bool distribute_z(); + + bool can_align(AlignType type) const; + bool can_distribute(AlignType type) const; + + std::vector get_selected_objects_info(BoundingBoxf3 &big_bb) const; + double get_current_coord(std::string operation_name, BoundingBoxf3 &bbox); + Vec3d generate_displacement(std::string operation_name, double offset); + bool is_part_align_parent(); + void set_parent_box(const BoundingBoxf3 &bb); + +private: + GLCanvas3D& m_canvas; + + template + bool align_objects_generic(GetCoordFunc get_coord, SetCoordFunc set_coord, const std::string &operation_name, bool align_parent = false); + + template + bool distribute_objects_generic(GetCoordFunc get_coord, int axis, + const std::string& operation_name); + + Selection& get_selection() const; + bool take_snapshot(const std::string& name); + void apply_transformation(int obj_idx, int inst_idx, const Vec3d& displacement); + void finish_operation(const std::string &operation_name, bool force_volume_move = false); + + bool validate_selection_for_align() const; + bool validate_selection_for_distribute() const; + +private: + BoundingBoxf3 m_parent_box; +}; + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GLGizmoAlignment_hpp_ \ No newline at end of file diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 41d4811664..a5a3122b85 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -470,6 +470,37 @@ bool GLGizmosManager::check_gizmos_closed_except(EType type) const return true; } +// Switches the active plate to the one containing the first selected object, +// so that subsequent operations (align/distribute) act on a plate the user +// can actually see. +void GLGizmosManager::check_object_located_outside_plate(bool change_plate) +{ + PartPlateList& plate_list = wxGetApp().plater()->get_partplate_list(); + auto curr_plate_index = plate_list.get_curr_plate_index(); + Selection& selection = m_parent.get_selection(); + auto idxs = selection.get_volume_idxs(); + if (idxs.empty()) + return; + + const GLVolume* v = selection.get_volume(*idxs.begin()); + int object_idx = v->object_idx(); + const Model* model = m_parent.get_model(); + if (object_idx < 0 || object_idx >= (int) model->objects.size()) + return; + + ModelObject* model_object = model->objects[object_idx]; + for (size_t i = 0; i < plate_list.get_plate_count(); ++i) { + auto plate = plate_list.get_plate(i); + for (auto object : plate->get_objects_on_this_plate()) { + if (model_object == object) { + if (change_plate && curr_plate_index != (int) i) + plate_list.select_plate(i); + return; + } + } + } +} + void GLGizmosManager::set_hover_id(int id) { // Measure and assembly handles hover by itself diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index 0914f8d958..cba59de9f1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -261,6 +261,7 @@ public: void reset_all_states(); bool open_gizmo(EType type); bool check_gizmos_closed_except(EType) const; + void check_object_located_outside_plate(bool change_plate = true); void set_hover_id(int id); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index cf9dddeee7..da828527f2 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -245,6 +245,7 @@ #include "Widgets/MultiNozzleSync.hpp" // NozzleOption, tryPopUpMultiNozzleDialog, setExtruderNozzleCount #include "DeviceCore/DevNozzleSystem.h" // DevNozzle, GetExtNozzles / GetRackNozzles #include "ProjectDirtyStateManager.hpp" +#include "Gizmos/GLGizmoAlignment.hpp" #include "Gizmos/GLGizmoSimplify.hpp" // create suggestion notification #include "Gizmos/GLGizmoSVG.hpp" // Drop SVG file #include "Gizmos/GizmoObjectManipulation.hpp" @@ -7484,6 +7485,18 @@ struct Plater::priv void delete_all_objects_from_model(); void reset(bool apply_presets_change = false, bool reload_presets = true); void center_selection(); + void distribute_selection_x(); + void distribute_selection_y(); + void distribute_selection_z(); + void align_selection_x_min(); + void align_selection_x_center(); + void align_selection_x_max(); + void align_selection_y_min(); + void align_selection_y_center(); + void align_selection_y_max(); + void align_selection_z_min(); + void align_selection_z_center(); + void align_selection_z_max(); void drop_selection(); void mirror(Axis axis); void split_object(bool auto_drop = true); @@ -10913,6 +10926,78 @@ void Plater::priv::center_selection() view3D->center_selected(); } +void Plater::priv::distribute_selection_x() +{ + GLGizmoAlignment alignment_helper(*view3D->get_canvas3d()); + alignment_helper.distribute_objects(GLGizmoAlignment::AlignType::DISTRIBUTE_X); +} + +void Plater::priv::distribute_selection_y() +{ + GLGizmoAlignment alignment_helper(*view3D->get_canvas3d()); + alignment_helper.distribute_objects(GLGizmoAlignment::AlignType::DISTRIBUTE_Y); +} + +void Plater::priv::distribute_selection_z() +{ + GLGizmoAlignment alignment_helper(*view3D->get_canvas3d()); + alignment_helper.distribute_objects(GLGizmoAlignment::AlignType::DISTRIBUTE_Z); +} + +void Plater::priv::align_selection_x_min() +{ + GLGizmoAlignment alignment_helper(*view3D->get_canvas3d()); + alignment_helper.align_objects(GLGizmoAlignment::AlignType::X_MIN); +} + +void Plater::priv::align_selection_x_center() +{ + GLGizmoAlignment alignment_helper(*view3D->get_canvas3d()); + alignment_helper.align_objects(GLGizmoAlignment::AlignType::CENTER_X); +} + +void Plater::priv::align_selection_x_max() +{ + GLGizmoAlignment alignment_helper(*view3D->get_canvas3d()); + alignment_helper.align_objects(GLGizmoAlignment::AlignType::X_MAX); +} + +void Plater::priv::align_selection_y_min() +{ + GLGizmoAlignment alignment_helper(*view3D->get_canvas3d()); + alignment_helper.align_objects(GLGizmoAlignment::AlignType::Y_MIN); +} + +void Plater::priv::align_selection_y_center() +{ + GLGizmoAlignment alignment_helper(*view3D->get_canvas3d()); + alignment_helper.align_objects(GLGizmoAlignment::AlignType::CENTER_Y); +} + +void Plater::priv::align_selection_y_max() +{ + GLGizmoAlignment alignment_helper(*view3D->get_canvas3d()); + alignment_helper.align_objects(GLGizmoAlignment::AlignType::Y_MAX); +} + +void Plater::priv::align_selection_z_min() +{ + GLGizmoAlignment alignment_helper(*view3D->get_canvas3d()); + alignment_helper.align_objects(GLGizmoAlignment::AlignType::Z_MIN); +} + +void Plater::priv::align_selection_z_center() +{ + GLGizmoAlignment alignment_helper(*view3D->get_canvas3d()); + alignment_helper.align_objects(GLGizmoAlignment::AlignType::CENTER_Z); +} + +void Plater::priv::align_selection_z_max() +{ + GLGizmoAlignment alignment_helper(*view3D->get_canvas3d()); + alignment_helper.align_objects(GLGizmoAlignment::AlignType::Z_MAX); +} + void Plater::priv::drop_selection() { view3D->drop_selected(); @@ -22226,6 +22311,18 @@ void Plater::suppress_background_process(const bool stop_background_process) BackgroundSlicingProcess& Plater::background_process() { return p->background_process; } void Plater::center_selection() { p->center_selection(); } +void Plater::distribute_selection_x() { p->distribute_selection_x(); } +void Plater::distribute_selection_y() { p->distribute_selection_y(); } +void Plater::distribute_selection_z() { p->distribute_selection_z(); } +void Plater::align_selection_x_min() { p->align_selection_x_min(); } +void Plater::align_selection_x_center() { p->align_selection_x_center(); } +void Plater::align_selection_x_max() { p->align_selection_x_max(); } +void Plater::align_selection_y_min() { p->align_selection_y_min(); } +void Plater::align_selection_y_center() { p->align_selection_y_center(); } +void Plater::align_selection_y_max() { p->align_selection_y_max(); } +void Plater::align_selection_z_min() { p->align_selection_z_min(); } +void Plater::align_selection_z_center() { p->align_selection_z_center(); } +void Plater::align_selection_z_max() { p->align_selection_z_max(); } void Plater::drop_selection() { p->drop_selection(); } void Plater::mirror(Axis axis) { p->mirror(axis); } void Plater::split_object(bool auto_drop) { p->split_object(auto_drop); } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 0bb2d65add..a270eea1c3 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -762,6 +762,18 @@ public: //BBS: add clone logic void clone_selection(); void center_selection(); + void distribute_selection_x(); + void distribute_selection_y(); + void distribute_selection_z(); + void align_selection_x_min(); + void align_selection_x_center(); + void align_selection_x_max(); + void align_selection_y_min(); + void align_selection_y_center(); + void align_selection_y_max(); + void align_selection_z_min(); + void align_selection_z_center(); + void align_selection_z_max(); void drop_selection(); void search(bool plater_is_active, Preset::Type type, wxWindow *tag, TextInput *etag, wxWindow *stag); void mirror(Axis axis);