The viewport and the kernel now answer "is this joint closed?" with one number

Tommaso asked the question that names the real defect: if the sketch was open, why was
the same sketch shaded closed and offered for extrude? Because the two halves used
different tolerances. region_loops shades a region closed at 1e-3 mm; connected_loop
chained at 1e-3; the kernel welded at 1e-4 and OCCT matched vertices at 1e-7. The
2.28e-5 mm gap in the reported sketch did not cause that disagreement, it only made it
visible — and fixing the gap alone would have left the contradiction in place, ready to
reappear anywhere in (1e-4, 1e-3].

kSketchJoinTol now lives in SketchEngine.hpp and is the only place the number exists.
region_loops, loop_report, connected_loop and entities_to_wires all read it through
sketch_join_tol(). The viewport cannot promise a region the kernel refuses to build.

The welding is optional, because a kernel that silently closes loops should let you say
no: "Auto-close sketch loops" in Preferences, default ON, no restart. OFF means only
exactly coincident endpoints join — and since both halves read the same value, the
viewport simply stops shading the region closed, so an open loop is visible rather than
welded behind your back. No separate UI needed for that; it falls out of sharing one
number.

Details that matter. The kernel defaults to auto-close ON independently of the GUI, so
headless and MCP callers behave like the viewport instead of inheriting an unset
preference. With the tolerance at 0 the comparisons become <=, because OFF must mean
exact, not broken. OCCT never receives a zero vertex tolerance — it is clamped to
Precision::Confusion.

The preference is pushed from EVERY entry that starts a sketch session, not just
begin(): a Constrain session enters through begin_constrain / begin_constrain_entities
and uses region_loops and connected_loop, so a single push site would have left those
sessions running on whatever the previous one set. begin_imported_transform is excluded
deliberately — it works on imported regions, not chained entities.

Tests: a loop with one joint open by 9e-4 mm, given out of traversal order, builds a
closed four-edge wire; with auto-close off the same loop yields no wire; and an exactly
closed loop still builds with auto-close off, proving OFF means exact. Kernel 66104
assertions / 606 cases green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011FbJKJAJxxkhDTs9XdZzKA
This commit is contained in:
Tommaso Bianchi
2026-09-02 14:34:17 +02:00
co-authored by Claude Opus 5
parent faf4406f89
commit 8b568b7b9d
7 changed files with 174 additions and 11 deletions
+12
View File
@@ -8,6 +8,7 @@
#include "I18N.hpp"
#include "libslic3r/AppConfig.hpp"
#include "libslic3r/Format/DRC.hpp"
#include "libslic3r/CAD/SketchEngine.hpp"
#include <wx/language.h>
#include "OG_CustomCtrl.hpp"
#include "wx/graphics.h"
@@ -1747,6 +1748,13 @@ void PreferencesDialog::create_items()
"parametrically. This feature is experimental and still under development."),
"enable_cad_feature", _L("(Requires restart)"));
g_sizer->Add(item_cad_feature);
auto item_auto_close_sketch_loops = create_item_checkbox(_L("Auto-close sketch loops"),
_L("Treat sketch endpoints within 0.001 mm as one joint and weld the loop shut. "
"Off: only exactly coincident endpoints join, so a loop with a tiny gap is "
"shown as open instead of being closed for you."),
"auto_close_sketch_loops");
g_sizer->Add(item_auto_close_sketch_loops);
#endif
#if 0
@@ -1834,6 +1842,10 @@ void PreferencesDialog::create_items()
"Turn this off for the conventional CAD representation."), "design_connector_face_glyph");
g_sizer->Add(item_connector_face_glyph);
}
// Push the weld preference into the kernel now so toggling it takes effect without
// a restart (the sketch tool also re-pushes on activation, see DesignSketchTool::begin).
Slic3r::set_sketch_auto_close(wxGetApp().is_auto_close_sketch_loops());
#endif
std::vector<wxString> ButtonDragActions = {_L("None"), _L("Pan"), _L("Rotate")};