mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-06 07:11:10 +00:00
Stop the Design tab's move arrows from jumping the body on the first drag
Grabbing an arrow anywhere along its length snapped the body's centre to that point as soon as the mouse moved. The body now moves by how far the cursor travels from where the arrow was grabbed, including when it is grabbed while looking straight down the axis.
This commit is contained in:
@@ -78,6 +78,7 @@ static bool point_in_poly(const Vec2d& q, const std::vector<Vec2d>& poly);
|
||||
static bool ray_triangle(const Vec3d& ro, const Vec3d& rd, const Vec3d& v0, const Vec3d& v1,
|
||||
const Vec3d& v2, double& t);
|
||||
static double ray_segment_dist3(const Vec3d& ro, const Vec3d& rd, const Vec3d& a, const Vec3d& b);
|
||||
static double ray_axis_proj(const Linef3& ray, const Vec3d& anchor, const Vec3d& dir);
|
||||
|
||||
// Project a world-space point to canvas screen pixels (device px, GL viewport units;
|
||||
// origin top-left after the GL y-flip). Mirrors GLCanvas3D's world->screen pattern:
|
||||
@@ -5819,19 +5820,22 @@ bool DesignSketchTool::hit_test_move_arrow(GLCanvas3D& canvas, const wxMouseEven
|
||||
axis = best; return true;
|
||||
}
|
||||
|
||||
// Skew-line closest point of the mouse ray to the axis line through the ORIGINAL centroid
|
||||
// -> signed offset along that axis (no clamp; a body can move either way).
|
||||
void DesignSketchTool::drag_move_arrow(GLCanvas3D& canvas, const wxMouseEvent& evt, int axis)
|
||||
// Record how far along the arrow it was grabbed (NaN while the camera looks down the axis; the
|
||||
// first drag sample that projects stands in), so the body's centre does not snap to the grab.
|
||||
void DesignSketchTool::grab_move_arrow(int axis, const Linef3& ray)
|
||||
{
|
||||
const Linef3 r = canvas.mouse_ray(Point(evt.GetX(), evt.GetY()));
|
||||
const Vec3d ro = r.a, rd = r.b - r.a;
|
||||
const Vec3d axes[3] = { Vec3d::UnitX(), Vec3d::UnitY(), Vec3d::UnitZ() };
|
||||
const Vec3d e = axes[axis];
|
||||
const Vec3d w0 = m_mv_base - ro;
|
||||
const double a = e.dot(e), b = e.dot(rd), c = rd.dot(rd), dd = e.dot(w0), ee = rd.dot(w0);
|
||||
const double denom = a * c - b * b;
|
||||
if (std::abs(denom) < 1e-7) return; // camera ∥ axis: leave offset as-is
|
||||
m_mv_offset[axis] = (b * ee - c * dd) / denom;
|
||||
m_mv_drag = axis;
|
||||
m_mv_grab_along = ray_axis_proj(ray, m_mv_base + m_mv_offset, Vec3d::Unit(axis));
|
||||
}
|
||||
|
||||
// Slide the body along the grabbed axis by the cursor's travel (no clamp; it can move either way).
|
||||
void DesignSketchTool::drag_move_arrow(const Linef3& ray)
|
||||
{
|
||||
if (m_mv_drag < 0 || m_mv_drag > 2) return;
|
||||
const double proj = ray_axis_proj(ray, m_mv_base + m_mv_offset, Vec3d::Unit(m_mv_drag));
|
||||
if (std::isnan(proj)) return; // camera ∥ axis: leave offset as-is
|
||||
if (std::isnan(m_mv_grab_along)) { m_mv_grab_along = proj; return; }
|
||||
m_mv_offset[m_mv_drag] += proj - m_mv_grab_along;
|
||||
if (on_body_move_changed) on_body_move_changed(m_mv_body, compose_move_xform());
|
||||
}
|
||||
|
||||
@@ -6201,21 +6205,10 @@ int DesignSketchTool::hit_test_hole_handle(GLCanvas3D& canvas, const wxMouseEven
|
||||
return best;
|
||||
}
|
||||
|
||||
// Skew-line closest point of the mouse ray to an axis (anchor + t*dir) -> signed distance along
|
||||
// dir. NaN when the camera is ~parallel to the axis (no meaningful projection).
|
||||
double DesignSketchTool::hole_axis_proj(GLCanvas3D& canvas, const wxMouseEvent& evt,
|
||||
const Vec3d& anchor, const Vec3d& dir) const
|
||||
{
|
||||
const Linef3 r = canvas.mouse_ray(Point(evt.GetX(), evt.GetY()));
|
||||
const Vec3d ro = r.a, rd = r.b - r.a;
|
||||
const Vec3d e = dir;
|
||||
const Vec3d w0 = anchor - ro;
|
||||
const double a = e.dot(e), b = e.dot(rd), c = rd.dot(rd), dd = e.dot(w0), ee = rd.dot(w0);
|
||||
const double denom = a * c - b * b;
|
||||
// Relative near-parallel guard: when the camera ray is ~along the axis (e.g. the depth axis in
|
||||
// top view) denom collapses; a tiny absolute floor lets a huge, unstable projection through.
|
||||
if (std::abs(denom) < 1e-4 * std::max(a * c, 1e-12)) return std::nan("");
|
||||
return (b * ee - c * dd) / denom;
|
||||
return ray_axis_proj(canvas.mouse_ray(Point(evt.GetX(), evt.GetY())), anchor, dir);
|
||||
}
|
||||
|
||||
void DesignSketchTool::start_hole_drag(GLCanvas3D& canvas, const wxMouseEvent& evt, int which)
|
||||
@@ -10179,6 +10172,21 @@ static double ray_segment_dist3(const Vec3d& ro, const Vec3d& rd, const Vec3d& a
|
||||
return (pr - ps).norm();
|
||||
}
|
||||
|
||||
// Skew-line closest point of the mouse ray to an axis (anchor + t*dir) -> signed distance along
|
||||
// dir. NaN when the camera is ~parallel to the axis (no meaningful projection).
|
||||
static double ray_axis_proj(const Linef3& ray, const Vec3d& anchor, const Vec3d& dir)
|
||||
{
|
||||
const Vec3d ro = ray.a, rd = ray.vector();
|
||||
const Vec3d e = dir;
|
||||
const Vec3d w0 = anchor - ro;
|
||||
const double a = e.dot(e), b = e.dot(rd), c = rd.dot(rd), dd = e.dot(w0), ee = rd.dot(w0);
|
||||
const double denom = a * c - b * b;
|
||||
// Relative near-parallel guard: when the camera ray is ~along the axis (e.g. the depth axis in
|
||||
// top view) denom collapses; a tiny absolute floor lets a huge, unstable projection through.
|
||||
if (std::abs(denom) < 1e-4 * std::max(a * c, 1e-12)) return std::nan("");
|
||||
return (b * ee - c * dd) / denom;
|
||||
}
|
||||
|
||||
// Screen-plane distance from p to a sketch entity, for click picking in Constrain
|
||||
// mode. Circles/arcs measure distance to the ring; points to their position.
|
||||
static double entity_pick_dist(const Vec2d& p, const SketchEntity& e)
|
||||
@@ -10687,7 +10695,7 @@ bool DesignSketchTool::on_mouse_impl(wxMouseEvent& evt, GLCanvas3D& canvas)
|
||||
// exits move mode. A LeftDown that misses the arrows falls through to solid re-pick.
|
||||
if (m_mv_active) {
|
||||
if (m_mv_drag >= 0 && evt.Dragging() && evt.LeftIsDown()) {
|
||||
if (m_mv_drag < 3) drag_move_arrow(canvas, evt, m_mv_drag);
|
||||
if (m_mv_drag < 3) drag_move_arrow(canvas.mouse_ray(Point(evt.GetX(), evt.GetY())));
|
||||
else drag_move_arc(canvas, evt, m_mv_drag - 3);
|
||||
return true;
|
||||
}
|
||||
@@ -10705,7 +10713,8 @@ bool DesignSketchTool::on_mouse_impl(wxMouseEvent& evt, GLCanvas3D& canvas)
|
||||
if (evt.LeftDown()) {
|
||||
int axis = -1;
|
||||
if (hit_test_move_arrow(canvas, evt, axis)) { // translate arrows win over rings
|
||||
m_mv_drag = axis; m_mv_press_x = evt.GetX(); m_mv_press_y = evt.GetY();
|
||||
grab_move_arrow(axis, canvas.mouse_ray(Point(evt.GetX(), evt.GetY())));
|
||||
m_mv_press_x = evt.GetX(); m_mv_press_y = evt.GetY();
|
||||
return true;
|
||||
}
|
||||
if (hit_test_move_arc(canvas, evt, axis)) {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include "libslic3r/Line.hpp"
|
||||
#include "libslic3r/CAD/SketchEngine.hpp"
|
||||
#include "libslic3r/CAD/CadDocument.hpp" // CadBody for per-body solid picking
|
||||
#include <TopoDS_Shape.hxx>
|
||||
@@ -260,6 +261,9 @@ public:
|
||||
void clear_move_gizmo();
|
||||
bool moving_body() const { return m_mv_active; }
|
||||
int move_body_index() const { return m_mv_body; }
|
||||
// Press, then drag, translate arrow `axis` (0..2 = X/Y/Z) with the mouse ray under the cursor.
|
||||
void grab_move_arrow(int axis, const Linef3& ray);
|
||||
void drag_move_arrow(const Linef3& ray);
|
||||
std::function<void(int body, const Transform3d& xform)> on_body_move_changed;
|
||||
// Fired on each cycle change: (level 0=None/1=Whole/2=Face/3=Edge, body index, face id, edge id).
|
||||
std::function<void(int level, int body, int face, int edge)> on_solid_selection_changed;
|
||||
@@ -1432,6 +1436,7 @@ private:
|
||||
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};
|
||||
double m_mv_grab_along{0.0}; // arrow grab point's distance from the anchor; NaN = unknown
|
||||
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();
|
||||
@@ -1439,7 +1444,6 @@ private:
|
||||
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);
|
||||
void drag_move_arc(GLCanvas3D& canvas, const wxMouseEvent& evt, int axis);
|
||||
bool arc_mouse_angle(GLCanvas3D& canvas, const wxMouseEvent& evt, int axis, double& ang) const;
|
||||
void open_move_editor(int axis);
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include "libslic3r/CAD/SketchEngine.hpp"
|
||||
#include "libslic3r/Line.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "slic3r/GUI/3DScene.hpp"
|
||||
#include "slic3r/GUI/CAD/DesignSketchTool.hpp"
|
||||
@@ -305,3 +306,41 @@ TEST_CASE("Cutting the base planes along each other keeps every plane whole", "[
|
||||
for (double a : covered)
|
||||
CHECK_THAT(a, WithinRel(4. * half * half, 1e-9));
|
||||
}
|
||||
|
||||
TEST_CASE("A move arrow drag moves the body by the cursor's travel, wherever the arrow is grabbed", "[DesignSketchTool]")
|
||||
{
|
||||
DesignSketchTool tool;
|
||||
Transform3d moved = Transform3d::Identity();
|
||||
tool.on_body_move_changed = [&moved](int, const Transform3d& xform) { moved = xform; };
|
||||
tool.set_move_gizmo(0, Vec3d::Zero(), Transform3d::Identity(), 10.);
|
||||
// Looking straight down onto the X arrow, the cursor over x = `x` on it.
|
||||
const auto ray_at = [](double x) { return Linef3(Vec3d(x, 0., 100.), Vec3d(x, 0., 0.)); };
|
||||
|
||||
// Grabbed 12 mm out from the body centre: a 1 mm move moves the body 1 mm, not 13.
|
||||
tool.grab_move_arrow(0, ray_at(12.));
|
||||
tool.drag_move_arrow(ray_at(13.));
|
||||
CHECK_THAT(moved.translation().x(), WithinAbs(1., 1e-9));
|
||||
tool.drag_move_arrow(ray_at(17.));
|
||||
CHECK_THAT(moved.translation().x(), WithinAbs(5., 1e-9));
|
||||
|
||||
// The next drag carries on from where the body was left: grabbed 3 mm past it, moved 2 mm.
|
||||
tool.grab_move_arrow(0, ray_at(8.));
|
||||
tool.drag_move_arrow(ray_at(10.));
|
||||
CHECK_THAT(moved.translation().x(), WithinAbs(7., 1e-9));
|
||||
}
|
||||
|
||||
TEST_CASE("A move arrow pressed while looking down its axis does not jump on the first move", "[DesignSketchTool]")
|
||||
{
|
||||
DesignSketchTool tool;
|
||||
Transform3d moved = Transform3d::Identity();
|
||||
tool.on_body_move_changed = [&moved](int, const Transform3d& xform) { moved = xform; };
|
||||
tool.set_move_gizmo(0, Vec3d::Zero(), Transform3d::Identity(), 10.);
|
||||
const auto ray_at = [](double x) { return Linef3(Vec3d(x, 0., 100.), Vec3d(x, 0., 0.)); };
|
||||
|
||||
// The press projects nowhere on the X axis, so the first move only finds where it was grabbed.
|
||||
tool.grab_move_arrow(0, Linef3(Vec3d(100., 0., 0.), Vec3d::Zero()));
|
||||
tool.drag_move_arrow(ray_at(13.));
|
||||
CHECK_THAT(moved.translation().x(), WithinAbs(0., 1e-9));
|
||||
tool.drag_move_arrow(ray_at(15.));
|
||||
CHECK_THAT(moved.translation().x(), WithinAbs(2., 1e-9));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user