ENH: Add "Drop" button to move gizmo for objects, parts, and modifiers. And unify all Drop actions / checks (#16323)

* Add Drop to bed button for objects, parts and modifiers to the Position panel

Ported from BambuStudio PR #11511. The button appears under the position
inputs when the selection is not resting on the bed and moves it along
global Z. Works for a single full instance as well as a single part or
modifier, and is undoable.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7uDtamzhNGT6PFwmVdRJ8

* Reuse existing 'Drop to Bed' string so the button is already translated

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7uDtamzhNGT6PFwmVdRJ8

* Add tooltip to the Drop to Bed button

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7uDtamzhNGT6PFwmVdRJ8

* unify function usage across UI

* Update GizmoObjectManipulation.cpp

* show tooltip even its disabled

* update

* Update GizmoObjectManipulation.cpp

* update

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: yw4z <ywsyildiz@gmail.com>
This commit is contained in:
Eldenroot
2026-10-15 03:22:28 +03:00
committed by GitHub
co-authored by Claude Sonnet 5.5 yw4z
parent 70e5b330be
commit 6a7763f08d
6 changed files with 46 additions and 28 deletions
+1 -1
View File
@@ -2400,7 +2400,7 @@ void MenuFactory::append_menu_item_drop(wxMenu* menu)
if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D)
return false;
else {
return (std::abs(plater()->get_view3D_canvas3D()->get_selection().get_bounding_box().min.z()) > -SINKING_Z_THRESHOLD);
return plater()->get_view3D_canvas3D()->get_selection().can_drop();
} //disable if model is on the bed / not in View3D
}, m_parent);
}
@@ -57,24 +57,6 @@ const double GizmoObjectManipulation::mm_to_in = 0.0393700787;
const double GizmoObjectManipulation::oz_to_g = 28.34952;
const double GizmoObjectManipulation::g_to_oz = 0.035274;
// Helper function to be used by drop to bed button. Returns lowest point of this
// volume in world coordinate system.
static double get_volume_min_z(const GLVolume* volume)
{
const Transform3f& world_matrix = volume->world_matrix().cast<float>();
// need to get the ModelVolume pointer
const ModelObject* mo = wxGetApp().model().objects[volume->composite_id.object_id];
const ModelVolume* mv = mo->volumes[volume->composite_id.volume_id];
const TriangleMesh& hull = mv->get_convex_hull();
float min_z = std::numeric_limits<float>::max();
for (const stl_vertex& vert : hull.its.vertices) {
min_z = std::min(min_z, Vec3f::UnitZ().dot(world_matrix * vert));
}
return min_z;
}
GizmoObjectManipulation::GizmoObjectManipulation(GLCanvas3D& glcanvas)
: m_glcanvas(glcanvas), m_alignment_helper(glcanvas)
{
@@ -295,12 +277,14 @@ void GizmoObjectManipulation::update_reset_buttons_visibility()
{
const Selection& selection = m_glcanvas.get_selection();
// Assembly view has its own selection and transformations, which drop_selection() doesn't update
m_enable_drop_to_bed = m_glcanvas.get_canvas_type() == GLCanvas3D::ECanvasType::CanvasView3D && selection.can_drop();
if (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) {
const GLVolume * volume = selection.get_first_volume();
Vec3d rotation;
Vec3d scale;
double min_z = 0.;
if (selection.is_single_full_instance()) {
rotation = volume->get_instance_rotation();
@@ -309,12 +293,10 @@ void GizmoObjectManipulation::update_reset_buttons_visibility()
else {
rotation = volume->get_volume_rotation();
scale = volume->get_volume_scaling_factor();
min_z = get_volume_min_z(volume);
}
m_show_clear_rotation = !rotation.isApprox(m_init_rotation);
m_show_reset_0_rotation = !rotation.isApprox(Vec3d::Zero());
m_show_clear_scale = (m_cache.scale / 100.0f - Vec3d::Ones()).norm() > 0.001;
m_show_drop_to_bed = (std::abs(min_z) > EPSILON);
}
}
@@ -1072,6 +1054,17 @@ void GizmoObjectManipulation::do_render_move_window(ImGuiWrapper *imgui_wrapper,
GLGizmoUtils::render_tooltip_button(imgui_wrapper, m_glcanvas, m_shortcuts_move, x, y);
// ORCA use existing function for drop and show button on bottom bar to keep gizmo compact
imgui_wrapper->disabled_begin(!m_enable_drop_to_bed);
ImGui::SameLine(0, space_size * 1.5f);
if (imgui_wrapper->button(_L("Drop"))){
wxGetApp().plater()->drop_selection();
UpdateAndShow(true);
}
imgui_wrapper->disabled_end();
if(ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
imgui_wrapper->tooltip(_L("Move the selection along Z so that its lowest point touches the bed."), ImGui::GetFontSize() * 20.0f);
ImGui::SameLine();
GLGizmoUtils::begin_right_aligned_buttons({ _L("Done") });
if (imgui_wrapper->button(_L("Done"))) {
@@ -103,7 +103,7 @@ public:
bool m_show_reset_0_rotation{false};
bool m_show_clear_rotation { false };
bool m_show_clear_scale { false };
bool m_show_drop_to_bed { false };
bool m_enable_drop_to_bed { false };
enum class RotateType { None, Relative, Absolute
};
RotateType m_last_rotate_type{RotateType::None}; // 0:no input 1:relative 2:absolute
+1 -1
View File
@@ -365,7 +365,7 @@ std::vector<NativeCommand> build_command_catalog()
return object_op(wxGetApp().plater(), [](Plater* p) { return !p->is_selection_empty(); }, [](Plater* p) { p->center_selection(); });
});
add_with_icon("obj_drop", L("Drop to Bed"), L("Object"), "toolbar_flatten", [](const std::string&) {
return object_op(wxGetApp().plater(), [](Plater* p) { return !p->is_selection_empty(); }, [](Plater* p) { p->drop_selection(); });
return object_op(wxGetApp().plater(), [](Plater* p) { return p->get_selection().can_drop(); }, [](Plater* p) { p->drop_selection(); });
});
add("obj_fit_volume", L("Scale to Fit Print Volume"), L("Object"), [](const std::string&) {
return object_op(
+26 -4
View File
@@ -558,15 +558,37 @@ void Selection::center()
return;
}
double Selection::get_drop_min_z() const
{
if (!m_valid)
return 0.;
if (m_mode == Volume)
return get_bounding_box().min.z();
double min_z = DBL_MAX;
for (const auto& [obj_idx, inst_idxs] : m_cache.content) {
if (obj_idx >= (int)m_model->objects.size())
continue; // wipe tower
for (int inst_idx : inst_idxs)
min_z = std::min(min_z, m_model->objects[obj_idx]->get_instance_min_z(inst_idx));
}
return min_z == DBL_MAX ? 0. : min_z;
}
bool Selection::can_drop() const
{
return std::abs(get_drop_min_z()) > -SINKING_Z_THRESHOLD;
}
void Selection::drop()
{
if (std::abs(this->get_bounding_box().min.z()) < -SINKING_Z_THRESHOLD) {
return; // shouldnt happen, but better check anyways, already checked in append_menu_item_drop()
}
const double min_z = get_drop_min_z();
if (std::abs(min_z) < -SINKING_Z_THRESHOLD)
return; // already checked in can_drop()
wxGetApp().plater()->take_snapshot(L("Move object"));
this->move_to_center(Vec3d(0, 0, -this->get_bounding_box().min.z()));
this->move_to_center(Vec3d(0, 0, -min_z));
for (unsigned int i : m_list) {
GLVolume& volume = *(*m_volumes)[i];
+3
View File
@@ -248,6 +248,9 @@ public:
void clone(int numbers = 1);
void center();
void drop();
bool can_drop() const;
// Lowest world Z that drop() moves to the bed; whole objects count model parts only, as auto drop does.
double get_drop_min_z() const;
void center_plate(const int plate_idx);
void set_printable(bool printable);
bool get_auto_drop() const;