Size the Design move/rotate gizmo from the body, like Orca's Prepare gizmos

The Design gizmo already had both move arrows and rotation rings, but drew them
at a fixed 70 px arm regardless of the body: on a 40 mm cube everything
collapsed into a ~100 px tangle buried inside the solid, so the rings were
effectively invisible and the tool read as "move only, no rotate".

Orca's Prepare gizmos size themselves from the selection's bounding sphere
(GLGizmoRotate3D: m_radius = Offset + sphere radius) so the handles always clear
the object. Same rule here: DesignPanel passes the body's bounding-sphere radius
(scale-aware) into the gizmo, and move_gizmo_arm() returns
max(70 px, 1.25 * radius) — the screen-space floor keeps it grabbable on a tiny
body or when zoomed far out. Ring radius and BOTH hit-tests derive from that one
helper, so picking cannot drift from what is drawn.

Verified on a 40 mm cube: rings now encircle the body, arrow drag moves with a
live mm readout, ring drag rotates live.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
This commit is contained in:
Tommaso Bianchi
2026-07-19 08:36:26 +02:00
co-authored by Claude Opus 4.8
parent f4160595b0
commit 8d529ad413
5 changed files with 39 additions and 11 deletions
+3 -2
View File
@@ -512,9 +512,10 @@ ColorRGBA DesignCanvas::body_color(int body) const
return body_palette(body);
}
void DesignCanvas::begin_move_body(int body, const Vec3d& pivot, const Transform3d& base_xform)
void DesignCanvas::begin_move_body(int body, const Vec3d& pivot, const Transform3d& base_xform,
double body_radius)
{
m_sketch_tool.set_move_gizmo(body, pivot, base_xform);
m_sketch_tool.set_move_gizmo(body, pivot, base_xform, body_radius);
request_repaint();
}
+4 -1
View File
@@ -94,7 +94,10 @@ public:
ColorRGBA body_color(int body) const;
// Move-body gizmo (M5): three world-axis drag arrows on a body; drag fires the move
// callback with the body index + accumulated translation (display-only, host applies it).
void begin_move_body(int body, const Vec3d& pivot, const Transform3d& base_xform);
// body_radius = bounding-sphere radius of the body in world mm; the gizmo scales with it so
// the rotation rings sit OUTSIDE the solid (Orca's Prepare gizmos do the same).
void begin_move_body(int body, const Vec3d& pivot, const Transform3d& base_xform,
double body_radius);
void clear_move_gizmo();
bool moving_body() const;
void set_on_body_move_changed(std::function<void(int, const Transform3d&)> cb);
+7 -2
View File
@@ -3453,8 +3453,13 @@ void DesignPanel::on_move_body()
// Delta gizmo: pivot at the body's CURRENT world centroid; the tool composes the drag deltas
// onto its current pose, so move + rotate both work (incl. on an already place-on-face'd body).
const Transform3d base = m_body_xform[b];
const Vec3d pivot = base * m_doc.display_body_meshes[b].bounding_box().center();
m_viewport->begin_move_body(b, pivot, base);
const BoundingBoxf3 bb = m_doc.display_body_meshes[b].bounding_box();
const Vec3d pivot = base * bb.center();
// Bounding-sphere radius (world): the gizmo scales with it so the rotation rings sit clear of
// the body instead of collapsing into a tangle inside it — same sizing rule as Orca's Prepare
// gizmos. The scale part of `base` is applied so a scaled body still gets a correct radius.
const double radius = (base.linear() * (bb.size() * 0.5)).norm();
m_viewport->begin_move_body(b, pivot, base, radius);
m_move_body = b; // for the action bar: Cancel reverts to this pose
m_move_prev = base;
update_action_bar(); // surface the unified ✓/✗ while moving
+19 -5
View File
@@ -3390,7 +3390,8 @@ int DesignSketchTool::hit_test_base_pick(GLCanvas3D& canvas, const wxMouseEvent&
}
// ---- Move-body gizmo (M5) -------------------------------------------------------------
void DesignSketchTool::set_move_gizmo(int body, const Vec3d& pivot, const Transform3d& base_xform)
void DesignSketchTool::set_move_gizmo(int body, const Vec3d& pivot, const Transform3d& base_xform,
double body_radius)
{
m_mv_active = true;
m_mv_body = body;
@@ -3399,6 +3400,19 @@ void DesignSketchTool::set_move_gizmo(int body, const Vec3d& pivot, const Transf
m_mv_offset = Vec3d::Zero();
m_mv_rot = Eigen::Matrix3d::Identity();
m_mv_drag = -1;
m_mv_radius = std::max(body_radius, 0.0);
}
// Gizmo arm length in world mm. Orca's Prepare gizmos size themselves from the selection's
// bounding sphere (GLGizmoRotate3D: m_radius = Offset + sphere radius) so the handles always sit
// clear of the object; a fixed screen-size arm instead collapsed into a tangle buried inside a
// large solid, which is why the rotation rings read as "missing". Same idea here, with a
// screen-space floor so the gizmo stays grabbable on a tiny body or when zoomed far out.
double DesignSketchTool::move_gizmo_arm(const Camera& cam) const
{
const double upp = 1.0 / std::max(cam.get_zoom(), 1e-6);
const double screen_min = 70.0 * upp; // never smaller than the old fixed size
return std::max(screen_min, m_mv_radius * 1.25); // 25% clear of the body surface
}
void DesignSketchTool::clear_move_gizmo()
@@ -3443,7 +3457,7 @@ void DesignSketchTool::render_move_gizmo()
const Vec3d anchor = m_mv_base + m_mv_offset;
const double upp = 1.0 / std::max(cam.get_zoom(), 1e-6);
const double th = std::max(15.0 * upp, 1e-4);
const double L = 70.0 * upp; // fixed screen-size arrow length
const double L = move_gizmo_arm(cam); // scales with the body (see move_gizmo_arm)
const SketchPlane saved = m_plane;
SketchPlane bb; bb.origin = anchor; bb.x_axis = right; bb.y_axis = up; bb.normal = fwd;
@@ -3478,7 +3492,7 @@ void DesignSketchTool::render_move_gizmo()
// Three world-axis rotation rings (X/Y/Z), each a circle in the plane perpendicular to
// its axis through the gizmo anchor — drag a ring to rotate the body about that axis.
const double R = 58.0 * upp;
const double R = 0.83 * move_gizmo_arm(cam); // rings just inside the arrow tips
for (int a = 0; a < 3; ++a) {
Vec3d e, u, v; ring_basis(a, e, u, v);
SketchPlane rp; rp.origin = anchor; rp.x_axis = u; rp.y_axis = v; rp.normal = e;
@@ -3504,7 +3518,7 @@ bool DesignSketchTool::hit_test_move_arrow(GLCanvas3D& canvas, const wxMouseEven
const Vec3d ro = r.a, rd = r.b - r.a;
const Camera& cam = wxGetApp().plater()->get_camera();
const double upp = 1.0 / std::max(cam.get_zoom(), 1e-6);
const double L = 70.0 * upp;
const double L = move_gizmo_arm(cam); // must match render_move_gizmo
const Vec3d anchor = m_mv_base + m_mv_offset;
const Vec3d axes[3] = { Vec3d::UnitX(), Vec3d::UnitY(), Vec3d::UnitZ() };
int best = -1; double bestd = 7.0 * upp; // ~7 px tolerance
@@ -3552,7 +3566,7 @@ bool DesignSketchTool::hit_test_move_arc(GLCanvas3D& canvas, const wxMouseEvent&
const Vec3d ro = r.a, rd = r.b - r.a;
const Camera& cam = wxGetApp().plater()->get_camera();
const double upp = 1.0 / std::max(cam.get_zoom(), 1e-6);
const double R = 58.0 * upp;
const double R = 0.83 * move_gizmo_arm(cam); // rings just inside the arrow tips
const Vec3d anchor = m_mv_base + m_mv_offset;
int best = -1; double bestd = 7.0 * upp;
const int N = 48;
+6 -1
View File
@@ -22,6 +22,7 @@ class TriangleMesh; // fwd (libslic3r) — solid-pick mesh, non-owning pointer
namespace GUI {
class GLCanvas3D;
class Camera; // fwd — move_gizmo_arm() sizes the gizmo from the current zoom
// Onshape-style sketch session. `begin` enters a session on a plane; the active
// drawing tool (Mode) can be switched mid-session via `set_tool` while entities
@@ -131,7 +132,8 @@ public:
// keeps a per-body Transform3d and re-feeds the moved display/pick meshes; the OCCT
// shape (and thus face/edge global ids) is never touched. Drag fires on_body_move_changed
// live; a stationary click on an arrow opens the inline offset editor for that axis.
void set_move_gizmo(int body, const Vec3d& pivot, const Transform3d& base_xform);
void set_move_gizmo(int body, const Vec3d& pivot, const Transform3d& base_xform,
double body_radius = 0.0);
void clear_move_gizmo();
bool moving_body() const { return m_mv_active; }
int move_body_index() const { return m_mv_body; }
@@ -960,10 +962,13 @@ private:
Eigen::Matrix3d m_mv_rot_start{Eigen::Matrix3d::Identity()}; // rot snapshot at arc-drag start
double m_mv_arc_a0{0.0}; // mouse angle on the ring at drag start
int m_mv_drag{-1}; // 0..2 = X/Y/Z arrow, 3..5 = X/Y/Z ring, -1 none
double m_mv_radius{0.0}; // body bounding-sphere radius (mm); 0 = unknown
int m_mv_press_x{0}, m_mv_press_y{0};
Transform3d compose_move_xform() const; // T(offset)*T(pivot)*rot*T(-pivot)*base_xform
void ring_basis(int axis, Vec3d& e, Vec3d& u, Vec3d& v) const; // world axis + in-plane basis
void render_move_gizmo();
// Gizmo arm length (world mm): scales with the body so the rings clear its surface.
double move_gizmo_arm(const Camera& cam) const;
bool hit_test_move_arrow(GLCanvas3D& canvas, const wxMouseEvent& evt, int& axis) const;
bool hit_test_move_arc(GLCanvas3D& canvas, const wxMouseEvent& evt, int& axis) const;
void drag_move_arrow(GLCanvas3D& canvas, const wxMouseEvent& evt, int axis);