mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-06 15:21:09 +00:00
Open the Design tab's sketches only once their plane is picked
Sketch on a picked flat face or reference plane opens the sketch on it at once. With nothing picked it no longer enters sketch mode: the reference planes and axes come up, and the plane or flat face clicked next opens the sketch and puts them away. Esc or Cancel leaves without one. A picked plane is used up by the sketch on it and dropped by Esc or a click on nothing, the plane prompt is no longer replaced by a stale tool hint, and clicking the face a sketch was just cancelled on picks that face again rather than the whole body.
This commit is contained in:
+11
-3
@@ -135,7 +135,7 @@ contract between them is stated in code rather than spread across handlers.
|
||||
| --- | --- | --- |
|
||||
| `Transient` | a value field or a popup menu | closes it; the tool stays armed |
|
||||
| `Gesture` | an uncommitted delta — an entity being drawn, a body being dragged | reverts it; committed work is untouched |
|
||||
| `Tool` | a feature card, an armed sketch tool, a constrain session | exits it; drawn entities survive |
|
||||
| `Tool` | a feature card, Sketch waiting for its plane, an armed sketch tool, a constrain session | exits it; drawn entities survive |
|
||||
| `Idle` | nothing transient | clears the selection, a Feature tree or Bodies row included; leaves a sketch session only if it is empty |
|
||||
|
||||
`cad_escape_level()` is a `constexpr` free function over a POD of four booleans rather than a
|
||||
@@ -181,10 +181,18 @@ canvas (a sibling of the canvas, not a child over it — on GTK a child window o
|
||||
a plate grid is never read as a sketch grid, and `N` to look normal to the plane. Code that
|
||||
changes any of the three belongs with a change to this section.
|
||||
|
||||
Sketch mode is never entered without a plane under it, so the banner, the sketch keys and the
|
||||
sketch offer always have a session to act on. Sketch on a picked flat face or reference plane opens
|
||||
the session on it at once. With nothing picked it stays in Feature mode and waits for one — an
|
||||
armed `Tool`, left with `Esc` or ✗, and ended by anything that starts another edit — and the
|
||||
reference plane or flat face clicked next opens the session. A picked plane is a selection like a
|
||||
face: the sketch on it uses it up, and `Esc` or a click on nothing lets go of it, so a plane that
|
||||
can no longer be seen never decides where the next sketch goes.
|
||||
|
||||
The reference planes — XY, XZ and YZ through the modeling origin, with their half-axes — are
|
||||
drawn on demand, because three translucent squares over every model are noise once they are not
|
||||
the thing being picked. Sketch mode brings them up, since choosing the sketch plane is exactly
|
||||
when they are picked; a live session draws none, and Finish or Cancel takes them away. The
|
||||
the thing being picked. Sketch brings them up while it waits for a plane, which is exactly when
|
||||
they are picked, and the session the pick opens takes them away; a live session draws none. The
|
||||
Feature tree's Origin row, pinned above the features and never removable, keeps them up outside a
|
||||
sketch. Its state is a view preference in AppConfig rather than part of the recipe, so it costs
|
||||
the project format nothing. The Plane tool keeps its own rule: the planes and the datums as Offset
|
||||
|
||||
@@ -480,13 +480,14 @@ def type_into_open_field(value, mark):
|
||||
def enter_sketch(timeout=180):
|
||||
"""Open a real sketch on a real plane, and PROVE it with the socket before drawing anything.
|
||||
|
||||
THE SEQUENCE MATTERS AND IT IS NOT OBVIOUS. The reference planes are hidden until Shift+S
|
||||
enters sketch MODE, so the plane is clicked AFTER it. Entering with a plane an earlier rung
|
||||
chose pops the plane offer at once, and a click outside a popup menu only closes it, so the
|
||||
plane is clicked twice, apart: the second click always lands on the plane. No Escape — with
|
||||
no offer up, Escape in an empty sketch leaves it. Then a tool letter starts the session.
|
||||
check-gui-sketching.py does the same steps. This ladder once did two of them — Design tab,
|
||||
then Shift+S — and went straight to the tool letters.
|
||||
THE SEQUENCE MATTERS AND IT IS NOT OBVIOUS. The reference planes are hidden until Shift+S,
|
||||
with nothing picked, brings them up to choose from, so the plane is clicked AFTER it. That
|
||||
click opens the session on the plane and pops the tool offer, and a click outside a popup
|
||||
menu only closes it, so the spot is clicked twice, apart: the second click closes the offer,
|
||||
and inside the session it is empty canvas. No Escape — with no offer up, Escape in an empty
|
||||
sketch leaves it. Then a tool letter arms the tool. check-gui-sketching.py does the same
|
||||
steps. This ladder once did two of them — Design tab, then Shift+S — and went straight to
|
||||
the tool letters.
|
||||
|
||||
That intermediate state is the trap. `is_sketching` reads 1, every tool key is accepted and
|
||||
traced, and not one click draws anything, because there is no plane under them. The ladder
|
||||
@@ -502,10 +503,10 @@ def enter_sketch(timeout=180):
|
||||
click(132, 53) # Design tab
|
||||
time.sleep(2.0)
|
||||
dismiss_modals()
|
||||
key("shift+s", 1.0) # sketch mode brings the reference planes up
|
||||
click(*PLANE_PX, pause=0.8) # closes the offer if an earlier rung's plane popped it...
|
||||
click(*PLANE_PX) # ...so this is the click that picks the plane IN THE VIEWPORT
|
||||
key("p", 0.6) # any sketch tool starts the session on that plane
|
||||
key("shift+s", 1.0) # nothing picked: the reference planes come up
|
||||
click(*PLANE_PX, pause=0.8) # picks the plane IN THE VIEWPORT: session open, offer up...
|
||||
click(*PLANE_PX) # ...and this closes the offer
|
||||
key("p", 0.6) # arms Point in the session
|
||||
if try_call("sketch_describe") is not None:
|
||||
# NO Escape here. Every rung already opens with one to drop whatever tool the last
|
||||
# one left armed, and Escape in the Design tab walks a LIFO: first press drops the
|
||||
@@ -520,7 +521,7 @@ def enter_sketch(timeout=180):
|
||||
|
||||
# tool key, the clicks that draw it, and one distinct value per queued field. The values are
|
||||
# deliberately nothing like the as-drawn size, so a committed prefill cannot coincide with them.
|
||||
# Where the plane label sits in the viewport once sketch mode has brought the planes up. Same
|
||||
# Where the plane label sits in the viewport once Shift+S has brought the planes up. Same
|
||||
# constant the gesture ladder uses; it is a label on the 3D view, not a widget, so it moves only
|
||||
# if the camera does.
|
||||
PLANE_PX = (913, 359)
|
||||
|
||||
@@ -268,16 +268,17 @@ def reset_document():
|
||||
def enter_sketch(tool_key, plane_px=(913, 359)):
|
||||
"""Enter a sketch the way the design law says: pick the plane in the viewport, then the tool.
|
||||
|
||||
Shift+S enters sketch MODE, which brings the reference planes up; the click picks one; the
|
||||
tool letter then starts the session on it. Entering with a plane an earlier rung chose pops
|
||||
the offer at once, and a click outside a popup menu only closes it, so the plane is clicked
|
||||
twice, apart. No Escape: with no offer up, Escape in an empty sketch leaves it. All of it is
|
||||
real input — nothing here goes through the socket.
|
||||
leave_sketch()'s Escapes let go of every pick, so Shift+S finds nothing to sketch on and only
|
||||
brings the reference planes up; the click on one opens the session on it and pops the tool
|
||||
offer. A click outside a popup menu only closes it, so the same spot is clicked again, apart:
|
||||
inside the session it is empty canvas, so it selects nothing if the offer is slow to appear.
|
||||
No Escape: with no offer up, Escape in an empty sketch leaves it. The tool letter then arms
|
||||
the tool. All of it is real input — nothing here goes through the socket.
|
||||
"""
|
||||
leave_sketch()
|
||||
key("shift+s", 0.8)
|
||||
click(*plane_px, pause=0.8) # closes the offer if an earlier rung's plane popped it...
|
||||
click(*plane_px) # ...so this is the click that picks the plane
|
||||
click(*plane_px, pause=0.8) # picks the plane: the session opens and the offer pops...
|
||||
click(*plane_px) # ...and this closes the offer
|
||||
key("p", 0.6)
|
||||
if try_call("sketch_describe") is None:
|
||||
shot("/shots/gl-enter-failed.png")
|
||||
|
||||
@@ -229,11 +229,11 @@
|
||||
"none"
|
||||
],
|
||||
"needs": {},
|
||||
"refusal": "Click a face or a reference plane in the viewport, then a sketch tool",
|
||||
"refusal": "Select a flat face or a reference plane to sketch on",
|
||||
"gui": true,
|
||||
"action": "key:S+S",
|
||||
"icon": "design_sketch",
|
||||
"hint": "Click a face or a reference plane, then pick a drawing tool"
|
||||
"hint": "Sketch on the selected flat face or plane, or click one next"
|
||||
},
|
||||
{
|
||||
"id": "extrude",
|
||||
|
||||
@@ -481,13 +481,6 @@ void DesignCanvas::begin_sketch(const SketchPlane& plane, DesignSketchTool::Mode
|
||||
if (m_canvas_widget) m_canvas_widget->Refresh();
|
||||
}
|
||||
|
||||
void DesignCanvas::set_sketch_plane(const SketchPlane& plane)
|
||||
{
|
||||
m_sketch_tool.set_plane(plane); // keeps the 2D entities; only the carrier plane changes
|
||||
if (m_canvas) m_canvas->set_as_dirty();
|
||||
if (m_canvas_widget) m_canvas_widget->Refresh();
|
||||
}
|
||||
|
||||
void DesignCanvas::edit_sketch(const std::vector<SketchEntity>& entities,
|
||||
const std::vector<SketchEntityConstraintDef>& constraints,
|
||||
const SketchPlane& plane)
|
||||
|
||||
@@ -68,7 +68,6 @@ public:
|
||||
const std::vector<SketchEntityConstraintDef>& constraints,
|
||||
const SketchPlane& plane);
|
||||
void set_sketch_tool(DesignSketchTool::Mode mode);
|
||||
void set_sketch_plane(const SketchPlane& plane); // re-plane the live sketch when a reference plane is clicked in 3D
|
||||
void set_sketch_construction(bool c);
|
||||
// Flip the sketch selection between construction and real geometry; returns the
|
||||
// number of entities changed (0 = nothing selected, caller falls back to the mode).
|
||||
|
||||
@@ -99,7 +99,7 @@ static const bool kOfferRowFlat[] = {
|
||||
};
|
||||
|
||||
static const OfferVerb kOfferVerbs[] = {
|
||||
{"sketch", L("Sketch"), 0, "Shift+S", "key:S+S", L("Click a face or a reference plane in the viewport, then a sketch tool"), 0x00000403u, 0, 0, false, false, nullptr, "design_sketch", L("Click a face or a reference plane, then pick a drawing tool")},
|
||||
{"sketch", L("Sketch"), 0, "Shift+S", "key:S+S", L("Select a flat face or a reference plane to sketch on"), 0x00000403u, 0, 0, false, false, nullptr, "design_sketch", L("Sketch on the selected flat face or plane, or click one next")},
|
||||
{"extrude", L("Extrude"), 1, "Shift+E", "key:S+E", L("Create a sketch, or pick a solid face, first"), 0x00004002u, 0, 0, false, false, nullptr, "design_extrude", L("Extrude a sketch profile, or push/pull a picked face")},
|
||||
{"revolve", L("Revolve"), 1, "Shift+R", "key:S+R", L("Create a sketch profile to revolve first"), 0x00004000u, 0, 0, false, false, nullptr, "design_revolve", L("Revolve a profile about an axis")},
|
||||
{"sweep", L("Sweep"), 1, "Shift+W", "key:S+W", L("Create a profile sketch to sweep first"), 0x00004000u, 0, 2, false, false, nullptr, "design_sweep", L("Sweep a profile along a path")},
|
||||
|
||||
@@ -302,6 +302,12 @@ static wxString origin_toggle_text(bool shown)
|
||||
return shown ? _L("Hide reference planes and axes") : _L("Show reference planes and axes");
|
||||
}
|
||||
|
||||
// What Sketch asks for while it waits for a plane (DesignPanel::start_sketch).
|
||||
static wxString sketch_plane_prompt()
|
||||
{
|
||||
return _L("Click a reference plane or a flat face to sketch on it — Esc cancels");
|
||||
}
|
||||
|
||||
// Prepare outlines every numeric field (rounded, #4A4A51 on dark). wxSpinCtrlDouble is a
|
||||
// native GTK control that cannot draw that, and Orca's own SpinInput is int-only — it would
|
||||
// silently truncate a 2.5 mm radius. So the double spin keeps its arrows and validation and
|
||||
@@ -490,22 +496,11 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
m_construction->SetForegroundColour(dp_ctl_text());
|
||||
auto select_tool = [this](DesignSketchTool::Mode mode, const wxString& hint) {
|
||||
if (!m_viewport) return;
|
||||
if (!m_viewport->is_sketching()) {
|
||||
wxString on;
|
||||
const SketchPlane plane = sketch_plane_from_selection(on);
|
||||
m_viewport->begin_sketch(plane, mode);
|
||||
m_construction->SetValue(false); // a fresh session starts non-construction
|
||||
m_sketch_on = on; // shown with the tool hint, so the target is visible
|
||||
// The face has been CONSUMED as the sketch plane, so drop the pick. Leaving it live
|
||||
// meant the next Extrude saw a selected face and push/pulled it instead of extruding
|
||||
// the sketch just drawn — the same trap the imported-art path already guards against.
|
||||
if (m_sel_solid_face >= 0 || m_pick_face >= 0) {
|
||||
m_sel_solid_face = m_sel_solid_edge = m_sel_solid_body = -1;
|
||||
m_pick_face = m_pick_face_body = -1;
|
||||
}
|
||||
} else {
|
||||
m_viewport->set_sketch_tool(mode);
|
||||
}
|
||||
// Sketch mode opens with its session (start_sketch_on_target); this covers a mode a
|
||||
// failed Constrain left without one.
|
||||
if (!m_viewport->is_sketching())
|
||||
start_sketch_on_target(false);
|
||||
m_viewport->set_sketch_tool(mode);
|
||||
m_viewport->set_sketch_construction(m_construction->GetValue());
|
||||
set_status(StatusKind::Info, m_sketch_on.IsEmpty() ? hint
|
||||
: wxString::Format(_L("%s · on %s"), hint, m_sketch_on));
|
||||
@@ -730,32 +725,9 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
};
|
||||
|
||||
auto* b_sketch = icon_btn("design_sketch", _L("Sketch"));
|
||||
// Sketch means a tool. Pressing it used to set the mode and then ask, unconditionally,
|
||||
// for the very thing the user had just done — click XZ, read "XZ plane selected, press
|
||||
// Sketch", press Sketch, and be told to click a reference plane. The plane was never
|
||||
// lost (m_ref_plane holds it and begin_sketch captures it when the first tool is armed);
|
||||
// the sentence was simply false, and with right-click excluded in sketch mode there was
|
||||
// no door to the tools at all, so the only way on was the toolbar this tab is retiring.
|
||||
std::function<void()> act_sketch = [this] {
|
||||
set_ui_mode(UiMode::Sketch);
|
||||
wxString where;
|
||||
const bool have_plane = sketch_plane_target(where);
|
||||
set_status(StatusKind::Info, have_plane
|
||||
? wxString::Format(_L("Sketching on %s — pick a tool"), where)
|
||||
: _L("Click a face or a reference plane in the viewport, then a sketch tool"));
|
||||
if (m_sketch_hint) { // the card must agree with the status line, not argue with it
|
||||
m_sketch_hint->SetLabel(have_plane
|
||||
? wxString::Format(_L("Drawing on %s.\nPick a tool, or press Menu for the list."), where)
|
||||
: _L("Click a face or a reference plane, then a sketch tool."));
|
||||
m_sketch_hint->Refresh();
|
||||
m_cards->Layout();
|
||||
}
|
||||
// Hand over the tools rather than naming them in a status line. CallAfter so the
|
||||
// mode change has settled before a modal menu takes the loop; the menu carries each
|
||||
// tool's shortcut, so pressing the key instead of picking a row costs nothing.
|
||||
if (have_plane)
|
||||
CallAfter([this] { show_offer_menu(offer_anchor()); });
|
||||
};
|
||||
// Sketch opens a sketch on the face or plane already picked, or asks for one first
|
||||
// (start_sketch): sketch mode is never entered without a plane under it.
|
||||
std::function<void()> act_sketch = [this] { start_sketch(); };
|
||||
b_sketch->Bind(wxEVT_BUTTON, [act_sketch](wxCommandEvent&) { act_sketch(); });
|
||||
m_keys_feature[SHIFT('S')] = act_sketch;
|
||||
fadd("sketch", b_sketch);
|
||||
@@ -1406,12 +1378,11 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
// the sketch tools, not in the generic Features strip. Each places the art
|
||||
// in-canvas, then commits via the Insert card's Confirm.
|
||||
{
|
||||
// In Sketch MODE the art must land in the sketch — but begin_sketch() does not run
|
||||
// until the first tool is armed, so pressing Sketch and then Text would find no
|
||||
// session and commit a separate feature. Arm Select first: it starts the session on
|
||||
// the picked plane without drawing anything, so Text behaves the same whether or not
|
||||
// you had already drawn a line. (MODE vs SESSION — the distinction that has bitten
|
||||
// this panel before.)
|
||||
// In Sketch MODE the art must land in the sketch. The mode opens with its session
|
||||
// (start_sketch_on_target), but a failed Constrain can leave it without one, and Text
|
||||
// would then find no session and commit a separate feature. Arm Select first: it
|
||||
// starts the session without drawing anything. (MODE vs SESSION — the distinction
|
||||
// that has bitten this panel before.)
|
||||
auto ensure_sketch = [this, select_tool] {
|
||||
if (m_ui_mode == UiMode::Sketch && m_viewport && !m_viewport->is_sketching())
|
||||
select_tool(DesignSketchTool::Mode::Select,
|
||||
@@ -3125,11 +3096,9 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
// looking and pointing. A combo duplicated that decision somewhere the geometry could not
|
||||
// see it, and once a face could be picked it went further and displayed a stale row that
|
||||
// contradicted the real target. e1p.
|
||||
// Kept as a member, not a local: the card has to be able to STOP saying this. It asked
|
||||
// for a plane even when one had just been picked, directly contradicting the status line
|
||||
// two inches below it, which by then read "Sketching on XZ".
|
||||
m_sketch_hint = new wxStaticText(m_cards, wxID_ANY,
|
||||
_L("Click a face or a reference plane, then a sketch tool."));
|
||||
// A member, not a local: it names the plane the session opened on, which is known only
|
||||
// when it opens (start_sketch_on_target writes it), so the card and the status line agree.
|
||||
m_sketch_hint = new wxStaticText(m_cards, wxID_ANY, _L("Pick a tool, or press Menu for the list."));
|
||||
m_sketch_hint->SetForegroundColour(dp_sec_text());
|
||||
m_box_sketch_session->Add(m_sketch_hint, 0, wxLEFT | wxRIGHT | wxTOP | wxBOTTOM, 12);
|
||||
}
|
||||
@@ -3748,6 +3717,22 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
m_viewport->clear_loop_pick();
|
||||
m_plane_picked = false;
|
||||
}
|
||||
// Sketch is waiting for a plane: a flat face is one. m_pick_face is the face the click
|
||||
// landed on whether it took the face, one of its edges or a corner — the rule Sketch
|
||||
// applies to a face picked beforehand. A curved face says why it was not taken; a swept
|
||||
// body or a click that let go of the pick leaves the prompt up.
|
||||
if (sketch_plane_pick_live()) {
|
||||
wxString on;
|
||||
if (level >= 1 && sketch_plane_target(on)) {
|
||||
start_sketch_on_target(true);
|
||||
return;
|
||||
}
|
||||
set_status(StatusKind::Info, level >= 1 && m_pick_face >= 0
|
||||
? _L("That face is not flat — click a flat face or a reference plane")
|
||||
: sketch_plane_prompt());
|
||||
m_status->Refresh();
|
||||
return;
|
||||
}
|
||||
// Say what got picked. Without this the ONLY feedback is the viewport highlight, so a
|
||||
// pick that registers but draws faintly is indistinguishable from one that never
|
||||
// happened — which is precisely how this failure was reported and why it resisted
|
||||
@@ -3934,7 +3919,9 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
// A click on nothing drops a list row as it drops a pick. Not while a card is open: the
|
||||
// feature being edited keeps its row.
|
||||
m_viewport->set_on_empty_pick([this] {
|
||||
if (m_active == Tool::None && deselect_rows())
|
||||
if (m_active != Tool::None) return;
|
||||
const bool row = deselect_rows();
|
||||
if (drop_plane_pick() || row)
|
||||
set_status(StatusKind::Info, _L("Nothing selected"));
|
||||
});
|
||||
|
||||
@@ -4012,25 +3999,25 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
refresh_plane_labels();
|
||||
refresh_preview(); // re-resolve the frame + move the gizmo/ghosts to the new base
|
||||
} else {
|
||||
// Clicking a reference plane in 3D IS how a sketch plane is chosen now. Record it and,
|
||||
// when a session is already live, re-plane it immediately: begin_sketch captures the
|
||||
// plane at first-tool-pick, so without this the entities would stay on the old plane
|
||||
// while the committed feature landed on the new one.
|
||||
// Clicking a reference plane in 3D IS how a sketch plane is chosen. The planes are
|
||||
// only pickable outside a session (DesignSketchTool hit-tests them while no session
|
||||
// is live), and the canvas reports the click after clearing the solid selection on the
|
||||
// same miss, so the face pick is already empty and the sketch falls back to this plane.
|
||||
if (base >= 0) {
|
||||
m_ref_plane = base;
|
||||
m_plane_picked = true; // chosen, not merely defaulted to
|
||||
m_pick_face = m_pick_face_body = -1; // last pick wins: a plane beats a stale face
|
||||
if (m_viewport && m_viewport->is_sketching())
|
||||
m_viewport->set_sketch_plane(plane_from_choice(m_ref_plane));
|
||||
}
|
||||
if (base >= 0 && sketch_plane_pick_live()) {
|
||||
start_sketch_on_target(true); // Sketch was waiting for exactly this
|
||||
return;
|
||||
}
|
||||
const char* nm = (base == 0) ? "XY" : (base == 1) ? "XZ" : (base == 2) ? "YZ" : "datum";
|
||||
// Both halves named the TOOLBAR, which no longer carries either button: the tools
|
||||
// moved to the offer. Name the gesture that actually works in each mode, and say
|
||||
// what a plain click does, since the two are easy to confuse on a plane.
|
||||
set_status(StatusKind::Ok, m_ui_mode == UiMode::Sketch
|
||||
? wxString::Format(_L("%s plane selected — right-click for the drawing tools"), nm)
|
||||
: wxString::Format(_L("%s plane selected — right-click to sketch on it, "
|
||||
"or click an object to select it"), nm));
|
||||
// Name the gesture that works, and what a plain click does, since the two are easy to
|
||||
// confuse on a plane.
|
||||
set_status(StatusKind::Ok,
|
||||
wxString::Format(_L("%s plane selected — right-click to sketch on it, "
|
||||
"or click an object to select it"), nm));
|
||||
m_status->Refresh();
|
||||
}
|
||||
});
|
||||
@@ -4176,13 +4163,8 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
const int key = e.GetKeyCode();
|
||||
const bool ctrl = e.ControlDown() || e.CmdDown();
|
||||
const bool sketching = (m_ui_mode == UiMode::Sketch) && m_viewport && m_viewport->is_sketching();
|
||||
// Which key MAP applies is a question about the MODE, not about whether a session is
|
||||
// already running. Gating the sketch map on is_sketching() made it unreachable by
|
||||
// keyboard: is_sketching() only turns true inside select_tool()'s begin_sketch(), and
|
||||
// select_tool() is what the sketch keys call — so the first letter after entering
|
||||
// sketch mode fell through to the feature map, matched nothing (feature keys are
|
||||
// Shift+letter), and did nothing. The mouse worked only because the toolbar flyout
|
||||
// reaches select_tool() directly. That is why all 17 keys read as dead. 0ud.
|
||||
// Which key MAP applies is a question about the MODE, not about the canvas tool:
|
||||
// is_sketching() is true in Constrain too, which keeps the feature map. 0ud.
|
||||
const bool sketch_mode = (m_ui_mode == UiMode::Sketch);
|
||||
// Never steal editing keys from a focused text field or an open in-canvas value field —
|
||||
// Delete/Ctrl+Z there must edit the text, not the model.
|
||||
@@ -4500,6 +4482,9 @@ void DesignPanel::set_ui_mode(UiMode m)
|
||||
// since the gizmo is not drawn in a sketch.
|
||||
if (m != UiMode::Feature) end_body_move(true);
|
||||
m_ui_mode = m;
|
||||
// Any mode change ends a plane choice under way; the refreshes below put the planes and the
|
||||
// ✓/✗ right for it.
|
||||
m_choosing_sketch_plane = false;
|
||||
if (m != UiMode::Sketch) m_sketch_on.clear(); // no stale "on the picked face" on the next hint
|
||||
// A committed loop picked before the session means nothing to the sketch map, but it would
|
||||
// still count as a selection and swallow the first Esc. The double-click that opens a sketch
|
||||
@@ -4546,9 +4531,8 @@ void DesignPanel::set_ui_mode(UiMode m)
|
||||
m_form->FitInside();
|
||||
}
|
||||
update_action_bar(); // Sketch/Constrain modes show the unified ✓/✗; Feature idle hides it
|
||||
// The reference planes follow the mode (update_reference_planes). Without this they would
|
||||
// only refresh on the next tree rebuild, which is not an event that happens when you merely
|
||||
// press Sketch.
|
||||
// The reference planes follow the plane choice the mode change just ended
|
||||
// (update_reference_planes); without this they would wait for the next tree rebuild.
|
||||
update_reference_planes();
|
||||
|
||||
// Say where you are, in words, across the top of the viewport.
|
||||
@@ -4699,6 +4683,7 @@ void DesignPanel::open_text_dialog(int feat)
|
||||
{
|
||||
if (m_text_dlg != nullptr) { m_text_dlg->Raise(); return; }
|
||||
end_body_move(true);
|
||||
end_sketch_plane_choice(); // the text goes on the plane picked before, not on the next click
|
||||
m_text_editing = feat >= 0 && feat < int(m_doc.features.size()) && m_doc.features[feat].is_text();
|
||||
m_text_feat = m_text_editing ? feat : -1;
|
||||
m_text_face_body = -1;
|
||||
@@ -5158,6 +5143,7 @@ void DesignPanel::open_insert_card(const wxString& base_name)
|
||||
{
|
||||
end_body_move(true);
|
||||
m_active = Tool::Insert;
|
||||
end_sketch_plane_choice();
|
||||
if (m_hdr_insert) m_hdr_insert->SetLabel(base_name);
|
||||
wxSizer* s = m_cards->GetSizer();
|
||||
s->Show(m_box_insert, true, true);
|
||||
@@ -5203,6 +5189,7 @@ void DesignPanel::on_transform_imported(int feat_idx)
|
||||
if (f.imported_regions.empty())
|
||||
return;
|
||||
end_body_move(true); // the art's placement gizmo and ✓/✗ take over from a Move left open
|
||||
end_sketch_plane_choice(); // ...and from a plane choice: Scale artwork reaches here directly
|
||||
// In-canvas bbox handles (replaces the Move/Scale dialog): drag a corner to scale,
|
||||
// the centre to move. Values stream back via set_on_imported_transform.
|
||||
m_viewport->begin_imported_transform(feat_idx, f.imported_regions, f.plane,
|
||||
@@ -7658,6 +7645,16 @@ bool DesignPanel::deselect_rows()
|
||||
return any;
|
||||
}
|
||||
|
||||
// A picked reference plane is a selection like a face: the next Sketch opens on it, so letting go
|
||||
// of everything has to let go of it too. The viewport asks for it every frame (set_selected_base).
|
||||
bool DesignPanel::drop_plane_pick()
|
||||
{
|
||||
if (!m_plane_picked) return false;
|
||||
m_plane_picked = false;
|
||||
if (m_viewport) m_viewport->request_repaint();
|
||||
return true;
|
||||
}
|
||||
|
||||
void DesignPanel::update_section_flip_btn()
|
||||
{
|
||||
if (m_section_flip_btn) m_section_flip_btn->Enable(m_section_on);
|
||||
@@ -7828,6 +7825,7 @@ void DesignPanel::on_move_body()
|
||||
set_status(StatusKind::Info, hint);
|
||||
return;
|
||||
}
|
||||
end_sketch_plane_choice(); // the gizmo takes the clicks a plane choice was waiting for
|
||||
sync_body_xform();
|
||||
// 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).
|
||||
@@ -7945,6 +7943,7 @@ bool DesignPanel::place_on_face()
|
||||
}
|
||||
const TopoDS_Face face = GeometryEngine::face_by_index(m_doc.bodies[b].shape, m_sel_solid_face);
|
||||
if (face.IsNull()) return false;
|
||||
end_sketch_plane_choice(); // the body moves; a sketch was not started
|
||||
sync_body_xform();
|
||||
const Transform3d old_x = m_body_xform[b];
|
||||
// Outward face normal in the body's CURRENT displayed orientation.
|
||||
@@ -8017,6 +8016,9 @@ bool DesignPanel::begin_renumber()
|
||||
|
||||
void DesignPanel::after_tree_edit(bool ok)
|
||||
{
|
||||
// Any edit of the history (undo, delete, show/hide, a socket call...) writes the status line
|
||||
// over the plane prompt, so it ends the choice rather than leave it waiting unannounced.
|
||||
end_sketch_plane_choice();
|
||||
update_undo_redo_buttons();
|
||||
refresh_tree();
|
||||
refresh_variables();
|
||||
@@ -10094,6 +10096,7 @@ void DesignPanel::on_commit()
|
||||
// is shown on screen, not the pre-feature solid. (confirm_tool() applies + closes.)
|
||||
if (m_active != Tool::None)
|
||||
confirm_tool();
|
||||
end_sketch_plane_choice(); // the bodies leave for the plate; a sketch was not started
|
||||
|
||||
if (m_doc.display_mesh.its.indices.empty()) {
|
||||
set_status(_L("Nothing to commit — add a feature first"));
|
||||
@@ -11105,11 +11108,11 @@ void DesignPanel::update_reference_planes()
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Sketch mode is where a plane is the thing being picked (act_sketch asks for "a face or a
|
||||
// reference plane"); a live session draws none (DesignSketchTool::draws_reference_axes).
|
||||
// Up while Sketch waits for a plane to be picked (start_sketch), which the session it opens
|
||||
// takes away; a live session would draw none anyway (DesignSketchTool::draws_reference_axes).
|
||||
// They never block selection: clicking existing geometry wins, and a base-plane pick only
|
||||
// fires on a click that hit nothing else (see on_mouse fall-through).
|
||||
if (m_show_origin || m_ui_mode == UiMode::Sketch)
|
||||
if (m_show_origin || m_choosing_sketch_plane)
|
||||
m_viewport->set_base_pick(std::move(bp), std::move(bi), std::move(bl));
|
||||
else
|
||||
m_viewport->clear_base_pick();
|
||||
@@ -11119,9 +11122,77 @@ void DesignPanel::toggle_origin()
|
||||
{
|
||||
m_show_origin = !m_show_origin;
|
||||
wxGetApp().app_config->set_bool("design_show_origin", m_show_origin);
|
||||
// A plane picked while they were up is not a selection once nobody can see it.
|
||||
if (!m_show_origin) drop_plane_pick();
|
||||
// Not an edit: a plane choice under way stays, and keeps its planes up.
|
||||
refresh_tree(); // the Origin row's eye, and update_reference_planes(), which repaints
|
||||
}
|
||||
|
||||
void DesignPanel::start_sketch()
|
||||
{
|
||||
if (m_ui_mode != UiMode::Feature) return; // already sketching or constraining
|
||||
if (m_active != Tool::None || m_text_dlg != nullptr || body_move_pending()) {
|
||||
set_status(StatusKind::Info, _L("Finish or cancel what is open first"));
|
||||
return;
|
||||
}
|
||||
wxString where;
|
||||
if (sketch_plane_target(where)) {
|
||||
start_sketch_on_target(true);
|
||||
return;
|
||||
}
|
||||
// Nothing to sketch on yet: offer the planes and stay in Feature mode until one is picked
|
||||
// (set_on_datum_base_picked, set_on_solid_selection_changed).
|
||||
m_choosing_sketch_plane = true;
|
||||
update_reference_planes();
|
||||
update_action_bar(); // ✗ leaves the choice
|
||||
set_status(StatusKind::Info, sketch_plane_prompt());
|
||||
}
|
||||
|
||||
void DesignPanel::start_sketch_on_target(bool offer_tools)
|
||||
{
|
||||
if (!m_viewport) return;
|
||||
wxString on;
|
||||
const SketchPlane plane = sketch_plane_from_selection(on);
|
||||
m_viewport->begin_sketch(plane, DesignSketchTool::Mode::Select);
|
||||
// The face or plane has been CONSUMED as the sketch plane, so drop the pick. A face left live
|
||||
// meant the next Extrude saw a selected face and push/pulled it instead of extruding the
|
||||
// sketch just drawn — the same trap the imported-art path already guards against — and a
|
||||
// plane left picked would open the next sketch on it, unseen once the planes are hidden.
|
||||
// The canvas's half goes too: left lit there, the next click on that face read as a second
|
||||
// click on it and took the whole body, which has no plane to sketch on.
|
||||
drop_solid_pick();
|
||||
m_plane_picked = false;
|
||||
m_construction->SetValue(false); // a fresh session starts non-construction
|
||||
set_ui_mode(UiMode::Sketch); // ends the plane choice, so the planes go
|
||||
m_sketch_on = on; // shown with the tool hint, so the target is visible
|
||||
set_status(StatusKind::Info, wxString::Format(_L("Sketching on %s — pick a tool"), on));
|
||||
if (m_sketch_hint) { // the card must agree with the status line, not argue with it
|
||||
m_sketch_hint->SetLabel(wxString::Format(_L("Drawing on %s.\nPick a tool, or press Menu for the list."), on));
|
||||
m_sketch_hint->Refresh();
|
||||
m_cards->Layout();
|
||||
}
|
||||
// Hand over the tools rather than naming them in a status line: the sketch bar carries none.
|
||||
// CallAfter so the mode change has settled, and the pick that got here has returned, before a
|
||||
// modal menu takes the loop; the menu carries each tool's shortcut, so pressing the key
|
||||
// instead of picking a row costs nothing.
|
||||
if (offer_tools)
|
||||
CallAfter([this] { show_offer_menu(offer_anchor()); });
|
||||
}
|
||||
|
||||
void DesignPanel::end_sketch_plane_choice()
|
||||
{
|
||||
if (!m_choosing_sketch_plane) return;
|
||||
m_choosing_sketch_plane = false;
|
||||
update_reference_planes();
|
||||
update_action_bar();
|
||||
}
|
||||
|
||||
bool DesignPanel::sketch_plane_pick_live() const
|
||||
{
|
||||
return m_choosing_sketch_plane && m_ui_mode == UiMode::Feature && m_active == Tool::None
|
||||
&& m_text_dlg == nullptr && !body_move_pending();
|
||||
}
|
||||
|
||||
TriangleMesh DesignPanel::ghost_from_bodies(const std::vector<TriangleMesh>& per_body) const
|
||||
{
|
||||
TriangleMesh out;
|
||||
@@ -11378,6 +11449,7 @@ void DesignPanel::open_tool(Tool t)
|
||||
{
|
||||
end_body_move(true); // first: arm_transform_gizmo below takes over the same gizmo
|
||||
m_active = t;
|
||||
end_sketch_plane_choice(); // after m_active: the Plane card keeps its planes
|
||||
m_candidate_ok = true; // a fresh card is confirmable until its preview says otherwise
|
||||
// Fillet/Chamfer/Draft no longer fade the body see-through; instead, once a valid target
|
||||
// is picked, refresh_preview hides the base bodies entirely (preview-only). Keep it opaque
|
||||
@@ -11834,6 +11906,11 @@ void DesignPanel::tool_cancel()
|
||||
}
|
||||
if (m_active == Tool::Insert) { cancel_insert(); return; }
|
||||
if (m_active != Tool::None) { cancel_tool(); return; }
|
||||
if (m_choosing_sketch_plane) { // Sketch was waiting for a plane: no sketch, planes away
|
||||
end_sketch_plane_choice();
|
||||
set_status(StatusKind::Info, wxString());
|
||||
return;
|
||||
}
|
||||
if (m_ui_mode == UiMode::Sketch) {
|
||||
// THE explicit discard. Esc no longer arrives here at all (it routes through escape(),
|
||||
// which cannot destroy anything), so this button is now the only way a drawn sketch is
|
||||
@@ -11895,9 +11972,10 @@ CadLevel DesignPanel::escape_level() const
|
||||
// being moved by a gizmo that has not been confirmed.
|
||||
st.gesture_active = body_move_pending()
|
||||
|| (m_viewport && m_viewport->drawing_in_progress());
|
||||
// Something is armed and waiting for input: a feature card, a sketch draw tool, Constrain.
|
||||
// A sketch SESSION is deliberately not in this list — see escape().
|
||||
// Something is armed and waiting for input: a feature card, Sketch waiting for its plane, a
|
||||
// sketch draw tool, Constrain. A sketch SESSION is deliberately not in this list — see escape().
|
||||
st.tool_armed = (m_active != Tool::None)
|
||||
|| m_choosing_sketch_plane
|
||||
|| m_ui_mode == UiMode::Constrain
|
||||
|| (m_ui_mode == UiMode::Sketch && m_viewport && !m_viewport->sketch_is_selecting());
|
||||
st.has_selection = m_viewport && m_viewport->has_any_selection();
|
||||
@@ -11934,7 +12012,10 @@ void DesignPanel::escape()
|
||||
// CANDIDATE (never a committed feature — in edit mode reset_edit_state only forgets which
|
||||
// feature was being edited, the feature itself is untouched); an armed sketch tool falls
|
||||
// back to Select, leaving every entity already drawn exactly where it is.
|
||||
if (m_active != Tool::None || m_ui_mode == UiMode::Constrain) { tool_cancel(); return; }
|
||||
if (m_active != Tool::None || m_choosing_sketch_plane || m_ui_mode == UiMode::Constrain) {
|
||||
tool_cancel();
|
||||
return;
|
||||
}
|
||||
if (m_viewport && m_viewport->sketch_disarm_tool()) {
|
||||
set_status(StatusKind::Info, _L("Select"));
|
||||
}
|
||||
@@ -11945,7 +12026,8 @@ void DesignPanel::escape()
|
||||
// both of which say which one they are, and never through a key pressed on the way out of
|
||||
// something else. The Feature tree and Bodies rows count as selections too.
|
||||
const bool row = deselect_rows();
|
||||
if ((m_viewport && m_viewport->clear_any_selection()) || row) {
|
||||
const bool plane = drop_plane_pick();
|
||||
if ((m_viewport && m_viewport->clear_any_selection()) || row || plane) {
|
||||
set_status(StatusKind::Info, wxString());
|
||||
return;
|
||||
}
|
||||
@@ -11988,6 +12070,7 @@ void DesignPanel::update_action_bar()
|
||||
wxSizer* s = m_toolbar->GetSizer();
|
||||
if (s == nullptr) return;
|
||||
const bool active = (m_active != Tool::None)
|
||||
|| m_choosing_sketch_plane // ✗ only: ✓ has nothing to confirm
|
||||
|| m_ui_mode == UiMode::Sketch
|
||||
|| m_ui_mode == UiMode::Constrain
|
||||
|| body_move_pending();
|
||||
|
||||
@@ -383,8 +383,14 @@ private:
|
||||
void update_rib_gizmo(); // in-plane slab footprint + thickness handles (Rib card)
|
||||
void refresh_datum_planes(); // push resolved datum frames + per-plane u/v extents to viewport
|
||||
void refresh_mate_connectors(); // push connector frames so verse + polarity are visible
|
||||
void update_reference_planes(); // the XY/XZ/YZ planes + axes: Origin row, Sketch mode, Plane tool
|
||||
void update_reference_planes(); // the XY/XZ/YZ planes + axes: Origin row, plane choice, Plane tool
|
||||
void toggle_origin(); // the Origin row's eye: keep the reference planes up, or not
|
||||
// Sketch: open a session on the picked face or plane, or with none picked, put the planes up
|
||||
// and wait for one (m_choosing_sketch_plane).
|
||||
void start_sketch();
|
||||
void start_sketch_on_target(bool offer_tools); // a session on sketch_plane_from_selection()
|
||||
void end_sketch_plane_choice(); // leave the plane choice without a sketch; no-op outside it
|
||||
bool sketch_plane_pick_live() const; // may a pick made now still open the sketch?
|
||||
|
||||
CadDocument m_doc;
|
||||
|
||||
@@ -572,8 +578,13 @@ private:
|
||||
// deliberately no dropdown for it. e1p.
|
||||
int m_ref_plane{0};
|
||||
// m_ref_plane is always a VALID plane, so it cannot itself distinguish "the user chose XY"
|
||||
// from "nobody has chosen anything yet". This does.
|
||||
// from "nobody has chosen anything yet". This does. A selection like a picked face: the sketch
|
||||
// opened on it uses it up, and Esc or a click on nothing lets go of it.
|
||||
bool m_plane_picked{false};
|
||||
// Sketch was pressed with nothing to sketch on: the reference planes are up and the next
|
||||
// reference plane or flat face clicked opens the sketch on it. Still Feature mode — sketch
|
||||
// mode is entered only with the session (start_sketch_on_target).
|
||||
bool m_choosing_sketch_plane{false};
|
||||
// The Feature tree's Origin row: keeps the reference planes and their axes up
|
||||
// (update_reference_planes). A view preference (AppConfig "design_show_origin"), not part of
|
||||
// the recipe.
|
||||
@@ -933,6 +944,7 @@ private:
|
||||
void request_feature_highlight();
|
||||
void update_feature_highlight();
|
||||
bool deselect_rows(); // Esc / a click on nothing: drop the tree and Bodies rows
|
||||
bool drop_plane_pick(); // ...and a picked reference plane; true if one was picked
|
||||
// Bodies list under the feature tree: one row per body (parallel to m_doc.bodies). Selecting
|
||||
// one highlights that body and makes it the target for the next op.
|
||||
DesignRowList* m_parts{nullptr};
|
||||
|
||||
@@ -9486,7 +9486,10 @@ const ColorRGBA* DesignSketchTool::sketch_hl_color(int feature) const
|
||||
// forgotten by the next one. Cheap: three ints compared per frame.
|
||||
void DesignSketchTool::emit_step_hint()
|
||||
{
|
||||
if (!on_step_changed) return;
|
||||
// No session, no step. render() also runs while only the reference planes are up, and m_mode
|
||||
// then holds the last session's tool (Polyline before the first), whose prompt used to replace
|
||||
// the panel's own line — "click a reference plane" while Sketch waits for one.
|
||||
if (!on_step_changed || !m_active) return;
|
||||
int step = 0, picks = 0;
|
||||
if (is_edit_op_mode()) {
|
||||
picks = (m_mode == Mode::Mirror) ? int(m_mirror_targets.size())
|
||||
|
||||
@@ -173,7 +173,6 @@ public:
|
||||
// Returns false when no session is live, so the caller can fall back to a new feature.
|
||||
bool add_imported_regions(const std::vector<std::vector<std::vector<Vec2d>>>& regions);
|
||||
void set_tool(Mode mode); // switch tool, keep accumulated entities
|
||||
void set_plane(const SketchPlane& plane) { m_plane = plane; } // re-plane a live sketch (a reference plane was clicked mid-session); entities are 2D, re-lifted through the new plane
|
||||
void set_construction(bool c) { m_construction = c; }
|
||||
void set_polygon_sides(int n) { m_polygon_sides = (n < 3 ? 3 : n); }
|
||||
void set_polygon_circumscribed(bool c) { m_polygon_circumscribed = c; }
|
||||
|
||||
Reference in New Issue
Block a user