Add align and distribute to the Move gizmo

This commit is contained in:
SoftFever committed 2026-10-14 19:47:03 +08:00
1 parent 48d07a831d
commit 987c57dc15
34 files changed
+443 -14

No files matched your search

+14 -3
View File
@@ -4978,7 +4978,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
// Don't deselect a volume if layer editing is enabled or any gizmo is active. We want the object to stay selected
// during the scene manipulation.
if (m_picking_enabled && (!any_gizmo_active || !evt.CmdDown()) && (!m_hover_volume_idxs.empty())) {
if (m_picking_enabled && (m_gizmos.is_allow_multi_select_parts_or_objects() || !evt.CmdDown()) && (!m_hover_volume_idxs.empty())) {
if (evt.LeftDown() && !m_hover_volume_idxs.empty()) {
int volume_idx = get_first_hover_volume_idx();
bool already_selected = m_selection.contains_volume(volume_idx);
@@ -4988,7 +4988,18 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
if (already_selected && ctrl_down)
m_selection.remove(volume_idx);
else if (alt_down && m_gizmos.is_allow_multi_select_parts_or_objects() && m_selection.get_mode() == Selection::Volume &&
m_selection.get_object_idx() == m_volumes.volumes[volume_idx]->object_idx() &&
m_selection.get_instance_idx() == m_volumes.volumes[volume_idx]->instance_idx()) {
// Alt+click on another part of the selected parts' instance adds it, and on a selected
// part removes it, unless it is the only one.
if (!already_selected)
m_selection.add_volumes(Selection::Volume, {(unsigned int) volume_idx}, false);
else if (curr_idxs.size() > 1)
m_selection.remove_volumes(Selection::Volume, {(unsigned int) volume_idx});
}
else if (alt_down) {
// Select just the clicked part, also when an object is selected.
Selection::EMode mode = Selection::Volume;
if (already_selected) {
std::vector<unsigned int> volume_idxs;
@@ -7963,8 +7974,8 @@ void GLCanvas3D::_picking_pass()
if (0 <= hit.raycaster_id && hit.raycaster_id < (int)m_volumes.volumes.size()) {
const GLVolume* volume = m_volumes.volumes[hit.raycaster_id];
if (volume->is_active && !volume->disabled && (volume->composite_id.volume_id >= 0 || m_render_sla_auxiliaries)) {
// do not add the volume id if any gizmo is active and CTRL is pressed
if (m_gizmos.get_current_type() == GLGizmosManager::EType::Undefined || !wxGetKeyState(WXK_CONTROL))
// do not add the volume id if CTRL is pressed while a gizmo that does not allow Ctrl+click selection is active
if (m_gizmos.is_allow_multi_select_parts_or_objects() || !wxGetKeyState(WXK_CONTROL))
m_hover_volume_idxs.emplace_back(hit.raycaster_id);
m_gizmos.set_hover_id(-1);
}
+18 -7
View File
@@ -2,6 +2,7 @@
#include "slic3r/GUI/3DScene.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/PartPlate.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/Selection.hpp"
@@ -627,8 +628,10 @@ BoundingBoxf3 GLGizmoAlignment::get_parent_box() const
PartPlate *plate = plate_list.get_plate(plate_list.find_instance(obj_idx, *inst_idxs.begin()));
if (plate == nullptr)
break;
BoundingBoxf3 instances_box;
get_selected_objects_info(instances_box);
box = plate->get_plate_box();
box.max.z() = get_items_box().max.z();
box.max.z() = instances_box.max.z();
// Keep the aligned objects off the plate edge.
box.min += Vec3d(0.1, 0.1, 0.1);
box.max -= Vec3d(0.1, 0.1, 0.1);
@@ -663,10 +666,18 @@ void GLGizmoAlignment::finish_operation(bool force_volume_move)
m_canvas.do_move("", force_volume_move);
}
size_t GLGizmoAlignment::get_items_count(AlignType type, bool align_parent) const
size_t GLGizmoAlignment::get_items_count(AlignType type, bool align_parent, std::string *why_not) const
{
if (is_parts_selection(align_parent))
return get_selected_part_volumes().size();
auto cannot_move = [why_not](const std::string &reason) {
if (why_not != nullptr)
*why_not = reason;
return size_t(0);
};
if (is_parts_selection(align_parent)) {
const size_t count = get_selected_part_volumes().size();
return count > 0 ? count : cannot_move(_u8L("Parts of a cut object cannot be moved"));
}
// Instances are moved through their instance offsets, which do_move() commits only from an Instance selection.
const Selection &selection = get_selection();
@@ -677,7 +688,7 @@ size_t GLGizmoAlignment::get_items_count(AlignType type, bool align_parent) cons
type == AlignType::DISTRIBUTE_Z;
// The plate gives no Z target, and aligning to its bottom would duplicate Drop.
if (is_z && align_parent)
return 0;
return cannot_move(_u8L("Aligning to the plate is available in X and Y only"));
PartPlateList &plate_list = wxGetApp().plater()->get_partplate_list();
int plate_idx = -1;
@@ -687,13 +698,13 @@ size_t GLGizmoAlignment::get_items_count(AlignType type, bool align_parent) cons
continue;
// Auto-drop would put a raised object back on the bed and leave a lowered one sunk.
if (is_z && selection.get_model()->objects[obj_idx]->auto_drop)
return 0;
return cannot_move(_u8L("Turn off Auto-Drop for the selected objects to move them in Z"));
if (align_parent) {
// The plate holding the selection is the target, so it must hold all of it.
for (int inst_idx : inst_idxs) {
const int idx = plate_list.find_instance(obj_idx, inst_idx);
if (idx == -1 || (plate_idx != -1 && idx != plate_idx))
return 0;
return cannot_move(_u8L("All selected objects must be on the same plate"));
plate_idx = idx;
}
}
+2 -1
View File
@@ -77,6 +77,8 @@ public:
// World bounding box aligned to with align_parent: the instance of a parts selection, or the plate holding the
// selected instances, up to their top.
BoundingBoxf3 get_parent_box() const;
// Number of items the operation would move, or 0 when it cannot move them; why_not then gets the reason, if any.
size_t get_items_count(AlignType type, bool align_parent, std::string *why_not = nullptr) const;
private:
GLCanvas3D& m_canvas;
@@ -92,7 +94,6 @@ private:
bool take_snapshot(const std::string& name);
void apply_transformation(int obj_idx, int inst_idx, const Vec3d& displacement);
void finish_operation(bool force_volume_move = false);
size_t get_items_count(AlignType type, bool align_parent) const;
};
} // namespace GUI
+96
View File
@@ -31,6 +31,11 @@
#include "slic3r/GUI/Camera.hpp"
#include "slic3r/GUI/GLModel.hpp"
#include "slic3r/GUI/Gizmos/GizmoObjectManipulation.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoAlignment.hpp"
#include "libslic3r/BoundingBox.hpp"
#include <algorithm>
#include <cstddef>
#include <vector>
#include "slic3r/GUI/I18N.hpp"
namespace Slic3r {
@@ -109,6 +114,8 @@ void GLGizmoMove3D::on_set_state() {
m_last_selected_volume_idx = -1;
change_cs_by_selection();
}
if (m_object_manipulation)
m_object_manipulation->reset_align_state();
}
void GLGizmoMove3D::on_start_dragging()
@@ -157,6 +164,8 @@ void GLGizmoMove3D::on_render()
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
glsafe(::glEnable(GL_DEPTH_TEST));
render_align_preview();
const auto &[box, box_trafo] = selection.get_bounding_box_in_current_reference_system();
m_bounding_box = box;
m_center = box_trafo.translation();
@@ -340,6 +349,93 @@ double GLGizmoMove3D::calc_projection(const UpdateData& data) const
return projection;
}
void GLGizmoMove3D::render_align_preview()
{
using AlignType = GLGizmoAlignment::AlignType;
if (m_object_manipulation == nullptr || m_object_manipulation->get_hovered_align_type() == AlignType::NONE)
return;
// The box the hovered operation aligns to, in world coordinates like the operation itself.
const AlignType type = m_object_manipulation->get_hovered_align_type();
const GLGizmoAlignment &helper = m_object_manipulation->get_alignment_helper();
const BoundingBoxf3 box = m_object_manipulation->get_align_to_parent() ? helper.get_parent_box() : helper.get_items_box();
if (!box.defined)
return;
m_parent.get_selection().render_bounding_box(box, {0.0f, 150.0f / 255.0f, 136.0f / 255.0f}, m_parent.get_scale());
GLShaderProgram* shader = wxGetApp().get_shader("flat");
if (shader == nullptr)
return;
if (!m_align_plane.is_initialized()) {
GLModel::Geometry init_data;
init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 };
init_data.reserve_vertices(4);
init_data.reserve_indices(6);
init_data.add_vertex(Vec3f(-0.5f, -0.5f, 0.0f));
init_data.add_vertex(Vec3f(0.5f, -0.5f, 0.0f));
init_data.add_vertex(Vec3f(0.5f, 0.5f, 0.0f));
init_data.add_vertex(Vec3f(-0.5f, 0.5f, 0.0f));
init_data.add_triangle(0, 1, 2);
init_data.add_triangle(0, 2, 3);
m_align_plane.init_from(std::move(init_data));
m_align_plane.set_color({ 1.0f, 1.0f, 1.0f, 0.5f });
}
// A plane across the box at its min, center or max on the hovered axis. Distributing shows up to six evenly
// spaced planes.
int axis = Z;
double t = 0.5;
switch (type) {
case AlignType::X_MIN: axis = X; t = 0.0; break;
case AlignType::X_MAX: axis = X; t = 1.0; break;
case AlignType::CENTER_X:
case AlignType::DISTRIBUTE_X: axis = X; break;
case AlignType::Y_MIN: axis = Y; t = 0.0; break;
case AlignType::Y_MAX: axis = Y; t = 1.0; break;
case AlignType::CENTER_Y:
case AlignType::DISTRIBUTE_Y: axis = Y; break;
case AlignType::Z_MIN: t = 0.0; break;
case AlignType::Z_MAX: t = 1.0; break;
default: break;
}
const Vec3d size = box.size();
std::vector<double> positions;
if (type >= AlignType::DISTRIBUTE_X) {
const size_t count = std::min<size_t>(helper.get_items_count(type, false), 6);
for (size_t i = 1; i <= count; ++i)
positions.push_back(box.min[axis] + i * size[axis] / (count + 1));
} else
positions.push_back(box.min[axis] + t * size[axis]);
const int u = (axis + 1) % 3;
const int v = (axis + 2) % 3;
Transform3d plane_matrix = Transform3d::Identity();
plane_matrix.linear().col(0) = size[u] * Vec3d::Unit(u);
plane_matrix.linear().col(1) = size[v] * Vec3d::Unit(v);
plane_matrix.linear().col(2) = Vec3d::Unit(axis);
Vec3d plane_center = box.center();
glsafe(::glDisable(GL_CULL_FACE));
glsafe(::glEnable(GL_BLEND));
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
// Translucent planes must hide neither each other nor the grabbers drawn after them.
glsafe(::glDepthMask(GL_FALSE));
shader->start_using();
const Camera& camera = wxGetApp().plater()->get_camera();
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
for (double position : positions) {
plane_center[axis] = position;
plane_matrix.translation() = plane_center;
shader->set_uniform("view_model_matrix", camera.get_view_matrix() * plane_matrix);
m_align_plane.render();
}
shader->stop_using();
glsafe(::glDepthMask(GL_TRUE));
glsafe(::glEnable(GL_CULL_FACE));
glsafe(::glDisable(GL_BLEND));
}
void GLGizmoMove3D::change_cs_by_selection() {
int obejct_idx, volume_idx;
ModelVolume *model_volume = m_parent.get_selection().get_selected_single_volume(obejct_idx, volume_idx);
+3
View File
@@ -34,6 +34,8 @@ class GLGizmoMove3D : public GLGizmoBase
Vec3d old_center{ Vec3d::Zero() };
};
std::array<GrabberConnection, 3> m_grabber_connections;
// Unit square previewing the target plane of the hovered align or distribute icon.
GLModel m_align_plane;
//BBS: add size adjust related
GizmoObjectManipulation* m_object_manipulation;
@@ -76,6 +78,7 @@ protected:
private:
double calc_projection(const UpdateData& data) const;
void render_align_preview();
void change_cs_by_selection(); //cs mean Coordinate System
private:
int m_last_selected_obejct_idx, m_last_selected_volume_idx;
+20 -1
View File
@@ -367,7 +367,19 @@ bool GLGizmosManager::init_icon_textures()
{ IC_CANVAS_SECTION, "canvas_section" }, { IC_CANVAS_SECTION_HOVER, "canvas_section_hover" },
{ IC_CANVAS_SECTION_DARK, "canvas_section_dark" }, { IC_CANVAS_SECTION_DARK_HOVER, "canvas_section_dark_hover" },
{ IC_CANVAS_SECTION_ACTIVE, "canvas_section_active" }, { IC_CANVAS_SECTION_ACTIVE_HOVER, "canvas_section_active_hover" },
{ IC_CANVAS_SECTION_ACTIVE_DARK, "canvas_section_active_dark" }, { IC_CANVAS_SECTION_ACTIVE_DARK_HOVER, "canvas_section_active_dark_hover" } }) {
{ IC_CANVAS_SECTION_ACTIVE_DARK, "canvas_section_active_dark" }, { IC_CANVAS_SECTION_ACTIVE_DARK_HOVER, "canvas_section_active_dark_hover" },
{ IC_ALIGN_X_MIN, "align_x_min" }, { IC_ALIGN_X_MIN_DARK, "align_x_min_dark" },
{ IC_ALIGN_X_CENTER, "align_x_center" }, { IC_ALIGN_X_CENTER_DARK, "align_x_center_dark" },
{ IC_ALIGN_X_MAX, "align_x_max" }, { IC_ALIGN_X_MAX_DARK, "align_x_max_dark" },
{ IC_ALIGN_Y_MIN, "align_y_min" }, { IC_ALIGN_Y_MIN_DARK, "align_y_min_dark" },
{ IC_ALIGN_Y_CENTER, "align_y_center" }, { IC_ALIGN_Y_CENTER_DARK, "align_y_center_dark" },
{ IC_ALIGN_Y_MAX, "align_y_max" }, { IC_ALIGN_Y_MAX_DARK, "align_y_max_dark" },
{ IC_ALIGN_Z_MIN, "align_z_min" }, { IC_ALIGN_Z_MIN_DARK, "align_z_min_dark" },
{ IC_ALIGN_Z_CENTER, "align_z_center" }, { IC_ALIGN_Z_CENTER_DARK, "align_z_center_dark" },
{ IC_ALIGN_Z_MAX, "align_z_max" }, { IC_ALIGN_Z_MAX_DARK, "align_z_max_dark" },
{ IC_DISTRIBUTE_X, "distribute_x" }, { IC_DISTRIBUTE_X_DARK, "distribute_x_dark" },
{ IC_DISTRIBUTE_Y, "distribute_y" }, { IC_DISTRIBUTE_Y_DARK, "distribute_y_dark" },
{ IC_DISTRIBUTE_Z, "distribute_z" }, { IC_DISTRIBUTE_Z_DARK, "distribute_z_dark" } }) {
if (!IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/" + name + ".svg", 72, 72, texture_id))
return false;
icon_list.insert(std::make_pair((int) icon, texture_id));
@@ -617,6 +629,13 @@ bool GLGizmosManager::is_allow_select_all() {
return false;
}
// Ctrl+click and Alt+click grow the selection only where that serves the gizmo: the Move gizmo aligns and distributes
// several objects or parts, while Scale uses Ctrl for single sided scaling.
bool GLGizmosManager::is_allow_multi_select_parts_or_objects() const
{
return m_current == Undefined || m_current == EType::Move;
}
std::optional<ClippingPlane> GLGizmosManager::get_clipping_plane() const
{
if (! m_common_gizmos_data || ! m_common_gizmos_data->object_clipper())
+25
View File
@@ -201,6 +201,30 @@ public:
IC_CANVAS_SECTION_ACTIVE_HOVER,
IC_CANVAS_SECTION_ACTIVE_DARK,
IC_CANVAS_SECTION_ACTIVE_DARK_HOVER,
IC_ALIGN_X_MIN,
IC_ALIGN_X_CENTER,
IC_ALIGN_X_MAX,
IC_ALIGN_Y_MIN,
IC_ALIGN_Y_CENTER,
IC_ALIGN_Y_MAX,
IC_ALIGN_Z_MIN,
IC_ALIGN_Z_CENTER,
IC_ALIGN_Z_MAX,
IC_DISTRIBUTE_X,
IC_DISTRIBUTE_Y,
IC_DISTRIBUTE_Z,
IC_ALIGN_X_MIN_DARK,
IC_ALIGN_X_CENTER_DARK,
IC_ALIGN_X_MAX_DARK,
IC_ALIGN_Y_MIN_DARK,
IC_ALIGN_Y_CENTER_DARK,
IC_ALIGN_Y_MAX_DARK,
IC_ALIGN_Z_MIN_DARK,
IC_ALIGN_Z_CENTER_DARK,
IC_ALIGN_Z_MAX_DARK,
IC_DISTRIBUTE_X_DARK,
IC_DISTRIBUTE_Y_DARK,
IC_DISTRIBUTE_Z_DARK,
};
explicit GLGizmosManager(GLCanvas3D& parent);
@@ -301,6 +325,7 @@ public:
bool is_paint_gizmo();
bool is_allow_select_all();
bool is_allow_multi_select_parts_or_objects() const;
// Empty when the open gizmo has no object clipper.
std::optional<ClippingPlane> get_clipping_plane() const;
bool wants_reslice_supports_on_undo() const;
@@ -11,6 +11,7 @@
#include "slic3r/GUI/GUI_ObjectList.hpp"
//#include "I18N.hpp"
#include "GLGizmosManager.hpp"
#include "GLGizmoAlignment.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/Utils/UndoRedo.hpp"
@@ -75,7 +76,7 @@ static double get_volume_min_z(const GLVolume* volume)
}
GizmoObjectManipulation::GizmoObjectManipulation(GLCanvas3D& glcanvas)
: m_glcanvas(glcanvas)
: m_glcanvas(glcanvas), m_alignment_helper(glcanvas)
{
m_imperial_units = wxGetApp().app_config->get("use_inches") == "1";
m_new_unit_string = m_imperial_units ? L_CONTEXT("in", "inches") : L("mm");
@@ -86,6 +87,7 @@ GizmoObjectManipulation::GizmoObjectManipulation(GLCanvas3D& glcanvas)
m_shortcuts_move = {
{alt + _L("Left mouse button"), _L("Part selection")},
{ctrl + _L("Left mouse button"), _L("Select multiple objects")},
{shift + _L("Left mouse button"), _L("Fixed step drag")},
{_L("Context Menu"), _L("Toggle Auto-Drop")}
};
@@ -761,6 +763,12 @@ void GizmoObjectManipulation::set_init_rotation(const Geometry::Transformation &
m_init_rotation = value.get_rotation();
}
void GizmoObjectManipulation::reset_align_state()
{
m_hovered_align_type = GLGizmoAlignment::AlignType::NONE;
m_align_to_parent = !m_alignment_helper.can_align(GLGizmoAlignment::AlignType::X_MIN);
}
void GizmoObjectManipulation::do_render_move_window(ImGuiWrapper *imgui_wrapper, std::string window_name, float x, float y, float bottom_limit)
{
// BBS: GUI refactor: move gizmo to the right
@@ -798,16 +806,26 @@ void GizmoObjectManipulation::do_render_move_window(ImGuiWrapper *imgui_wrapper,
};
float space_size = imgui_wrapper->get_style_scaling() * 8;
// The align section shares the combo width, the caption column and the input columns of the position row.
const bool show_align = m_glcanvas.get_canvas_type() == GLCanvas3D::ECanvasType::CanvasView3D &&
!m_glcanvas.get_selection().is_wipe_tower();
//ORCA
float coord_combo_width = std::max({
imgui_wrapper->calc_text_size(_L("World")).x,
imgui_wrapper->calc_text_size(_L("Object")).x,
imgui_wrapper->calc_text_size(_L("Part")).x
}) + imgui_wrapper->calc_text_size("xxx"sv).x + imgui_wrapper->scaled(3.5f);
});
float label_max = std::max({
imgui_wrapper->calc_text_size(_L("Position")).x,
imgui_wrapper->calc_text_size(_L("Relative")).x
});
if (show_align) {
coord_combo_width = std::max({coord_combo_width, imgui_wrapper->calc_text_size(_L("Align selected")).x,
imgui_wrapper->calc_text_size(_L("Align to object")).x,
imgui_wrapper->calc_text_size(_L("Align to plate")).x});
label_max = std::max(label_max, imgui_wrapper->calc_text_size(_L("Align/Distribute")).x);
}
coord_combo_width += imgui_wrapper->calc_text_size("xxx"sv).x + imgui_wrapper->scaled(3.5f);
float caption_max = std::max(label_max, coord_combo_width - 3 * space_size);
float end_text_size = imgui_wrapper->calc_text_size(this->m_new_unit_string).x;
@@ -821,6 +839,11 @@ void GizmoObjectManipulation::do_render_move_window(ImGuiWrapper *imgui_wrapper,
// Rotation
float unit_size = imgui_wrapper->calc_text_size(MAX_SIZE).x + space_size;
// Four align buttons sit under each axis' input, a space before the next axis, so the inputs widen to hold them.
const float align_icon_size = ImGui::GetFrameHeight() * 1.2f;
const float align_button_size = align_icon_size + 2 * ImGui::GetStyle().FramePadding.x;
if (show_align)
unit_size = std::max(unit_size, 4 * align_button_size);
int index = 1;
int index_unit = 1;
@@ -909,6 +932,114 @@ void GizmoObjectManipulation::do_render_move_window(ImGuiWrapper *imgui_wrapper,
}
if (!focued_on_text) m_glcanvas.handle_sidebar_focus_event("", false);
// Align and distribute along the world axes, within the selection or to the object or plate holding it.
using AlignType = GLGizmoAlignment::AlignType;
const bool old_align_to_parent = m_align_to_parent;
AlignType hovered_align_type = AlignType::NONE;
if (show_align) {
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
// The last mode aligns to the parent. Aligning within the selection needs two items.
std::vector<std::string> align_modes = {_u8L("Align selected"),
m_alignment_helper.is_parts_selection(true) ? _u8L("Align to object") : _u8L("Align to plate")};
if (!m_alignment_helper.can_align(AlignType::X_MIN)) {
align_modes.erase(align_modes.begin());
m_align_to_parent = true;
}
size_t align_mode_idx = m_align_to_parent ? align_modes.size() - 1 : 0;
ImGuiWrapper::push_combo_style(m_glcanvas.get_scale());
// render_combo() takes its ImGui id from the empty label, which the coordinate combo uses too.
ImGui::PushID("align_mode");
if (render_combo(imgui_wrapper, "", align_modes, align_mode_idx, 0, coord_combo_width))
m_align_to_parent = align_mode_idx + 1 == align_modes.size();
ImGui::PopID();
ImGuiWrapper::pop_combo_style();
// The label in the caption column and each axis' buttons from the left edge of its input, both centered
// vertically in the row.
const float row_y = ImGui::GetCursorPosY();
const float button_height = align_icon_size + 2 * ImGui::GetStyle().FramePadding.y;
const float row_height = std::max(ImGui::GetTextLineHeight(), button_height);
ImGui::SetCursorPosY(row_y + (row_height - ImGui::GetTextLineHeight()) * 0.5f);
imgui_wrapper->text(_L("Align/Distribute"));
const bool dark_mode = wxGetApp().dark_mode();
int current_axis = -1;
auto align_icon = [&](int axis, AlignType type, GLGizmosManager::MENU_ICON_NAME icon,
GLGizmosManager::MENU_ICON_NAME dark_icon, const wxString &tip) {
const bool distribute = type >= AlignType::DISTRIBUTE_X;
const bool enabled = m_align_to_parent ? m_alignment_helper.can_align(type, true) :
distribute ? m_alignment_helper.can_distribute(type) :
m_alignment_helper.can_align(type);
if (axis != current_axis) {
ImGui::SameLine(caption_max + (axis + 1) * space_size + axis * unit_size);
if (current_axis < 0)
ImGui::SetCursorPosY(row_y + (row_height - button_height) * 0.5f);
current_axis = axis;
} else
ImGui::SameLine(0, 0);
if (!enabled) {
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3f);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::GetStyle().Colors[ImGuiCol_Button]);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImGui::GetStyle().Colors[ImGuiCol_Button]);
}
ImTextureID texture = m_glcanvas.get_gizmos_manager().get_icon_texture_id(dark_mode ? dark_icon : icon);
if (ImGui::ImageButton3(texture, texture, ImVec2(align_icon_size, align_icon_size)) && enabled) {
if (distribute)
m_alignment_helper.distribute_objects(type);
else
m_alignment_helper.align_objects(type, m_align_to_parent);
}
if (!enabled) {
ImGui::PopStyleColor(2);
ImGui::PopStyleVar();
}
if (ImGui::IsItemHovered()) {
wxString text = tip;
if (enabled)
hovered_align_type = type;
else {
// Too few items is the reason when there are items to move (only distribute needs three);
// otherwise the helper names it.
std::string why_not;
if (m_alignment_helper.get_items_count(type, m_align_to_parent, &why_not) > 0)
why_not = _u8L("Select at least 3 parts or objects");
if (!why_not.empty())
text += "\n" + from_u8(why_not);
}
imgui_wrapper->tooltip(text, imgui_wrapper->calc_text_size(text).x + 3 * space_size);
}
};
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);
align_icon(0, AlignType::X_MIN, GLGizmosManager::IC_ALIGN_X_MIN, GLGizmosManager::IC_ALIGN_X_MIN_DARK, _L("Align left") + " (-X)");
align_icon(0, AlignType::CENTER_X, GLGizmosManager::IC_ALIGN_X_CENTER, GLGizmosManager::IC_ALIGN_X_CENTER_DARK, _L("Align left-right center") + " (X)");
align_icon(0, AlignType::X_MAX, GLGizmosManager::IC_ALIGN_X_MAX, GLGizmosManager::IC_ALIGN_X_MAX_DARK, _L("Align right") + " (+X)");
if (!m_align_to_parent)
align_icon(0, AlignType::DISTRIBUTE_X, GLGizmosManager::IC_DISTRIBUTE_X, GLGizmosManager::IC_DISTRIBUTE_X_DARK, _L("Distribute left-right") + " (X)");
align_icon(1, AlignType::Y_MIN, GLGizmosManager::IC_ALIGN_Y_MIN, GLGizmosManager::IC_ALIGN_Y_MIN_DARK, _L("Align front") + " (-Y)");
align_icon(1, AlignType::CENTER_Y, GLGizmosManager::IC_ALIGN_Y_CENTER, GLGizmosManager::IC_ALIGN_Y_CENTER_DARK, _L("Align front-back center") + " (Y)");
align_icon(1, AlignType::Y_MAX, GLGizmosManager::IC_ALIGN_Y_MAX, GLGizmosManager::IC_ALIGN_Y_MAX_DARK, _L("Align back") + " (+Y)");
if (!m_align_to_parent)
align_icon(1, AlignType::DISTRIBUTE_Y, GLGizmosManager::IC_DISTRIBUTE_Y, GLGizmosManager::IC_DISTRIBUTE_Y_DARK, _L("Distribute front-back") + " (Y)");
align_icon(2, AlignType::Z_MIN, GLGizmosManager::IC_ALIGN_Z_MIN, GLGizmosManager::IC_ALIGN_Z_MIN_DARK, _L("Align bottom") + " (-Z)");
align_icon(2, AlignType::CENTER_Z, GLGizmosManager::IC_ALIGN_Z_CENTER, GLGizmosManager::IC_ALIGN_Z_CENTER_DARK, _L("Align top-bottom center") + " (Z)");
align_icon(2, AlignType::Z_MAX, GLGizmosManager::IC_ALIGN_Z_MAX, GLGizmosManager::IC_ALIGN_Z_MAX_DARK, _L("Align top") + " (+Z)");
if (!m_align_to_parent)
align_icon(2, AlignType::DISTRIBUTE_Z, GLGizmosManager::IC_DISTRIBUTE_Z, GLGizmosManager::IC_DISTRIBUTE_Z_DARK, _L("Distribute top-bottom") + " (Z)");
ImGui::PopStyleVar();
ImGui::SetCursorPosY(row_y + row_height + ImGui::GetStyle().ItemSpacing.y);
}
// The Move gizmo draws the preview with the scene, which an ImGui-only frame does not redraw.
if (hovered_align_type != m_hovered_align_type || m_align_to_parent != old_align_to_parent) {
m_hovered_align_type = hovered_align_type;
m_glcanvas.set_as_dirty();
m_glcanvas.request_extra_frame();
}
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
@@ -13,6 +13,7 @@
#include <wx/string.h>
#include "slic3r/GUI/GUI_Geometry.hpp"
#include "GLGizmoAlignment.hpp"
//#include "slic3r/GUI/GLCanvas3D.hpp"
@@ -151,6 +152,13 @@ public:
void set_init_rotation(const Geometry::Transformation &value);
// Align/distribute state of the Move panel, which the Move gizmo previews.
const GLGizmoAlignment &get_alignment_helper() const { return m_alignment_helper; }
GLGizmoAlignment::AlignType get_hovered_align_type() const { return m_hovered_align_type; }
bool get_align_to_parent() const { return m_align_to_parent; }
// When the Move gizmo opens or closes: no preview, and "Align selected" whenever the selection allows it.
void reset_align_state();
private:
void reset_settings_value();
void update_settings_value(const Selection& selection);
@@ -175,6 +183,12 @@ private:
GLCanvas3D& m_glcanvas;
unsigned int m_last_active_item { 0 };
GLGizmoAlignment m_alignment_helper;
// Operation of the enabled align icon under the mouse.
GLGizmoAlignment::AlignType m_hovered_align_type{GLGizmoAlignment::AlignType::NONE};
// Align to the object or plate holding the selection rather than within the selection.
bool m_align_to_parent{false};
// Contains all shortcuts in the format of {shortcut, description}, e.g. {alt + _L("Left mouse button"), _L("Part_selection")}
std::vector<std::pair<wxString, wxString>> m_shortcuts_move;
// Contains all shortcuts in the format of {shortcut, description}, e.g. {alt + _L("Left mouse button"), _L("Part_selection")}