Compare commits

...
Author SHA1 Message Date
SoftFever 34696f5651 Name the Design canvas flag after its role, not the axes it once moved 2026-10-06 03:44:15 +08:00
SoftFever c300fa1e8d Keep the Design tab's bed, grid and reference planes aligned on every plate 2026-10-06 03:41:33 +08:00
SoftFever cd30b196b2 Let the camera pan and orbit freely while sketching in the Design tab
Panning or orbiting with a right-drag no longer ends the polyline chain or drops
the point already placed. Only a right-click that doesn't move does. In the
Touchpad camera style, Alt+move and Shift+move now orbit and pan even while a
draw tool is armed.
2026-10-05 23:24:50 +08:00
SoftFever b67269ccc4 Fix z-fighting between feature previews and the bodies in the Design tab
The bodies now always draw over the preview on every face a feature leaves unchanged, for
all features. Since a hole's cut sits inside the body, the Hole preview now hides the
bodies and shows the result alone, as Fillet/Chamfer and Draft do.
2026-10-05 19:38:46 +08:00
11 changed files with 163 additions and 68 deletions
@@ -262,9 +262,9 @@ and a band is not a "gesture" in the `CadLevel::Gesture` sense because nothing h
|---|---|---|---|---|---| |---|---|---|---|---|---|
| `Move` | — | `update_hover` / `update_solid_hover` (**non-consuming**, returns false) | — | passthrough | hover only asks for a repaint, `:10022-10025` | | `Move` | — | `update_hover` / `update_solid_hover` (**non-consuming**, returns false) | — | passthrough | hover only asks for a repaint, `:10022-10025` |
| `LeftDown` | orbit may begin | latch press, **return false** | — | consume-or-orbit is the canvas's call | `:10058-10063`; consuming here killed orbit once already | | `LeftDown` | orbit may begin | latch press, **return false** | — | consume-or-orbit is the canvas's call | `:10058-10063`; consuming here killed orbit once already |
| `LeftDrag` > 8 px | — | start + drive band, **consume** | — | no longer orbits in this canvas | middle-drag orbits, right-drag pans (`:10013-10016`) | | `LeftDrag` > 8 px | — | start + drive band, **consume**, when Shift is held or the left button has no camera action | — | otherwise the left button's drag action (Preferences > Control) | the band is Prepare's Shift+left-drag rectangle selection |
| `LeftUp` | — | commit pick **or** resolve band | — | — | `:10035-10041` | | `LeftUp` | — | commit pick **or** resolve band | — | — | `:10035-10041` |
| `MiddleDrag` / `RightDrag` | — | must not see it | — | orbit / pan | camera gestures never reach the FSM | | `MiddleDrag` / `RightDrag` | — | must not see it | — | the button's drag action (Preferences > Control) | camera gestures never reach the FSM; a right press reaches the tool only once its release shows it was a click |
| `RightClick` | — | — | offer menu | — | `snaporca-xmh6` open: a right-click that only clears the sketch selection eats the offer | | `RightClick` | — | — | offer menu | — | `snaporca-xmh6` open: a right-click that only clears the sketch selection eats the offer |
| `Esc` | — | — | `escape()` ladder | — | one route whatever holds focus (`:4228-4231`) | | `Esc` | — | — | `escape()` ladder | — | one route whatever holds focus (`:4228-4231`) |
| `Del` / `Backspace` | — | `delete_selected_or_last_sketch_entity()` | char hook | — | `:4260` | | `Del` / `Backspace` | — | `delete_selected_or_last_sketch_entity()` | char hook | — | `:4260` |
+28 -7
View File
@@ -154,15 +154,18 @@ rather than to its end points: a press that wandered past the budget at any mome
navigation, even if it comes back to where it started, which is what stops a slow, careful navigation, even if it comes back to where it started, which is what stops a slow, careful
orbit from ending in a menu. There is no time budget — a gesture that means something different orbit from ending in a menu. There is no time budget — a gesture that means something different
when it is slow is exactly what the interaction charter rules out. The raycast uses the press when it is slow is exactly what the interaction charter rules out. The raycast uses the press
position, not the release. An armed sketch tool that already consumed the right position, not the release. The sketch tool sees a right press only once the release has shown it
button (to terminate a chain, say) declines to also open a menu, through a read-and-clear flag. was a click: the press itself goes to the camera, which may pan or orbit with that button, and the
Past either budget the event is navigation, and navigation does not transition the state canvas replays it to the tool on a stationary release. A tool that uses the click (to terminate a
machine. chain, say) keeps the menu closed. Past either budget the event is navigation, and navigation
does not transition the state machine.
Navigation itself is Prepare's: the camera reads the drag actions set in Preferences > Control Navigation itself is Prepare's: the camera reads the drag actions set in Preferences > Control
for each button. The left button is shared with picking, so a whole body is swept with a for each button, and in the Touchpad camera style a move with Alt held orbits and one with Shift
rectangle on plain left-drag only while no camera action is assigned to it, and with held pans, whatever tool is armed. The left button is shared with picking and drawing, so a tool
Shift+left-drag otherwise — Prepare's own rectangle selection. handle or a press that draws takes it first, as a gizmo does in Prepare; a whole body is swept
with a rectangle on plain left-drag only while no camera action is assigned to the left button,
and with Shift+left-drag otherwise — Prepare's own rectangle selection.
Entering a sketch changes three things at once so the mode is legible: a banner above the Entering a sketch changes three things at once so the mode is legible: a banner above the
canvas (a sibling of the canvas, not a child over it — on GTK a child window over a canvas (a sibling of the canvas, not a child over it — on GTK a child window over a
@@ -189,6 +192,15 @@ degenerate edges are left out (`GeometryEngine::display_edges`), and the polylin
once per shape, keyed by its `TShape`, because a recompute that leaves a body unchanged is the once per shape, keyed by its `TShape`, because a recompute that leaves a body unchanged is the
common case. common case.
While a feature card is open, its preview ghost is the whole model the candidate would produce,
drawn translucent over the bodies, so every face the feature leaves alone is in both at the same
depth. The ghost is drawn with a depth bias that pushes it back (`GLVolume::depth_bias`), so on a shared face
the body always wins instead of the two copies z-fighting, and the ghost shows only where the
result reaches past the bodies. Material a feature removes lies inside the old solid and would not
show at all, so the tools whose result mostly coincides with the body — Fillet/Chamfer, Draft,
Hole and the Mate hover — hide the bodies once the preview is valid and draw the result alone,
opaque.
## Showing what is selected ## Showing what is selected
A selection is drawn on the faces it names, never as a tint over the body: a translucent A selection is drawn on the faces it names, never as a tint over the body: a translucent
@@ -234,6 +246,15 @@ The tab is a page of Orca's main window and answers to the same settings as Prep
`DesignRowList` rather than a `wxTreeCtrl`, so each row carries its own actions — Edit, `DesignRowList` rather than a `wxTreeCtrl`, so each row carries its own actions — Edit,
Show/hide and Delete on a feature, Move, Show/hide and Delete on a body — and the eye shows Show/hide and Delete on a feature, Move, Show/hide and Delete on a body — and the eye shows
whether that row is hidden. whether that row is hidden.
- **Plates.** This is the one thing the tab does not follow. The canvas has a bed of its own at
the printer bed's home position, whichever plate Prepare has current, and a new document's
modeling origin is that bed's centre. A bed that followed the current plate would slide out
from under a design: the origin is fixed once per document, baked into every sketch plane and
saved in the recipe, while the current plate can change between visits. Commit to Plate does
not need it either, since the committed object is placed on an empty spot of the current
plate. What the canvas does read from the plate is moved onto its bed: the exclude areas, the
plate box the camera orbits about when nothing is picked (`GLCanvas3D::_current_plate_box`),
and the first view, which starts as a copy of Prepare's camera.
- **Viewport text.** The status line and the active tool's values are drawn by the canvas in - **Viewport text.** The status line and the active tool's values are drawn by the canvas in
its ImGui pass, so they go with the canvas: a top-level window over GL does not follow its its ImGui pass, so they go with the canvas: a top-level window over GL does not follow its
frame and was left floating over other applications. frame and was left floating over other applications.
+7
View File
@@ -310,6 +310,7 @@ GLVolume::GLVolume(float r, float g, float b, float a)
, force_native_color(false) , force_native_color(false)
, force_neutral_color(false) , force_neutral_color(false)
, force_sinking_contours(false) , force_sinking_contours(false)
, depth_bias(false)
, picking(false) , picking(false)
, tverts_range(0, size_t(-1)) , tverts_range(0, size_t(-1))
{ {
@@ -1332,11 +1333,17 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type,
shader->set_uniform("projection_matrix", projection_matrix); shader->set_uniform("projection_matrix", projection_matrix);
const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose();
shader->set_uniform("view_normal_matrix", view_normal_matrix); shader->set_uniform("view_normal_matrix", view_normal_matrix);
if (volume.first->depth_bias) {
glsafe(::glEnable(GL_POLYGON_OFFSET_FILL));
glsafe(::glPolygonOffset(1.0f, 1.0f));
}
//BBS: add outline related logic //BBS: add outline related logic
if (volume.first->selected && shader_can_outline && GUI::wxGetApp().show_outline()) if (volume.first->selected && shader_can_outline && GUI::wxGetApp().show_outline())
volume.first->render_with_outline(cnv_size); volume.first->render_with_outline(cnv_size);
else else
volume.first->render(); volume.first->render();
if (volume.first->depth_bias)
glsafe(::glDisable(GL_POLYGON_OFFSET_FILL));
#if ENABLE_ENVIRONMENT_MAP #if ENABLE_ENVIRONMENT_MAP
if (use_environment_texture) if (use_environment_texture)
+4
View File
@@ -232,6 +232,10 @@ public:
bool force_neutral_color : 1; bool force_neutral_color : 1;
// Whether or not to force rendering of sinking contours // Whether or not to force rendering of sinking contours
bool force_sinking_contours : 1; bool force_sinking_contours : 1;
// Orca: draw this volume with a positive depth bias (glPolygonOffset, pushed away from the
// camera), so on a surface it shares with another volume the other volume wins the depth
// test instead of z-fighting it
bool depth_bias : 1;
// Is render for picking // Is render for picking
bool picking : 1; bool picking : 1;
// slice error // slice error
+29 -16
View File
@@ -8,6 +8,7 @@
#include "slic3r/GUI/Camera.hpp" // N: look down the sketch plane normal #include "slic3r/GUI/Camera.hpp" // N: look down the sketch plane normal
#include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/PartPlate.hpp" // the current plate's origin: the first view's offset
#include "slic3r/GUI/ImGuiWrapper.hpp" #include "slic3r/GUI/ImGuiWrapper.hpp"
#include "slic3r/GUI/GLToolbar.hpp" #include "slic3r/GUI/GLToolbar.hpp"
#include "slic3r/GUI/Event.hpp" #include "slic3r/GUI/Event.hpp"
@@ -91,7 +92,7 @@ DesignCanvas::DesignCanvas(wxWindow* parent)
m_canvas->enable_plate_chrome(false); m_canvas->enable_plate_chrome(false);
m_canvas->enable_labels(false); m_canvas->enable_labels(false);
m_canvas->enable_sinking_contours(false); // they would be sliced from the plater's meshes m_canvas->enable_sinking_contours(false); // they would be sliced from the plater's meshes
m_canvas->set_axes_at_bed_center(true); // triad at bed centre = modeling origin m_canvas->set_design_canvas(true); // home-position bed, triad and CAD grid at the modeling origin
m_canvas->set_design_sketch_tool(&m_sketch_tool); m_canvas->set_design_sketch_tool(&m_sketch_tool);
m_sketch_tool.on_commit = [this](const SketchProfile& prof, const SketchPlane& pl) { m_sketch_tool.on_commit = [this](const SketchProfile& prof, const SketchPlane& pl) {
@@ -190,8 +191,11 @@ DesignCanvas::DesignCanvas(wxWindow* parent)
refresh_bed(); refresh_bed();
// The view this canvas opens on. Built lazily, on the way into the Design tab, so this // The view this canvas opens on. Built lazily, on the way into the Design tab, so this
// is the view the user is looking at right now. // is the view the user is looking at right now — moved off the current plate onto the
// Design bed, which stays at the printer bed's home whichever plate is current.
m_parked_camera = wxGetApp().plater()->get_camera(); m_parked_camera = wxGetApp().plater()->get_camera();
if (PartPlate* plate = wxGetApp().plater()->get_partplate_list().get_curr_plate())
m_parked_camera.translate_world(-plate->get_origin());
// Before any of this class's own Binds below: wx calls dynamically bound handlers in // Before any of this class's own Binds below: wx calls dynamically bound handlers in
// reverse order of binding, and GLCanvas3D swallows several events without skipping them — // reverse order of binding, and GLCanvas3D swallows several events without skipping them —
@@ -378,10 +382,14 @@ void DesignCanvas::reload(bool keep_view)
v->set_color(c); v->set_color(c);
} }
} else if (obj_idx == 1) { } else if (obj_idx == 1) {
// The ghost is normally a faint blue overlay on the visible body. In preview-only // The ghost is the whole resulting model, normally drawn as a faint blue overlay on
// mode it IS the result (base bodies hidden), so render it opaque so it reads as a // the visible bodies. Every face the feature leaves alone is in both, at the same
// finished solid rather than a see-through hint. // depth, so the ghost is drawn with a depth bias: the bodies win on those faces instead
// of the two copies z-fighting, and the ghost shows only where the result reaches past
// the bodies. In preview-only mode it IS the result (base bodies hidden), so render it
// opaque so it reads as a finished solid rather than a see-through hint.
v->set_color(m_body_hidden ? ColorRGBA(0.40f, 0.82f, 1.0f, 1.0f) : ghost); v->set_color(m_body_hidden ? ColorRGBA(0.40f, 0.82f, 1.0f, 1.0f) : ghost);
v->depth_bias = true;
} }
} }
@@ -550,6 +558,7 @@ void DesignCanvas::refresh_bed()
double printable_height = 100.0; double printable_height = 100.0;
const auto* ph_opt = config->opt<ConfigOptionFloat>("printable_height"); const auto* ph_opt = config->opt<ConfigOptionFloat>("printable_height");
if (ph_opt) printable_height = ph_opt->value; if (ph_opt) printable_height = ph_opt->value;
// No position: the Design bed stays at the printer bed's home, whichever plate is current.
m_bed.set_shape(bed_shape_opt->values, printable_height, {}, {}, "", false); // mainline added extruder_areas/heights params m_bed.set_shape(bed_shape_opt->values, printable_height, {}, {}, "", false); // mainline added extruder_areas/heights params
} }
@@ -1047,15 +1056,18 @@ void DesignCanvas::set_on_context_menu(std::function<void(const wxPoint&)> cb)
return; return;
m_ctx_bound = true; m_ctx_bound = true;
// Bound AFTER GLCanvas3D's own handlers, so this runs first and can consume the event. // Bound AFTER GLCanvas3D's own handlers, so this runs first and can consume the event.
// It only consumes when it actually opens the offer; every other right-click still falls // It only consumes when it actually opens the offer.
// through to the polyline-chain end and the move gizmo, which were there first. // Right-drag may pan or orbit (Preferences > Control). Without remembering where the press
// Right-drag pans. Without remembering where the press landed, every pan ended by popping // landed, every such drag ended by popping the offer over wherever the camera stopped — the
// the offer over wherever the camera stopped — the menu appearing as the reward for moving // menu appearing as the reward for moving the view. A right-click is the release of a
// the view. The offer is the release of a STATIONARY right-click (kCadRightClickDriftPx). // STATIONARY press (kCadRightClickDriftPx); only that reaches the sketch tool or the offer.
m_canvas_widget->Bind(wxEVT_RIGHT_DOWN, [this](wxMouseEvent& e) { m_canvas_widget->Bind(wxEVT_RIGHT_DOWN, [this](wxMouseEvent& e) {
m_ctx_press = e.GetPosition(); m_ctx_press = e.GetPosition();
m_ctx_travelled = false; m_ctx_travelled = false;
e.Skip(); // the canvas still needs the press to seed the orbit // Drop any press the tool still keeps (only a click's release takes it, so a pan's stays)
// before the canvas offers it this one, which ImGui may take instead.
m_sketch_tool.drop_right_click();
e.Skip(); // the canvas still needs the press to seed a pan or an orbit
}); });
m_canvas_widget->Bind(wxEVT_MOTION, [this](wxMouseEvent& e) { m_canvas_widget->Bind(wxEVT_MOTION, [this](wxMouseEvent& e) {
if (e.RightIsDown()) { if (e.RightIsDown()) {
@@ -1066,12 +1078,13 @@ void DesignCanvas::set_on_context_menu(std::function<void(const wxPoint&)> cb)
}); });
m_canvas_widget->Bind(wxEVT_RIGHT_UP, [this](wxMouseEvent& e) { m_canvas_widget->Bind(wxEVT_RIGHT_UP, [this](wxMouseEvent& e) {
const wxPoint d = e.GetPosition() - m_ctx_press; const wxPoint d = e.GetPosition() - m_ctx_press;
// Always read-and-clear, even when another guard already rules the offer out, or a // Click, or navigation? A press that travelled panned or orbited; one that did not, did not.
// terminator recorded under one condition would still be pending under the next.
const bool terminated = m_sketch_tool.take_right_consumed();
// Click, or navigation? A press that travelled orbited; one that did not, did not.
const bool is_click = !m_ctx_travelled && std::max(std::abs(d.x), std::abs(d.y)) <= kCadRightClickDriftPx; const bool is_click = !m_ctx_travelled && std::max(std::abs(d.x), std::abs(d.y)) <= kCadRightClickDriftPx;
if (m_on_context_menu && !terminated && !inline_busy() && is_click) { // Ending a chain or abandoning an anchor uses the click up.
const bool terminated = is_click && m_canvas && m_sketch_tool.take_right_click(*m_canvas);
if (terminated)
m_canvas->set_as_dirty(); // drawn by the canvas's own RightUp (e.Skip below) or at idle
else if (m_on_context_menu && !inline_busy() && is_click) {
// The menu belongs to what you POINTED AT — and pointing happened at the PRESS, not // The menu belongs to what you POINTED AT — and pointing happened at the PRESS, not
// at the release, so the raycast uses the press position. Within a 3 px budget the // at the release, so the raycast uses the press position. Within a 3 px budget the
// two are the same pixel in practice; using the press is what makes that a // two are the same pixel in practice; using the press is what makes that a
+3
View File
@@ -3,6 +3,7 @@
#include <vector> #include <vector>
#include "libslic3r/Point.hpp" #include "libslic3r/Point.hpp"
#include "libslic3r/BuildVolume.hpp"
#include "libslic3r/Color.hpp" #include "libslic3r/Color.hpp"
#include <utility> #include <utility>
#include <wx/colour.h> #include <wx/colour.h>
@@ -83,6 +84,8 @@ public:
void finish_sketch(); void finish_sketch();
bool is_sketching() const; bool is_sketching() const;
void refresh_bed(); // re-sync the bed to the current printer (call on tab activation) void refresh_bed(); // re-sync the bed to the current printer (call on tab activation)
// Centre of the Design bed: the printer bed at its home position, whichever plate is current.
Vec2d bed_center() const { return m_bed.build_volume().bed_center(); }
// The Camera is Plater-owned and shared with Prepare/Preview/Assemble; GLCanvas3D has no // The Camera is Plater-owned and shared with Prepare/Preview/Assemble; GLCanvas3D has no
// per-canvas camera, so every orbit here would otherwise overwrite what the editor tabs // per-canvas camera, so every orbit here would otherwise overwrite what the editor tabs
// show. Exactly one of the two views is live at a time, so entering and leaving are the // show. Exactly one of the two views is live at a time, so entering and leaving are the
+10 -5
View File
@@ -104,7 +104,6 @@
#include "libslic3r/Model.hpp" #include "libslic3r/Model.hpp"
#include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/Plater.hpp"
#include "libslic3r/BuildVolume.hpp"
#include "slic3r/GUI/MainFrame.hpp" #include "slic3r/GUI/MainFrame.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/GUI/Shortcuts.hpp" #include "slic3r/GUI/Shortcuts.hpp"
@@ -7345,8 +7344,12 @@ void DesignPanel::hydrate_from_model()
// that has none yet. A design in progress keeps its origin: its sketches have it baked into // that has none yet. A design in progress keeps its origin: its sketches have it baked into
// their planes, so moving it under them (a printer change between visits) would slide the // their planes, so moving it under them (a printer change between visits) would slide the
// datum planes off the geometry. A loaded project brings its own (load_recipe). // datum planes off the geometry. A loaded project brings its own (load_recipe).
// The centre of the Design bed, not the plater's: the plater moves its bed onto the current
// plate, so on plate 2+ the origin would land one plate stride away from the bed drawn here.
// Re-synced first because the control socket calls this without on_tab_shown().
if (!m_doc.origin_from_recipe) { if (!m_doc.origin_from_recipe) {
const Vec2d bc = plater->build_volume().bed_center(); m_viewport->refresh_bed();
const Vec2d bc = m_viewport->bed_center();
m_doc.modeling_origin = Vec3d(bc.x(), bc.y(), 0.0); m_doc.modeling_origin = Vec3d(bc.x(), bc.y(), 0.0);
// A document started here takes the weld rule from the preference; a loaded one // A document started here takes the weld rule from the preference; a loaded one
// brings its own (load_recipe). // brings its own (load_recipe).
@@ -11185,10 +11188,12 @@ void DesignPanel::refresh_preview()
// Confirm so the user sees the gate before clicking; the red status says why. // Confirm so the user sees the gate before clicking; the red status says why.
m_candidate_ok = ok; m_candidate_ok = ok;
update_confirm_button(); update_confirm_button();
// Fillet/Chamfer/Draft: once the target edge/face yields a valid result, show ONLY the // Fillet/Chamfer/Draft/Hole: once the target edge/face yields a valid result, show ONLY the
// preview (hide the base bodies) so the user sees the finished shape, not the old solid // preview (hide the base bodies) so the user sees the finished shape, not the old solid
// doubled with the ghost. Before a valid pick the body stays visible so it can be picked. // doubled with the ghost. A hole's cut lies inside the old solid, so without this the body
m_viewport->set_body_hidden((m_active == Tool::Dressup || m_active == Tool::Draft) && ok); // would hide it entirely. Before a valid pick the body stays visible so it can be picked.
m_viewport->set_body_hidden((m_active == Tool::Dressup || m_active == Tool::Draft ||
m_active == Tool::Hole) && ok);
m_status->Refresh(); m_status->Refresh();
// Refresh the in-canvas Extrude depth arrow (self-gates: only while the Extrude card is open). // Refresh the in-canvas Extrude depth arrow (self-gates: only while the Extrude card is open).
+25 -13
View File
@@ -10315,14 +10315,14 @@ std::vector<int> DesignSketchTool::connected_loop(int seed) const
// the offer was excluded in sketch mode wholesale so a right-click could end a polyline chain, // the offer was excluded in sketch mode wholesale so a right-click could end a polyline chain,
// abandon an anchor or exit a tool. That made every sketch row in the atlas unreachable. // abandon an anchor or exit a tool. That made every sketch row in the atlas unreachable.
// The honest test is not "which mode are we in" but "did the tool actually USE this right-click", // The honest test is not "which mode are we in" but "did the tool actually USE this right-click",
// and only the tool knows. Wrapping on_mouse records that once, for every terminator, instead of // and only the tool knows: take_right_click returns it for every terminator, from the
// threading a flag through the twenty-odd sites that consume a RightDown. // twenty-odd sites that consume a RightDown.
// Right-click abandons the anchor a draw tool has down. With NOTHING down there is nothing to // Right-click abandons the anchor a draw tool has down. With NOTHING down there is nothing to
// abandon — and consuming the click anyway made the offer unreachable from every armed draw tool: // abandon — and consuming the click anyway made the offer unreachable from every armed draw tool:
// on_mouse records the consumption in m_right_consumed and DesignCanvas's RIGHT_UP handler // DesignCanvas's RIGHT_UP handler suppresses the menu whenever the tool used the click, so
// suppresses the menu whenever it is set, so right-click became a no-op that also hid the one door // right-click became a no-op that also hid the one door to half the vocabulary (47 of 86 verbs
// to half the vocabulary (47 of 86 verbs have no shortcut). Measured on the rig: with Line armed, // have no shortcut). Measured on the rig: with Line armed, two right-clicks in a row produced no
// two right-clicks in a row produced no menu and no tool change; only Escape freed it. // menu and no tool change; only Escape freed it.
// Same rule as xmh6, which said it for the selection: clearing nothing is not a gesture // Same rule as xmh6, which said it for the selection: clearing nothing is not a gesture
// terminator. ghcz. // terminator. ghcz.
bool DesignSketchTool::right_abandon() bool DesignSketchTool::right_abandon()
@@ -10334,12 +10334,24 @@ bool DesignSketchTool::right_abandon()
return true; return true;
} }
// The camera follows Preferences > Control here as in Prepare, so no tool may take a gesture the
// camera owns. A right press may start whatever drag action the right button is given, and
// whether it did is known only at the release: ending a chain on the press made every pan or
// orbit started there end the chain too. The press goes to the camera and is kept;
// DesignCanvas's RIGHT_UP handler replays it through take_right_click when it was a click.
bool DesignSketchTool::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) bool DesignSketchTool::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas)
{ {
const bool consumed = on_mouse_impl(evt, canvas); if (evt.RightDown()) {
if (evt.RightDown()) m_right_press = evt;
m_right_consumed = consumed; return false;
return consumed; }
return on_mouse_impl(evt, canvas);
}
bool DesignSketchTool::take_right_click(GLCanvas3D& canvas)
{
auto press = std::exchange(m_right_press, {});
return press && on_mouse_impl(*press, canvas);
} }
bool DesignSketchTool::on_mouse_impl(wxMouseEvent& evt, GLCanvas3D& canvas) bool DesignSketchTool::on_mouse_impl(wxMouseEvent& evt, GLCanvas3D& canvas)
@@ -11288,9 +11300,9 @@ bool DesignSketchTool::on_mouse_impl(wxMouseEvent& evt, GLCanvas3D& canvas)
return true; return true;
} }
if (evt.RightDown()) { if (evt.RightDown()) {
// Hand the click back (return false) so the offer opens: the m_right_consumed flag // Hand the click back (return false) so the offer opens: take_right_click returns
// this return value feeds means "the tool USED this right-click", and a plain // this value as "the tool USED this right-click", and a plain right-click in Select
// right-click in Select mode is not a gesture terminator. // mode is not a gesture terminator.
// //
// But do NOT drop the selection on the way out. The offer menu describes WHAT IS // But do NOT drop the selection on the way out. The offer menu describes WHAT IS
// SELECTED, so clearing first guaranteed it could only ever describe nothing: select // SELECTED, so clearing first guaranteed it could only ever describe nothing: select
+7 -6
View File
@@ -9,14 +9,15 @@
#include "slic3r/GUI/GLSelectionRectangle.hpp" // left-drag rubber band over the committed bodies #include "slic3r/GUI/GLSelectionRectangle.hpp" // left-drag rubber band over the committed bodies
#include <Eigen/Core> #include <Eigen/Core>
#include <cstddef> #include <cstddef>
#include <wx/event.h>
#include <functional> #include <functional>
#include <optional>
#include "libslic3r/Color.hpp" #include "libslic3r/Color.hpp"
#include <math.h> #include <math.h>
#include <vector> #include <vector>
#include <string> #include <string>
#include <utility> #include <utility>
class wxMouseEvent;
class wxPoint; class wxPoint;
namespace Slic3r { namespace Slic3r {
@@ -149,10 +150,10 @@ public:
// Right-click on a draw tool: true when an in-progress anchor was abandoned, false when // Right-click on a draw tool: true when an in-progress anchor was abandoned, false when
// there was nothing to abandon — and false is what lets the offer menu open. ghcz. // there was nothing to abandon — and false is what lets the offer menu open. ghcz.
bool right_abandon(); bool right_abandon();
// True if the LAST right-press was consumed as a gesture terminator (end a polyline chain, // Replays and clears the right press on_mouse kept for the camera: true if the tool used it as
// abandon an anchor, exit a tool). Read-and-clear: the canvas asks on the matching release to // a gesture terminator (end a polyline chain, abandon an anchor), so no offer opens.
// decide whether that right-click was the user's, in which case it opens the offer. bool take_right_click(GLCanvas3D& canvas);
bool take_right_consumed() { const bool b = m_right_consumed; m_right_consumed = false; return b; } void drop_right_click() { m_right_press.reset(); }
void render(GLCanvas3D& canvas); void render(GLCanvas3D& canvas);
// The in-canvas value field, drawn by render() before any early return. Owned by // The in-canvas value field, drawn by render() before any early return. Owned by
// DesignCanvas; null until it sets it. Not a window — see SketchInlineEditor.hpp. // DesignCanvas; null until it sets it. Not a window — see SketchInlineEditor.hpp.
@@ -1274,7 +1275,7 @@ private:
void hit_display_sketch(const DisplaySketch& d, const Vec2d& p, double tol, void hit_display_sketch(const DisplaySketch& d, const Vec2d& p, double tol,
int& edge_feat, int& edge_reg, int& edge_ent, int& edge_feat, int& edge_reg, int& edge_ent,
double& edge_d, int& face_feat, int& face_reg) const; double& edge_d, int& face_feat, int& face_reg) const;
bool m_right_consumed{false}; // last RightDown was a gesture terminator, not a menu std::optional<wxMouseEvent> m_right_press; // right press not yet known to be a click
bool m_escalate_repick{true}; // re-picking the same sub-element takes the whole body bool m_escalate_repick{true}; // re-picking the same sub-element takes the whole body
void render_solid_highlight(); void render_solid_highlight();
// The above's edge and vertex highlight, from explicit arguments, so the committed selection // The above's edge and vertex highlight, from explicit arguments, so the committed selection
+38 -13
View File
@@ -1875,7 +1875,7 @@ BoundingBoxf3 GLCanvas3D::volumes_bounding_box(bool current_plate_only) const
bool is_limit = m_canvas_type != ECanvasType::CanvasAssembleView; bool is_limit = m_canvas_type != ECanvasType::CanvasAssembleView;
if (is_limit) { if (is_limit) {
if (current_plate_only) { if (current_plate_only) {
expand_part_plate_list_box = wxGetApp().plater()->get_partplate_list().get_curr_plate()->get_bounding_box(); expand_part_plate_list_box = _current_plate_box();
} else { } else {
auto plate_list_box = wxGetApp().plater()->get_partplate_list().get_bounding_box(); auto plate_list_box = wxGetApp().plater()->get_partplate_list().get_bounding_box();
auto horizontal_radius = 0.5 * sqrt(std::pow(plate_list_box.min[0] - plate_list_box.max[0], 2) + std::pow(plate_list_box.min[1] - plate_list_box.max[1], 2)); auto horizontal_radius = 0.5 * sqrt(std::pow(plate_list_box.min[0] - plate_list_box.max[0], 2) + std::pow(plate_list_box.min[1] - plate_list_box.max[1], 2));
@@ -2395,7 +2395,7 @@ void GLCanvas3D::_render_scene(const Camera& camera, const Size& cnv_size)
} }
else if (gizmo_type == GLGizmosManager::BrimEars && !camera.is_looking_downward()) else if (gizmo_type == GLGizmosManager::BrimEars && !camera.is_looking_downward())
show_grid = false; show_grid = false;
if (m_axes_at_bed_center) if (m_design_canvas)
// Design tab: the plate grid is generated from the plate's front-left corner, so it // Design tab: the plate grid is generated from the plate's front-left corner, so it
// floats mid-cell under the modeling-origin triad. Suppress it here; a CAD grid centred // floats mid-cell under the modeling-origin triad. Suppress it here; a CAD grid centred
// on the origin is rendered in its place (see _render_cad_grid). // on the origin is rendered in its place (see _render_cad_grid).
@@ -2411,7 +2411,7 @@ void GLCanvas3D::_render_scene(const Camera& camera, const Size& cnv_size)
m_frame_profiler.mark("bed"); m_frame_profiler.mark("bed");
if (show_bed) //BBS: add outline logic if (show_bed) //BBS: add outline logic
_render_platelist(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), only_current, only_body, hover_id, true, show_grid); _render_platelist(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), only_current, only_body, hover_id, true, show_grid);
if (m_axes_at_bed_center && show_bed) if (m_design_canvas && show_bed)
// Design tab: replace the plate's corner-origin grid with the origin-centred CAD grid. // Design tab: replace the plate's corner-origin grid with the origin-centred CAD grid.
_render_cad_grid(camera.get_view_matrix(), camera.get_projection_matrix()); _render_cad_grid(camera.get_view_matrix(), camera.get_projection_matrix());
m_frame_profiler.mark("plates"); m_frame_profiler.mark("plates");
@@ -4311,7 +4311,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
} }
if (evt.LeftDown() && m_canvas != nullptr) if (evt.LeftDown() && m_canvas != nullptr)
m_canvas->SetFocus(); // grab keyboard focus so Delete/keys reach this canvas m_canvas->SetFocus(); // grab keyboard focus so Delete/keys reach this canvas
if (m_design_sketch_tool->on_mouse(evt, *this)) { // A Touchpad-style orbit or pan (a plain move with Alt or Shift held) is the camera's,
// whatever the tool would make of the move. Only a drag consults the button mappings.
const bool camera_move = evt.Moving() && (is_camera_rotate(evt, {}) || is_camera_pan(evt, {}));
if (!camera_move && m_design_sketch_tool->on_mouse(evt, *this)) {
m_dirty = true; m_dirty = true;
render(); // force an immediate redraw so the sketch overlay updates live render(); // force an immediate redraw so the sketch overlay updates live
return; return;
@@ -8251,7 +8254,7 @@ void GLCanvas3D::_render_bed(const Transform3d& view_matrix, const Transform3d&
*/ */
//bool show_texture = true; //bool show_texture = true;
//BBS set axes mode //BBS set axes mode
if (m_axes_at_bed_center) { if (m_design_canvas) {
// Design tab: triad at the bed centre = modeling origin (set every frame because // Design tab: triad at the bed centre = modeling origin (set every frame because
// set_shape/set_axes_mode otherwise reset it to the bed corner). // set_shape/set_axes_mode otherwise reset it to the bed corner).
const Vec2d bc = m_bed.build_volume().bed_center(); const Vec2d bc = m_bed.build_volume().bed_center();
@@ -8264,7 +8267,30 @@ void GLCanvas3D::_render_bed(const Transform3d& view_matrix, const Transform3d&
void GLCanvas3D::_render_platelist(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool only_current, bool only_body, int hover_id, bool render_cali, bool show_grid) void GLCanvas3D::_render_platelist(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool only_current, bool only_body, int hover_id, bool render_cali, bool show_grid)
{ {
wxGetApp().plater()->get_partplate_list().render(view_matrix, projection_matrix, bottom, only_current, only_body, hover_id, render_cali, show_grid, !m_plate_chrome_enabled); PartPlateList& plate_list = wxGetApp().plater()->get_partplate_list();
// Design tab: its bed stays at the printer bed's home whichever plate is current, so the
// current plate's exclude areas are moved from that plate onto it.
PartPlate* curr_plate = plate_list.get_curr_plate();
const Transform3d plate_view_matrix = m_design_canvas && curr_plate != nullptr ?
Transform3d(view_matrix * Geometry::translation_transform(-curr_plate->get_origin())) : view_matrix;
plate_list.render(plate_view_matrix, projection_matrix, bottom, only_current, only_body, hover_id, render_cali, show_grid, !m_plate_chrome_enabled);
}
BoundingBoxf3 GLCanvas3D::_current_plate_box() const
{
// Design tab: its own bed stands in for the current plate (see _render_platelist).
const BuildVolume& build_volume = m_bed.build_volume();
if (m_design_canvas && build_volume.valid()) {
// Flat at z = 0 like the plate's own box. Merged, as PartPlate builds it: the min/max
// constructor leaves a flat box undefined.
const BoundingBoxf bb = build_volume.bounding_volume2d();
BoundingBoxf3 box;
box.merge(Vec3d(bb.min.x(), bb.min.y(), 0.));
box.merge(Vec3d(bb.max.x(), bb.max.y(), 0.));
return box;
}
PartPlate* curr_plate = wxGetApp().plater()->get_partplate_list().get_curr_plate();
return curr_plate != nullptr ? curr_plate->get_bounding_box() : BoundingBoxf3();
} }
// Design tab: CAD grid on the bed plane, drawn in place of the plate's corner-origin grid. // Design tab: CAD grid on the bed plane, drawn in place of the plate's corner-origin grid.
@@ -11058,10 +11084,9 @@ std::optional<Vec3d> GLCanvas3D::get_camera_orbit_target(ECameraNavigationType n
{ {
// Orca: Centralize the pre-existing pivot rules so orbiting and pan fallback cannot // Orca: Centralize the pre-existing pivot rules so orbiting and pan fallback cannot
// choose different reference depths for the same canvas and active tool. // choose different reference depths for the same canvas and active tool.
PartPlate* current_plate = wxGetApp().plater()->get_partplate_list().get_curr_plate(); const BoundingBoxf3 plate_box = _current_plate_box();
if (navigation_type == ECameraNavigationType::Gesture) if (navigation_type == ECameraNavigationType::Gesture)
return current_plate == nullptr ? std::nullopt : return plate_box.defined ? std::make_optional(plate_box.center()) : std::nullopt;
std::make_optional(current_plate->get_bounding_box().center());
const GLGizmosManager::EType gizmo_type = m_gizmos.get_current_type(); const GLGizmosManager::EType gizmo_type = m_gizmos.get_current_type();
const bool use_scene_target = m_canvas_type == ECanvasType::CanvasAssembleView || const bool use_scene_target = m_canvas_type == ECanvasType::CanvasAssembleView ||
@@ -11081,15 +11106,15 @@ std::optional<Vec3d> GLCanvas3D::get_camera_orbit_target(ECameraNavigationType n
Vec3d target = Vec3d::Zero(); Vec3d target = Vec3d::Zero();
if (m_canvas_type == ECanvasType::CanvasPreview) { if (m_canvas_type == ECanvasType::CanvasPreview) {
if (current_plate != nullptr) if (plate_box.defined)
target = current_plate->get_bounding_box().center(); target = plate_box.center();
} else if (!m_selection.is_empty()) { } else if (!m_selection.is_empty()) {
target = m_selection.get_bounding_box().center(); target = m_selection.get_bounding_box().center();
} else { } else {
// Orca: Match regular mouse orbit: objects on the active plate, then the plate itself. // Orca: Match regular mouse orbit: objects on the active plate, then the plate itself.
BoundingBoxf3 bbox = volumes_bounding_box(true); BoundingBoxf3 bbox = volumes_bounding_box(true);
if (!bbox.defined && current_plate != nullptr) if (!bbox.defined)
bbox = current_plate->get_bounding_box(); bbox = plate_box;
if (bbox.defined) if (bbox.defined)
target = bbox.center(); target = bbox.center();
} }
+10 -6
View File
@@ -604,9 +604,11 @@ private:
GLToolbar* m_collapse_toolbar{nullptr}; GLToolbar* m_collapse_toolbar{nullptr};
std::function<CollapseSide()> m_collapse_side; std::function<CollapseSide()> m_collapse_side;
bool m_plate_chrome_enabled{true}; bool m_plate_chrome_enabled{true};
// Design tab: render the world-axis triad at the bed centre (= modeling origin) instead of // This canvas is the Design tab's. Its bed stays at the printer bed's home whichever plate is
// the bed corner. Default false preserves the main editor's corner triad. // current, with the world-axis triad and a CAD grid at the bed centre (= modeling origin) in
bool m_axes_at_bed_center{false}; // place of the corner triad and the plate grid, and the plate data it reads (exclude areas,
// the current plate's box) moved onto that bed. Default false leaves the editor tabs untouched.
bool m_design_canvas{false};
// Design tab: draw the printer bed and its plate grid at all. Default true, so the // Design tab: draw the printer bed and its plate grid at all. Default true, so the
// main editor is untouched; the Design tab lets the user hide it to model without a bed. // main editor is untouched; the Design tab lets the user hide it to model without a bed.
bool m_show_bed{true}; bool m_show_bed{true};
@@ -1025,7 +1027,7 @@ public:
// initialized, which loads the toolbar's background. // initialized, which loads the toolbar's background.
void set_collapse_toolbar(GLToolbar* toolbar, std::function<CollapseSide()> side); void set_collapse_toolbar(GLToolbar* toolbar, std::function<CollapseSide()> side);
void enable_plate_chrome(bool enable); void enable_plate_chrome(bool enable);
void set_axes_at_bed_center(bool b) { m_axes_at_bed_center = b; } void set_design_canvas(bool b) { m_design_canvas = b; }
void set_show_bed(bool b) { m_show_bed = b; } void set_show_bed(bool b) { m_show_bed = b; }
bool get_show_bed() const { return m_show_bed; } bool get_show_bed() const { return m_show_bed; }
void enable_sinking_contours(bool enable) { m_sinking_contours_enabled = enable; } void enable_sinking_contours(bool enable) { m_sinking_contours_enabled = enable; }
@@ -1441,9 +1443,11 @@ private:
void _render_shadows(const Transform3d& view_matrix, const Transform3d& projection_matrix); void _render_shadows(const Transform3d& view_matrix, const Transform3d& projection_matrix);
//BBS: add part plate related logic //BBS: add part plate related logic
void _render_platelist(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool only_current, bool only_body = false, int hover_id = -1, bool render_cali = false, bool show_grid = true); void _render_platelist(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool only_current, bool only_body = false, int hover_id = -1, bool render_cali = false, bool show_grid = true);
// The current plate's box (XY, at z = 0); in the Design tab, its own bed's.
BoundingBoxf3 _current_plate_box() const;
// Design tab: draw the CAD grid (minor 10 mm + major 50 mm) in place of the plate's // Design tab: draw the CAD grid (minor 10 mm + major 50 mm) in place of the plate's
// corner-origin grid when the axes sit at the bed centre (modeling origin). Rebuilds its // corner-origin grid, centred on the modeling origin. Rebuilds its GLModels lazily, only
// GLModels lazily, only when the bed shape changed. // when the bed shape changed.
void _render_cad_grid(const Transform3d& view_matrix, const Transform3d& projection_matrix); void _render_cad_grid(const Transform3d& view_matrix, const Transform3d& projection_matrix);
//BBS: add outline drawing logic //BBS: add outline drawing logic
void _render_objects(GLVolumeCollection::ERenderType type, bool with_outline = true); void _render_objects(GLVolumeCollection::ERenderType type, bool with_outline = true);