Sketch where the user pointed: a picked face is the sketch plane

Selecting a face and sketching on it is the most common gesture in solid
modelling, and it was impossible. The plane came from a combo holding
XY/XZ/YZ plus datums, and plane_from_choice had no face branch at all, so
the only route onto a face was to build a Coincident datum plane on it
first, confirm that, reopen the sketch and find the datum in the
dropdown. Three extra steps and a junk feature in the tree.

The fix is not another combo row. A new sketch now takes its plane from
what is SELECTED IN THE VIEWPORT: a picked planar face wins outright, and
only when nothing is picked does it fall back to the reference plane —
which is itself normally set by clicking one of the ghost planes in 3D,
not by opening the combo. The tool hint names the target ("Circle — click
center, then radius · on the picked face") so the choice is visible on the
geometry side rather than needing a control to read back.

CadDocument::plane_of_face is the shared derivation, so the sketch path
and the Coincident datum method cannot drift apart. It refuses
non-planar faces: face_normal_world evaluates at the mid parameter, which
on a cylinder or a fillet is a tangent plane at one arbitrary point —
fine for offsetting a datum, wrong as a sketch plane, and silently
sketching on a tangent is worse than declining.

Picking the face also CONSUMES it. Leaving the pick 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.

Verified on :10 end to end with no combo interaction: build a box, click
its top face twice to cycle whole -> face, press S then C, and the circle
is drawn in the plane of that face with its Radius tab on the geometry
(artifacts/shots/f3a2-03-face.png, f3a2-04-sketch-on-face.png,
f3a2-05-circle-drawn.png). Kernel side: 154 cases / 2125 assertions green
on both forks, including that a cylinder resolves exactly its two flat
caps and refuses the barrel.

Still side-panel-shaped and to be dealt with separately: the Plane combo
remains on the card and now merely displays a stale row when a face is
the real target. It should show the actual target or go away.

snaporca-3a2.
This commit is contained in:
Tommaso Bianchi
2026-07-30 13:42:11 +02:00
parent 7f0a8c85ee
commit 6ce20d78c3
5 changed files with 119 additions and 2 deletions
+34 -2
View File
@@ -341,16 +341,24 @@ DesignPanel::DesignPanel(wxWindow* parent)
auto select_tool = [this](DesignSketchTool::Mode mode, const wxString& hint) {
if (!m_viewport) return;
if (!m_viewport->is_sketching()) {
const SketchPlane plane = plane_from_choice(m_draw_plane->GetSelection());
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_sel_solid_face = m_sel_solid_edge = m_sel_solid_body = -1;
} else {
m_viewport->set_sketch_tool(mode);
}
m_viewport->set_sketch_construction(m_construction->GetValue());
show_polygon_card(mode == DesignSketchTool::Mode::Polygon);
m_status->SetForegroundColour(wxNullColour);
m_status->SetLabel(hint);
m_status->SetLabel(m_sketch_on.IsEmpty() ? hint
: wxString::Format(_L("%s · on %s"), hint, m_sketch_on));
m_status->Refresh();
};
@@ -3523,6 +3531,7 @@ void DesignPanel::set_active_tool_btn(ScalableButton* b)
void DesignPanel::set_ui_mode(UiMode m)
{
m_ui_mode = m;
if (m != UiMode::Sketch) m_sketch_on.clear(); // no stale "on the picked face" on the next hint
wxSizer* s = m_toolbar->GetSizer();
s->Show(m_tb_feature, m == UiMode::Feature, true);
s->Show(m_tb_sketch, m == UiMode::Sketch, true);
@@ -4837,6 +4846,29 @@ SketchPlane DesignPanel::plane_from_choice(int row) const
SketchPlane p = SketchPlane::XY(); p.origin += m_doc.modeling_origin; return p;
}
// A sketch goes where the user pointed. A planar face picked in the viewport wins outright; only
// when nothing is picked do we fall back to the reference plane, which itself is normally set by
// clicking one of the ghost planes in 3D (on_datum_base_picked) rather than by opening the combo.
// Before this, a picked face was ignored and the only way onto it was to build a Coincident datum
// plane first and then find it in a dropdown — three steps and a junk feature in the tree for the
// most common gesture in solid modelling. snaporca-3a2.
SketchPlane DesignPanel::sketch_plane_from_selection(wxString& what) const
{
SketchPlane p;
if (m_sel_solid_face >= 0 && m_sel_solid_body >= 0
&& m_doc.plane_of_face(m_sel_solid_body, m_sel_solid_face, p)) {
what = (m_doc.bodies.size() > 1)
? wxString::Format(_L("the picked face of Body %d"), m_sel_solid_body + 1)
: _L("the picked face");
return p;
}
const int row = m_draw_plane ? m_draw_plane->GetSelection() : 0;
what = (m_draw_plane && row >= 0 && row < int(m_draw_plane->GetCount()))
? m_draw_plane->GetString(unsigned(row))
: wxString("XY");
return plane_from_choice(row);
}
void DesignPanel::apply_plane_refs(CadFeature& f) const
{
f.plane_type = (PlaneType)m_plane_type->GetSelection();