mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-18 22:42:37 +00:00
Design: the Hole and Thread cards say which face they are holding
snaporca-200 asked which of the two models of "the card's face input" is right, because clicking empty canvas now clears the selection (snaporca-od0) and made them visibly disagree: Thicken / Shell / Draft read the LIVE selection and their label reverts to "(pick a solid face)", while Hole / Thread LATCH the face they were opened or picked on and keep it. The complaint was that Hole then drills a face you can no longer see selected. Taken to the rig, that turns out to be the wrong half of the story. With Hole open and its face picked, a click on empty canvas leaves the Ø6.0 ghost and its dimension gizmo drawn on that exact face — the card was never operating in secret, it was showing its target the strongest way a CAD tool can. Meanwhile Draft, whose behaviour was held up as the honest one, threw the pick away and had to be told the face again. So neither model replaces the other. They are different in kind: Thicken / Shell / Draft are operations whose operand IS the selected face, and Hole / Thread are placement tools with their own plane state that a pick merely seeds. The latch is also the kinder of the two now that empty clicks are a deliberate gesture — a stray one costs Thicken a pick and costs Hole nothing. What was genuinely missing is that nothing in those two cards NAMED the latched face, so after such a click the only words on screen were the viewport's "Nothing selected" over a ghost about to drill. Both cards now carry an "On face" row, the way the other three already do: Hole Face 5 | (none — uses Hole plane) Thread Face 1 | Edge 2 | (none — uses Thread plane) Thread names an edge when the cylinder came from a circular rim rather than a cylindrical face, which the code already distinguished internally and never said out loud. Verified on both rigs, every state driven through the GUI: face pick on open and on live pick, survival across a click on empty canvas, and the fallback after choosing XY/XZ/YZ from the plane dropdown. Thread's edge branch was exercised on a revolved tube's rim, its face branch on the same tube's outer wall. Filed while here, surfaced by the new row rather than caused by it — snaporca-uif9: re-editing a stored Hole/Thread from the feature tree restores f.plane into the dropdown but never clears m_hole_on_face, so the re-edit silently reuses the PREVIOUS card's latched face. Now visible by name instead of invisible. Fork parity unchanged: DesignPanel.cpp 30, DesignPanel.hpp 0.
This commit is contained in:
@@ -985,6 +985,7 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
m_hole_on_face = false;
|
||||
m_hole_face_body = -1;
|
||||
m_hole_has_bounds = false;
|
||||
set_hole_target_label(-1);
|
||||
// Use the explicitly-picked face; otherwise default to the top face of the
|
||||
// selected (or first) body so the hole lands on the surface being viewed, not
|
||||
// the z=0 datum under the model. The XY/XZ/YZ dropdown still overrides.
|
||||
@@ -1000,6 +1001,7 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
m_hole_face_plane = face_plane_inward(face);
|
||||
m_hole_on_face = true;
|
||||
m_hole_face_body = hb;
|
||||
set_hole_target_label(hf); // may be the top-face default, not a pick
|
||||
// Face (u,v) extents so the hole dims read from the sides (#2 Part B).
|
||||
m_hole_has_bounds = GeometryEngine::face_plane_bounds(
|
||||
face, m_hole_face_plane.origin, m_hole_face_plane.x_axis,
|
||||
@@ -1021,11 +1023,14 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
// the user never types a radius. The diameter field shows what was derived.
|
||||
m_thread_on_face = false;
|
||||
m_thread_face_body = -1;
|
||||
set_thread_target_label(-1, -1);
|
||||
GeometryEngine::CylinderFace cf;
|
||||
bool from_face = false; // which of the two the cylinder actually came from
|
||||
if (m_sel_solid_body >= 0 && m_sel_solid_body < int(m_doc.bodies.size())) {
|
||||
const TopoDS_Shape& shape = m_doc.bodies[m_sel_solid_body].shape;
|
||||
if (m_sel_solid_face >= 0)
|
||||
cf = GeometryEngine::cylinder_of_face(GeometryEngine::face_by_index(shape, m_sel_solid_face));
|
||||
from_face = cf.ok;
|
||||
if (!cf.ok && m_sel_solid_edge >= 0)
|
||||
cf = GeometryEngine::circle_of_edge(GeometryEngine::edge_by_index(shape, m_sel_solid_edge));
|
||||
}
|
||||
@@ -1039,6 +1044,8 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
m_thread_face_plane = p;
|
||||
m_thread_on_face = true;
|
||||
m_thread_face_body = m_sel_solid_body;
|
||||
set_thread_target_label(from_face ? m_sel_solid_face : -1,
|
||||
from_face ? -1 : m_sel_solid_edge);
|
||||
infer_thread_spec(2.0 * cf.radius); // M diameter + pitch + depth from the cylinder
|
||||
if (m_thread_height && cf.height > 1e-6) m_thread_height->SetValue(cf.height);
|
||||
if (m_thread_internal) m_thread_internal->SetValue(cf.internal);
|
||||
@@ -1719,6 +1726,7 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
m_hole_plane->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& e) {
|
||||
m_hole_on_face = false;
|
||||
m_hole_has_bounds = false;
|
||||
set_hole_target_label(-1);
|
||||
update_hole_gizmo();
|
||||
refresh_preview();
|
||||
e.Skip();
|
||||
@@ -1747,6 +1755,10 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
hform->Add(new wxStaticText(m_cards, wxID_ANY, _L("Through")), 0, wxALIGN_CENTER_VERTICAL);
|
||||
hform->Add(m_hole_through, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
|
||||
|
||||
m_hole_target_label = new wxStaticText(m_cards, wxID_ANY, _L("(none — uses Hole plane)"));
|
||||
hform->Add(new wxStaticText(m_cards, wxID_ANY, _L("On face")), 0, wxALIGN_CENTER_VERTICAL);
|
||||
hform->Add(m_hole_target_label, 0, wxALIGN_CENTER_VERTICAL);
|
||||
|
||||
m_box_hole = new wxBoxSizer(wxVERTICAL);
|
||||
m_box_hole->Add(card_header(m_cards, "design_hole", _L("Hole"), m_hdr_hole), 0, wxLEFT | wxRIGHT | wxTOP, 12);
|
||||
m_box_hole->Add(new wxStaticLine(m_cards), 0, wxEXPAND | wxALL, 8);
|
||||
@@ -1812,6 +1824,10 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
tform->Add(new wxStaticText(m_cards, wxID_ANY, _L("Internal")), 0, wxALIGN_CENTER_VERTICAL);
|
||||
tform->Add(m_thread_internal, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
|
||||
|
||||
m_thread_target_label = new wxStaticText(m_cards, wxID_ANY, _L("(none — uses Thread plane)"));
|
||||
tform->Add(new wxStaticText(m_cards, wxID_ANY, _L("On face")), 0, wxALIGN_CENTER_VERTICAL);
|
||||
tform->Add(m_thread_target_label, 0, wxALIGN_CENTER_VERTICAL);
|
||||
|
||||
m_box_thread = new wxBoxSizer(wxVERTICAL);
|
||||
m_box_thread->Add(card_header(m_cards, "design_thread", _L("Thread"), m_hdr_thread), 0, wxLEFT | wxRIGHT | wxTOP, 12);
|
||||
m_box_thread->Add(new wxStaticLine(m_cards), 0, wxEXPAND | wxALL, 8);
|
||||
@@ -3348,6 +3364,7 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
m_hole_face_plane = face_plane_inward(fc);
|
||||
m_hole_on_face = true;
|
||||
m_hole_face_body = body;
|
||||
set_hole_target_label(face);
|
||||
m_hole_has_bounds = GeometryEngine::face_plane_bounds(
|
||||
fc, m_hole_face_plane.origin, m_hole_face_plane.x_axis,
|
||||
m_hole_face_plane.y_axis, m_hole_umin, m_hole_umax, m_hole_vmin, m_hole_vmax);
|
||||
@@ -3363,6 +3380,7 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
GeometryEngine::CylinderFace cf;
|
||||
if (face >= 0)
|
||||
cf = GeometryEngine::cylinder_of_face(GeometryEngine::face_by_index(shape, face));
|
||||
const bool from_face = cf.ok; // which of the two the cylinder actually came from
|
||||
if (!cf.ok && edge >= 0)
|
||||
cf = GeometryEngine::circle_of_edge(GeometryEngine::edge_by_index(shape, edge));
|
||||
if (cf.ok) {
|
||||
@@ -3373,6 +3391,7 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
m_thread_face_plane = p;
|
||||
m_thread_on_face = true;
|
||||
m_thread_face_body = m_sel_solid_body;
|
||||
set_thread_target_label(from_face ? face : -1, from_face ? -1 : edge);
|
||||
infer_thread_spec(2.0 * cf.radius); // M diameter + pitch + depth from the cylinder
|
||||
if (m_thread_height && cf.height > 1e-6) m_thread_height->SetValue(cf.height);
|
||||
if (m_thread_internal) m_thread_internal->SetValue(cf.internal);
|
||||
@@ -4409,6 +4428,32 @@ void DesignPanel::on_add_dressup()
|
||||
refresh_tree();
|
||||
}
|
||||
|
||||
// Hole and Thread LATCH the geometry they were opened or picked on; Thicken / Shell / Draft read
|
||||
// the live selection instead. Both models are right for what they are — a placement tool with its
|
||||
// own plane state, versus an operation whose operand IS the selected face — and the latch is the
|
||||
// kinder of the two now that a click on empty canvas clears the selection (snaporca-od0): a stray
|
||||
// click costs a Thicken pick, and costs a Hole nothing. What was missing is that nothing in the
|
||||
// Hole/Thread card NAMED the latched face, so after such a click the only words on screen were
|
||||
// the viewport's "Nothing selected" — over a ghost still drawn on the face Confirm would drill.
|
||||
// That reads as a contradiction and was filed as one (snaporca-200). The card now says what it
|
||||
// holds, the way the other three cards already do. Pass -1 for "none, using the plane dropdown".
|
||||
void DesignPanel::set_hole_target_label(int face)
|
||||
{
|
||||
if (m_hole_target_label)
|
||||
m_hole_target_label->SetLabel(face >= 0 ? wxString::Format(_L("Face %d"), face)
|
||||
: _L("(none — uses Hole plane)"));
|
||||
}
|
||||
|
||||
// Thread also latches onto a circular EDGE (a cylinder's rim), so it has two kinds to name.
|
||||
void DesignPanel::set_thread_target_label(int face, int edge)
|
||||
{
|
||||
if (!m_thread_target_label) return;
|
||||
m_thread_target_label->SetLabel(
|
||||
face >= 0 ? wxString::Format(_L("Face %d"), face)
|
||||
: edge >= 0 ? wxString::Format(_L("Edge %d"), edge)
|
||||
: _L("(none — uses Thread plane)"));
|
||||
}
|
||||
|
||||
SketchPlane DesignPanel::hole_plane() const
|
||||
{
|
||||
if (m_hole_on_face) return m_hole_face_plane;
|
||||
|
||||
Reference in New Issue
Block a user