mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-21 07:52:32 +00:00
CAD: extrude a sketch region with its holes, and stop crashing at startup
A rectangle with a circle inside it, drawn in ONE sketch, could not be extruded to a plate with a bore from the GUI. Five defects were in the way. Each was found by driving the app on a headless rig and measuring the result — the code reads correctly at every one of these points, which is why they survived. 1. Wire orientation (kernel). SketchEngine::wires_to_face added every hole as wires[i].Reversed(), which is only right when the sketch happens to wind both loops the same way. A circle drawn clockwise inside a counter-clockwise rectangle came out matching the outer boundary, OCCT swept it as a SECOND contour, and the prism was the plate with its bore filled and the disc's volume counted twice. Measured: bbox 67.17 x 219.67 x 10 with volume 152088 mm3 against a solid box of 147542 — a body larger than its own bounding box, which is the signature. Holes are now added as-is and ShapeFix_Face::FixOrientation() classifies them; that is winding-independent and is the idiom make_extrude_regions already used for imported glyphs, which is why holed TEXT always extruded correctly while a holed SKETCH never did. After the fix: 142996 mm3, implied bore radius 12.03 mm against the circle drawn. 2. The live-sketch click threw the picked region away. region_at() served only as a yes/no gate and on_face_selected() carried no argument, so Extrude fell back to whichever loop the resolver found first — clicking the material of a plate-with-a-hole extruded the disc. The region is now carried through, and DesignPanel also hands it to the tool with set_loop_pick(), AFTER open_tool() because that re-derives selection state, since extrude_uses_loop() reads selected_loop_entities() and that lives on the tool. 3. region_at() had no hole awareness and no innermost preference: it returned the first polygon containing the point. It now skips a region when the point lies inside one of that region's holes, and picks the smallest containing loop, so a click in the bore selects the disc and a click on the material selects the plate. 4. Startup segfault. DesignCanvas::request_repaint probed the GL backend via OpenGLManager::get_gl_info().get_renderer() before the canvas had initialised GL — glGetString with no context current and, before init_opengl(), no loaded function pointers. Anything that asked for a repaint while the panel was still being built landed there, with no window and nothing in the log. It now bails at the top on !is_initialized() and asks for a Refresh instead. Note the crash was in the PROBE, not in render(), which already guards itself. 5. A holed sketch on a plane whose normal points -Z came out as the full box PLUS a disc — 220274 mm3 where 163726 was due (192000 + 28274). wires_to_face took a SketchPlane parameter it never used and let OCCT infer a surface from the outer wire; when the inferred normal disagreed with the sketch's, the hole classification produced no hole. Every face is now built on the sketch's own gp_Pln. Also in this change, from the same rig session: - A right-click that only clears the sketch selection no longer reports itself as consumed, so it stops suppressing the offer menu. With any geometry in a live sketch there was no menu route left to add a second entity. - Escape no longer discards a live sketch that holds drawn geometry; it says so and keeps the work (live_sketch_has_work()). - The holed-region fill is an even-odd scanline instead of a keyhole bridge, so no corridor triangle leaks from the bore to the nearest corner. - Cyan is reserved for the selection: an unselected region no longer wears a shade one step off the selected one. - The origin planes follow the mode, so pressing Sketch on a document that already has a body offers them again instead of naming a plane you cannot see. Tests: three [holes] cases over add_extrude_entities asserting the plate-with-bore volume, solid and face counts on both a +Z and a -Z sketch plane, and the by-name refusal of two disjoint regions. Full [CadDocument] suite green.
This commit is contained in:
@@ -3321,7 +3321,7 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
|
||||
// Onshape flow: clicking inside a closed-loop face commits the sketch and opens
|
||||
// the Extrude dialog (with a ghost preview) targeting that sketch.
|
||||
m_viewport->set_on_sketch_face_selected([this]() {
|
||||
m_viewport->set_on_sketch_face_selected([this](int region) {
|
||||
if (!m_viewport) return;
|
||||
m_viewport->finish_sketch(); // commit live sketch (synchronous)
|
||||
m_extrude_sketch_ref = resolve_extrude_sketch();
|
||||
@@ -3333,6 +3333,13 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
}
|
||||
set_ui_mode(UiMode::Feature);
|
||||
open_tool(Tool::Extrude);
|
||||
// AFTER open_tool, not before: opening the tool re-derives the selection state, so a
|
||||
// region recorded ahead of it is wiped before Extrude ever reads it.
|
||||
m_sel_sketch_feat = m_extrude_sketch_ref;
|
||||
m_sel_sketch_region = region;
|
||||
m_sel_solid_face = m_sel_solid_edge = -1;
|
||||
m_pick_face = m_pick_face_body = -1;
|
||||
m_viewport->set_loop_pick(m_extrude_sketch_ref, region);
|
||||
m_status->SetForegroundColour(wxNullColour);
|
||||
set_status(_L("Face selected — set the depth and Confirm"));
|
||||
m_status->Refresh();
|
||||
@@ -3820,6 +3827,22 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
// Tool shortcuts (Onshape-style). While a sketch is open, single letters drive sketch
|
||||
// tools; otherwise Shift+letter drives feature tools and single letters drive view
|
||||
// toggles / section. Ctrl-combos and focused text fields are never intercepted.
|
||||
// A SKETCH SHORTCUT MAY LEAVE AN OPEN VALUE FIELD. in_text is true while an inline
|
||||
// dimension editor is up, and drawing a rectangle opens one automatically for its
|
||||
// Width/Height — so after a rectangle every single-letter tool key was swallowed and the
|
||||
// sketch could not be continued at all. A value field holds a NUMBER; a letter is never
|
||||
// meant for it, so a letter that names a sketch tool is unambiguously a tool switch.
|
||||
// set_tool() commits the field on the way through, so the typed value is kept.
|
||||
// Deliberately narrow: sketch mode only, only keys that are actually bound, and only the
|
||||
// in-canvas field — a focused wxTextCtrl elsewhere (the variables table, a card spin)
|
||||
// still keeps every key.
|
||||
const bool inline_field_only = (dynamic_cast<wxTextCtrl*>(wxWindow::FindFocus()) == nullptr)
|
||||
&& m_viewport && m_viewport->inline_busy();
|
||||
if (in_text && inline_field_only && sketch_mode && !ctrl) {
|
||||
const int up2 = (key >= 'a' && key <= 'z') ? key - 'a' + 'A' : key;
|
||||
auto it2 = m_keys_sketch.find(up2);
|
||||
if (it2 != m_keys_sketch.end()) { it2->second(); return; }
|
||||
}
|
||||
if (!in_text && !ctrl) {
|
||||
const int up = (key >= 'a' && key <= 'z') ? key - 'a' + 'A' : key; // normalise case
|
||||
if (sketch_mode) {
|
||||
@@ -4525,6 +4548,10 @@ void DesignPanel::on_add_extrude()
|
||||
} else if (extrude_uses_loop()) {
|
||||
// Extrude just the selected loop (its entity subset), leaving the source sketch's
|
||||
// other loops intact and still selectable.
|
||||
if (::getenv("SNAPORCA_PICK_TRACE"))
|
||||
std::fprintf(stderr, "[pick] on_add_extrude: feat=%d reg=%d ents=%zu\n",
|
||||
m_extrude_sketch_ref, m_sel_sketch_region,
|
||||
m_viewport->selected_loop_entities().size());
|
||||
idx = m_doc.add_extrude_entities(m_viewport->selected_loop_entities(),
|
||||
m_doc.features[m_extrude_sketch_ref].plane,
|
||||
m_distance->GetValue(), false, mode, name);
|
||||
@@ -10244,6 +10271,18 @@ void DesignPanel::tool_cancel()
|
||||
if (m_active == Tool::Insert) { cancel_insert(); return; }
|
||||
if (m_active != Tool::None) { cancel_tool(); return; }
|
||||
if (m_ui_mode == UiMode::Sketch) {
|
||||
// Escape is how anyone dismisses the inline dimension field, and it used to cascade
|
||||
// straight through to here: first press disarmed the tool, second press dropped the
|
||||
// whole live session — a drawn rectangle gone, silently, with no undo prompt. That is
|
||||
// the "I cannot add the circle after the rectangle" report: the sketch was already
|
||||
// destroyed. Discarding real work needs the explicit Cancel button, not a key people
|
||||
// press to close a text field.
|
||||
if (m_viewport && m_viewport->live_sketch_has_work()) {
|
||||
m_status->SetForegroundColour(wxNullColour);
|
||||
set_status(_L("Sketch kept — use Confirm to keep it, Cancel to discard"));
|
||||
m_status->Refresh();
|
||||
return;
|
||||
}
|
||||
if (m_viewport) m_viewport->cancel_sketch(); // drop the live session (committed art stays)
|
||||
m_edit_index = -1;
|
||||
set_ui_mode(UiMode::Feature);
|
||||
|
||||
Reference in New Issue
Block a user