Make the Fit camera button frame the Design tab's selection and sketches

In the Design tab the button always swung the camera to the plate view and framed the whole
bed, whatever was selected: the tab's picks and sketches are neither the canvas's selection
nor its volumes. It now frames the selected faces, body, edges, vertex, sketch region or
sketch entities, and with nothing selected everything on show — the visible bodies, the
feature preview and the sketches — keeping the current view direction. An empty tab still
frames the bed as before.
This commit is contained in:
SoftFever
2026-10-06 12:28:52 +08:00
parent 56ebde968a
commit afa9252b59
5 changed files with 211 additions and 8 deletions
+71
View File
@@ -3741,6 +3741,77 @@ std::vector<std::pair<int, int>> DesignSketchTool::selected_faces() const
return out;
}
BoundingBoxf3 DesignSketchTool::fit_box(const GLVolumeCollection& volumes) const
{
BoundingBoxf3 box;
const auto add_entity = [&box, this](const SketchEntity& e, const SketchPlane& plane) {
bool closed = false;
for (const Vec2d& p : entity_polyline(e, closed))
box.merge(plane.to_world(p));
};
const auto add_entities = [&add_entity](const std::vector<SketchEntity>& ents, const SketchPlane& plane) {
for (const SketchEntity& e : ents)
add_entity(e, plane);
};
// The selected faces, a whole body's included, from the pick mesh: it is in world
// coordinates already, moved bodies and all.
const std::vector<std::pair<int, int>> faces = selected_faces();
if (!faces.empty() && m_solid_mesh != nullptr && m_solid_tri_face != nullptr && m_solid_tri_body != nullptr) {
const indexed_triangle_set& its = m_solid_mesh->its;
const size_t n = std::min({ its.indices.size(), m_solid_tri_face->size(), m_solid_tri_body->size() });
for (size_t t = 0; t < n; ++t)
if (std::binary_search(faces.begin(), faces.end(), std::make_pair((*m_solid_tri_body)[t], (*m_solid_tri_face)[t])))
for (int i = 0; i < 3; ++i)
box.merge(its.vertices[its.indices[t][i]].cast<double>());
}
if (m_solid_sel == SolidSel::Edge) {
for (const Vec3d& p : m_sel_edge_pts)
box.merge(p);
for (const std::vector<Vec3d>& pts : m_sel_edges_more_pts)
for (const Vec3d& p : pts)
box.merge(p);
} else if (m_solid_sel == SolidSel::Vertex)
box.merge(m_sel_vertex_pt);
// A committed sketch's picked region with its holes; a stroke outside any region, the sketch.
if (m_display_pick >= 0)
for (const DisplaySketch& d : m_display_sketches)
if (d.feature == m_display_pick)
add_entities(m_display_pick_region >= 0 ? selected_loop_entities() : d.entities, d.plane);
if (m_active) {
for (int i : m_selection)
if (i >= 0 && i < int(m_entities.size()))
add_entity(m_entities[i], m_plane);
for (const auto& [i, role] : m_point_sel)
if (i >= 0 && i < int(m_entities.size())) {
const SketchEntity& e = m_entities[i];
box.merge(m_plane.to_world(role == SketchPointRole::Center ? e.center :
role == SketchPointRole::P1 ? e.p1 : e.p0));
}
}
// Nothing selected: everything on show. A hidden body's volume is inactive.
if (!box.defined) {
for (const GLVolume* v : volumes.volumes)
if (v->is_active)
box.merge(v->transformed_bounding_box());
for (const DisplaySketch& d : m_display_sketches)
add_entities(d.entities, d.plane);
if (m_active)
add_entities(m_entities, m_plane);
}
// A vertex has no size, nor has a sketch along its normal, and the camera cannot frame an
// extent it does not see. Every axis gets at least a small neighbourhood.
if (box.defined) {
constexpr double min_extent = 10.; // mm
const Vec3d grow = (Vec3d::Constant(min_extent) - box.size()).cwiseMax(0.) * 0.5;
box.min -= grow;
box.max += grow;
}
return box;
}
const DesignSketchTool::FaceHighlight& DesignSketchTool::cached_face_highlight(FaceHighlight& cache, int body,
std::vector<int> faces) const
{
+7
View File
@@ -2,6 +2,7 @@
#define slic3r_DesignSketchTool_hpp_
#include "libslic3r/Point.hpp"
#include "libslic3r/BoundingBox.hpp"
#include "libslic3r/CAD/SketchEngine.hpp"
#include "libslic3r/CAD/CadDocument.hpp" // CadBody for per-body solid picking
#include <TopoDS_Shape.hxx>
@@ -24,6 +25,7 @@ class wxPoint;
namespace Slic3r {
class TriangleMesh; // fwd (libslic3r) — solid-pick mesh, non-owning pointer
class GLVolumeCollection;
namespace GUI {
@@ -226,6 +228,11 @@ public:
void set_highlight_faces(const std::vector<std::pair<int, int>>& faces);
// Every face drawn as selected, sorted: the committed pick's and the still-valid ones above.
std::vector<std::pair<int, int>> selected_faces() const;
// What the canvas's Fit button frames: the selection — faces (the Feature tree's included), a
// body, edges, a vertex, a sketch region, the live sketch's picked entities — else everything
// on show: the visible bodies, the preview of the feature being edited and every sketch.
// Undefined when the tab shows none of it. `volumes` are the canvas's: bodies and preview.
BoundingBoxf3 fit_box(const GLVolumeCollection& volumes) const;
// Move-body gizmo (M5): translate a whole body with three world-axis drag arrows
// (X red / Y green / Z blue) anchored at the body centroid. Display-only — the host
+19 -8
View File
@@ -10144,14 +10144,25 @@ void GLCanvas3D::_render_canvas_toolbar()
ImTextureID z_hover_id = m_gizmos.get_icon_texture_id(m_is_dark ? GLGizmosManager::MENU_ICON_NAME::IC_CANVAS_ZOOM_DARK_HOVER : GLGizmosManager::MENU_ICON_NAME::IC_CANVAS_ZOOM_HOVER);
if (ImGui::ImageButton3(z_normal_id, z_hover_id, btn_size)) {
select_view("plate");
if (m_selection.is_empty()) {
if (m_canvas_type == ECanvasType::CanvasAssembleView)
zoom_to_volumes();
else
zoom_to_bed();
} else {
zoom_to_selection();
#ifdef SLIC3R_CAD
// The Design tab selects and sketches outside the canvas's selection and volumes, so
// it names what to frame. Framed along the current view, which is often square to a
// sketch plane; an empty tab falls through to the bed.
const BoundingBoxf3 design_box = m_design_sketch_tool != nullptr ? m_design_sketch_tool->fit_box(m_volumes) : BoundingBoxf3();
if (design_box.defined)
_zoom_to_box(design_box);
else
#endif
{
select_view("plate");
if (m_selection.is_empty()) {
if (m_canvas_type == ECanvasType::CanvasAssembleView)
zoom_to_volumes();
else
zoom_to_bed();
} else {
zoom_to_selection();
}
}
} else if (ImGui::IsItemHovered()) {
auto tooltip_str_wx = _L("Fit camera to scene or selected object.");
+4
View File
@@ -39,6 +39,10 @@ add_executable(${_TEST_NAME}_tests
../fff_print/test_helpers.cpp
)
if (SLIC3R_CAD)
target_sources(${_TEST_NAME}_tests PRIVATE test_design_sketch_tool.cpp)
endif ()
if (MSVC)
target_link_libraries(${_TEST_NAME}_tests Setupapi.lib)
endif ()
@@ -0,0 +1,110 @@
// Orca: This suite links libslic3r_gui; the Design tab's sketch tool needs no wx application or GL
// context until it renders.
#ifdef WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <Windows.h>
// Match the GUI precompiled header: wx/msw/wrapcctl.h needs HDITEM from CommCtrl.h.
#include <CommCtrl.h>
#endif
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include "libslic3r/BoundingBox.hpp"
#include "libslic3r/CAD/SketchEngine.hpp"
#include "libslic3r/Point.hpp"
#include "slic3r/GUI/3DScene.hpp"
#include "slic3r/GUI/CAD/DesignSketchTool.hpp"
using namespace Slic3r;
using namespace Slic3r::GUI;
using Catch::Matchers::WithinAbs;
namespace {
SketchEntity circle(const Vec2d& c, double r)
{
SketchEntity e;
e.type = SketchEntity::Type::Circle;
e.center = e.p0 = c;
e.radius = r;
return e;
}
// Two committed sketches on XY: feature 1 a 10 mm circle at (50, 20), feature 2 a 5 mm one at (-60, 0).
void show_two_sketches(DesignSketchTool& tool)
{
tool.set_display_sketches({ { { circle({ 50., 20. }, 10.) }, SketchPlane::XY(), 1 },
{ { circle({ -60., 0. }, 5.) }, SketchPlane::XY(), 2 } });
}
} // namespace
TEST_CASE("Fit frames the picked sketch region, not the other sketches", "[DesignSketchTool]")
{
DesignSketchTool tool;
GLVolumeCollection no_bodies;
show_two_sketches(tool);
tool.set_display_pick(1, 0);
const BoundingBoxf3 box = tool.fit_box(no_bodies);
REQUIRE(box.defined);
CHECK_THAT(box.min.x(), WithinAbs(40., 0.5));
CHECK_THAT(box.max.x(), WithinAbs(60., 0.5));
CHECK_THAT(box.min.y(), WithinAbs(10., 0.5));
CHECK_THAT(box.max.y(), WithinAbs(30., 0.5));
}
TEST_CASE("Fit frames every sketch when nothing is picked", "[DesignSketchTool]")
{
DesignSketchTool tool;
GLVolumeCollection no_bodies;
show_two_sketches(tool);
const BoundingBoxf3 box = tool.fit_box(no_bodies);
REQUIRE(box.defined);
CHECK_THAT(box.min.x(), WithinAbs(-65., 0.5));
CHECK_THAT(box.max.x(), WithinAbs(60., 0.5));
}
TEST_CASE("Fit frames the sketch being drawn along with the committed ones", "[DesignSketchTool]")
{
DesignSketchTool tool;
GLVolumeCollection no_bodies;
show_two_sketches(tool);
// A 100 mm line up the XZ plane, whose y axis is world Z.
SketchEntity line;
line.p0 = { 0., 0. };
line.p1 = { 0., 100. };
tool.begin_edit({ line }, {}, SketchPlane::XZ());
const BoundingBoxf3 box = tool.fit_box(no_bodies);
REQUIRE(box.defined);
CHECK_THAT(box.max.z(), WithinAbs(100., 1e-6));
CHECK_THAT(box.min.x(), WithinAbs(-65., 0.5));
}
TEST_CASE("Fit gives a flat sketch depth, so it can be framed edge-on", "[DesignSketchTool]")
{
DesignSketchTool tool;
GLVolumeCollection no_bodies;
show_two_sketches(tool);
tool.set_display_pick(2, 0);
const BoundingBoxf3 box = tool.fit_box(no_bodies);
REQUIRE(box.defined);
CHECK(box.size().z() > 0.);
CHECK_THAT(box.center().z(), WithinAbs(0., 1e-9));
}
TEST_CASE("Fit frames nothing when the Design tab shows nothing", "[DesignSketchTool]")
{
DesignSketchTool tool;
GLVolumeCollection no_bodies;
CHECK_FALSE(tool.fit_box(no_bodies).defined);
}