Port the sketch layer work from snaporca: offset chains, right-click, MCP verbs

Carries snaporca 971320e129, 6b049f0dc6, 4aae782029, 444d59f212, 74cf3d7e54 and the build
guards from 597557a6e4. Parity re-verified after every hunk: 17 files identical, 8 diverging by
their expected counts — DesignPanel.cpp still 32, DesignCanvas.cpp still 16, which is the proof
each hunk landed on the right side rather than being copied over a real divergence.

OFFSET OFFSETS THE CHAIN. Per-entity offsetting returned a closed rectangle as four parallel
segments that no longer touch, so entities_to_wires gave four OPEN wires and nothing could be
extruded. offset_entities now chains by shared endpoints and repairs each seam by mitering the
neighbours to their intersection. Second bug, invisible to any single-entity test: +d meant
"left of travel" for a line but "radius + d" for an arc regardless of sweep, so a slot outline
offset with its straights going one way and its caps the other. The convention is now written on
the declaration and pinned by a test.

tests/libslic3r/test_sketchprofile.cpp is new and asserts the LOOP rather than coordinates —
the property that decides whether a profile can be built, and the one the existing single-entity
[SketchEdit] cases cannot see. Its include is catch2/catch_all.hpp here: this fork ships Catch2
v3 while snaporca is on v2, which is why the test files are a tolerated divergence.

RIGHT-CLICK PICKS WHAT YOU POINTED AT, so a line's own verbs are offered instead of the
empty-selection vocabulary; sk_delete stops sharing btn:delete with the feature tree; and an
element's defining number (length / radius / diameter / angle / distance) can be typed, from the
menu or from V.

TWELVE MCP SKETCH VERBS. The socket had ~40 verbs and none touched a sketch, so the 2D layer
could only be exercised by driving a GUI with synthetic clicks. sketch_describe reports each
closed loop, the loops it encloses as voids, exact areas, and where a chain is still open;
sketch_validate/sketch_heal are FreeCAD's ValidateSketch — find vertices that overlap within a
tolerance but carry no coincidence, then weld them AND record the constraint, so a loop closed
by floating-point luck becomes one closed by construction. scripts/mcp-sketch-smoke.py is the
loop that asserts all of it.

Kernel suite on this fork: all tests passed, 2677 assertions in 230 test cases. The GUI target
links against the rebuilt deps image (the wxInspector blockage is gone) and the binary carries
the new verbs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tommaso Bianchi
2026-08-22 09:00:26 +02:00
co-authored by Claude Opus 5
parent df45edb13d
commit 5d7fc8c545
17 changed files with 1313 additions and 50 deletions
+30
View File
@@ -426,8 +426,28 @@ DesignPanel::DesignPanel(wxWindow* parent)
select_tool(DesignSketchTool::Mode::Polygon, _L("Polygon — click center, then a vertex"));
};
// Constrain (finish the live sketch + enter constrain), and Construction toggle.
// V for Value: type the defining number of whatever is selected — a line's length, an arc's
// radius, a circle's diameter, the angle between two lines. One handler behind three offer
// rows, because the quantity comes from the selection, not from which row was clicked.
//
// It needs a KEY, not just a menu row. The deck profile in VSD_n1_streamcontroller is
// generated from these two key tables, so a verb with no shortcut cannot be put on a
// physical button at all — which is the whole point of that profile for a user who drives
// the app from buttons rather than a menu.
m_keys_sketch['V'] = [this] {
if (m_viewport && m_viewport->is_sketching() && !m_viewport->edit_sketch_selection_value())
set_status(_L("Nothing here has a value to type — pick a line, an arc, a circle, or two entities"));
};
m_keys_sketch['K'] = [this] { enter_constrain_inline(); };
m_keys_sketch['Q'] = [this] {
// With geometry selected, Q converts THAT geometry (snaporca-6zic) — the reading
// everyone arrives with from other sketchers. With nothing selected it keeps its
// old meaning: arm construction for whatever you draw next.
if (m_viewport && m_viewport->is_sketching() &&
m_viewport->toggle_sketch_construction_selection() > 0) {
set_status(_L("Converted the selection between construction and real geometry"));
return;
}
if (m_construction) {
m_construction->SetValue(!m_construction->GetValue());
if (m_viewport && m_viewport->is_sketching())
@@ -1007,6 +1027,16 @@ DesignPanel::DesignPanel(wxWindow* parent)
fadd("color", b_color);
m_verb_actions["btn:colour"] = [this] { on_set_body_color(); };
m_verb_actions["btn:delete"] = [this] { on_delete_feature(); };
// The sketch's own Delete. It used to share "btn:delete" with the feature tree, so
// choosing Delete on a selected LINE ran on_delete_feature() and removed a tree row (or
// nothing) while the line stayed — the reported "I click a line and cannot remove it".
m_verb_actions["btn:sk_delete"] = [this] {
if (m_viewport && m_viewport->is_sketching())
m_viewport->delete_selected_sketch_entities();
else
on_delete_feature();
};
m_verb_actions["btn:delete_body"] = [this] { on_delete_body(); };
m_verb_actions["btn:edit"] = [this] { on_edit_feature(); };
m_verb_actions["btn:mass"] = [this] { on_mass_properties(); };