Design: Measure-style dimension labels + restore English-only pin + UX consistency

Dimensions (uniform with Prepare/Preview):
- Repurpose DesignSketchTool::draw_text -> new draw_dim_label that renders each
  sketch dimension as the exact Prepare "Measure" gizmo label: white ImGui text
  in a translucent-white box, positioned via the existing world_to_screen_px
  projection inside the active ImGui frame. All 29 label call sites convert with
  no churn; the bespoke Hershey vector font is retired.
- dim_text appends mm/in on linear dims (angles keep the degree sign).
- Placed-dimension leaders simplified to a single point-to-point line + arrows in
  a neutral colour (no extension lines), matching the Measure look.

i18n (Design tab pinned English, per the UX contract):
- Restore the lost "#undef _L / #define _L(s) wxString::FromUTF8(s)" override atop
  DesignPanel.cpp so one lever de-translates the whole tab, ending the half-EN/IT
  state. Wrap all ~54 dropdown options in _L so the single lever governs them.
- feature_type_name left untranslated (it feeds the MCP JSON, machine-facing).

UX consistency:
- Remove the per-card Confirm/Cancel buttons from the Value card; the single ribbon
  action bar now owns value confirm/cancel via an m_value_cont guard in
  tool_confirm/tool_cancel (one confirm surface, per contract).
- Unify the eight divergent "needs a body" status messages to one template.

Both forks; DesignSketchTool.{cpp,hpp} byte-identical across forks. Built clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
This commit is contained in:
Tommaso Bianchi
2026-06-30 22:01:54 +02:00
co-authored by Claude Opus 4.8
parent f341ee2677
commit 6e11cfc49b
3 changed files with 114 additions and 114 deletions
+62 -60
View File
@@ -41,6 +41,15 @@
#include "slic3r/GUI/MainFrame.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
// English-only pin for the Design tab (see snaporca-design-ux-contract): one lever
// de-translates this whole TU so our strings never half-translate against the host's
// localized chrome. Host UI still follows the app locale; only this tab is pinned EN.
// GOTCHA: every _L(...) in this file must take a STRING LITERAL (FromUTF8 wants const char*).
#ifdef _L
#undef _L
#endif
#define _L(s) wxString::FromUTF8(s)
namespace Slic3r { namespace GUI {
// Format a value with the international ('.') decimal separator regardless of the
@@ -815,16 +824,16 @@ DesignPanel::DesignPanel(wxWindow* parent)
auto* form = new wxFlexGridSizer(2, 6, 8);
m_shape = new wxChoice(m_form, wxID_ANY);
m_shape->Append("Rectangle");
m_shape->Append("Circle");
m_shape->Append(_L("Rectangle"));
m_shape->Append(_L("Circle"));
m_shape->SetSelection(0);
form->Add(new wxStaticText(m_form, wxID_ANY, _L("Shape")), 0, wxALIGN_CENTER_VERTICAL);
form->Add(m_shape);
m_plane = new wxChoice(m_form, wxID_ANY);
m_plane->Append("XY");
m_plane->Append("XZ");
m_plane->Append("YZ");
m_plane->Append(_L("XY"));
m_plane->Append(_L("XZ"));
m_plane->Append(_L("YZ"));
m_plane->SetSelection(0);
form->Add(new wxStaticText(m_form, wxID_ANY, _L("Plane")), 0, wxALIGN_CENTER_VERTICAL);
form->Add(m_plane);
@@ -901,17 +910,17 @@ DesignPanel::DesignPanel(wxWindow* parent)
auto* dform = new wxFlexGridSizer(2, 6, 8);
m_dressup_type = new wxChoice(m_form, wxID_ANY);
m_dressup_type->Append("Fillet");
m_dressup_type->Append("Chamfer");
m_dressup_type->Append(_L("Fillet"));
m_dressup_type->Append(_L("Chamfer"));
m_dressup_type->SetSelection(0);
dform->Add(new wxStaticText(m_form, wxID_ANY, _L("Dress-up")), 0, wxALIGN_CENTER_VERTICAL);
dform->Add(m_dressup_type);
m_face_group = new wxChoice(m_form, wxID_ANY);
m_face_group->Append("Top"); // index 0 -> FaceGroup::Top
m_face_group->Append("Bottom"); // 1 -> Bottom
m_face_group->Append("Lateral"); // 2 -> Lateral
m_face_group->Append("All"); // 3 -> All
m_face_group->Append(_L("Top")); // index 0 -> FaceGroup::Top
m_face_group->Append(_L("Bottom")); // 1 -> Bottom
m_face_group->Append(_L("Lateral")); // 2 -> Lateral
m_face_group->Append(_L("All")); // 3 -> All
m_face_group->SetSelection(3);
dform->Add(new wxStaticText(m_form, wxID_ANY, _L("Edges")), 0, wxALIGN_CENTER_VERTICAL);
dform->Add(m_face_group);
@@ -930,9 +939,9 @@ DesignPanel::DesignPanel(wxWindow* parent)
auto* hform = new wxFlexGridSizer(2, 6, 8);
m_hole_plane = new wxChoice(m_form, wxID_ANY);
m_hole_plane->Append("XY");
m_hole_plane->Append("XZ");
m_hole_plane->Append("YZ");
m_hole_plane->Append(_L("XY"));
m_hole_plane->Append(_L("XZ"));
m_hole_plane->Append(_L("YZ"));
m_hole_plane->SetSelection(0);
// Picking a plane here is an explicit choice: drop any on-face hijack (a stale face pick
// could keep m_hole_on_face true, so the dropdown was ignored and the hole drilled on the
@@ -978,9 +987,9 @@ DesignPanel::DesignPanel(wxWindow* parent)
auto* tform = new wxFlexGridSizer(2, 6, 8);
m_thread_plane = new wxChoice(m_form, wxID_ANY);
m_thread_plane->Append("XY");
m_thread_plane->Append("XZ");
m_thread_plane->Append("YZ");
m_thread_plane->Append(_L("XY"));
m_thread_plane->Append(_L("XZ"));
m_thread_plane->Append(_L("YZ"));
m_thread_plane->SetSelection(0);
tform->Add(new wxStaticText(m_form, wxID_ANY, _L("Thread plane")), 0, wxALIGN_CENTER_VERTICAL);
tform->Add(m_thread_plane);
@@ -1053,17 +1062,17 @@ DesignPanel::DesignPanel(wxWindow* parent)
rform->Add(m_revolve_angle);
m_revolve_axis = new wxChoice(m_form, wxID_ANY);
m_revolve_axis->Append("Plane X");
m_revolve_axis->Append("Plane Y");
m_revolve_axis->Append(_L("Plane X"));
m_revolve_axis->Append(_L("Plane Y"));
m_revolve_axis->SetSelection(0);
rform->Add(new wxStaticText(m_form, wxID_ANY, _L("Axis")), 0, wxALIGN_CENTER_VERTICAL);
rform->Add(m_revolve_axis);
m_revolve_mode = new wxChoice(m_form, wxID_ANY);
m_revolve_mode->Append("New");
m_revolve_mode->Append("Add");
m_revolve_mode->Append("Cut");
m_revolve_mode->Append("Intersect");
m_revolve_mode->Append(_L("New"));
m_revolve_mode->Append(_L("Add"));
m_revolve_mode->Append(_L("Cut"));
m_revolve_mode->Append(_L("Intersect"));
m_revolve_mode->SetSelection(0);
rform->Add(new wxStaticText(m_form, wxID_ANY, _L("Mode")), 0, wxALIGN_CENTER_VERTICAL);
rform->Add(m_revolve_mode);
@@ -1090,10 +1099,10 @@ DesignPanel::DesignPanel(wxWindow* parent)
sform->Add(m_sweep_path);
m_sweep_mode = new wxChoice(m_form, wxID_ANY);
m_sweep_mode->Append("New");
m_sweep_mode->Append("Add");
m_sweep_mode->Append("Cut");
m_sweep_mode->Append("Intersect");
m_sweep_mode->Append(_L("New"));
m_sweep_mode->Append(_L("Add"));
m_sweep_mode->Append(_L("Cut"));
m_sweep_mode->Append(_L("Intersect"));
m_sweep_mode->SetSelection(0);
sform->Add(new wxStaticText(m_form, wxID_ANY, _L("Mode")), 0, wxALIGN_CENTER_VERTICAL);
sform->Add(m_sweep_mode);
@@ -1110,8 +1119,8 @@ DesignPanel::DesignPanel(wxWindow* parent)
auto* pform = new wxFlexGridSizer(2, 6, 8);
m_pattern_type = new wxChoice(m_form, wxID_ANY);
m_pattern_type->Append("Linear");
m_pattern_type->Append("Circular");
m_pattern_type->Append(_L("Linear"));
m_pattern_type->Append(_L("Circular"));
m_pattern_type->SetSelection(0);
pform->Add(new wxStaticText(m_form, wxID_ANY, _L("Type")), 0, wxALIGN_CENTER_VERTICAL);
pform->Add(m_pattern_type);
@@ -1125,8 +1134,8 @@ DesignPanel::DesignPanel(wxWindow* parent)
pform->Add(m_pattern_spacing);
m_pattern_dir = new wxChoice(m_form, wxID_ANY);
m_pattern_dir->Append("Plane X");
m_pattern_dir->Append("Plane Y");
m_pattern_dir->Append(_L("Plane X"));
m_pattern_dir->Append(_L("Plane Y"));
m_pattern_dir->SetSelection(0);
pform->Add(new wxStaticText(m_form, wxID_ANY, _L("Direction")), 0, wxALIGN_CENTER_VERTICAL);
pform->Add(m_pattern_dir);
@@ -1147,9 +1156,9 @@ DesignPanel::DesignPanel(wxWindow* parent)
auto* bform = new wxFlexGridSizer(2, 6, 8);
m_bool_op = new wxChoice(m_form, wxID_ANY);
m_bool_op->Append("Union (join)");
m_bool_op->Append("Subtract (cut)");
m_bool_op->Append("Intersect");
m_bool_op->Append(_L("Union (join)"));
m_bool_op->Append(_L("Subtract (cut)"));
m_bool_op->Append(_L("Intersect"));
m_bool_op->SetSelection(0);
m_bool_op->Bind(wxEVT_CHOICE, [this](wxCommandEvent&) { refresh_preview(); });
bform->Add(new wxStaticText(m_form, wxID_ANY, _L("Operation")), 0, wxALIGN_CENTER_VERTICAL);
@@ -1254,8 +1263,8 @@ DesignPanel::DesignPanel(wxWindow* parent)
plform->Add(m_plane_tilt);
m_plane_tilt_axis = new wxChoice(m_form, wxID_ANY);
m_plane_tilt_axis->Append("Base X");
m_plane_tilt_axis->Append("Base Y");
m_plane_tilt_axis->Append(_L("Base X"));
m_plane_tilt_axis->Append(_L("Base Y"));
m_plane_tilt_axis->SetSelection(0);
plform->Add(new wxStaticText(m_form, wxID_ANY, _L("Tilt axis")), 0, wxALIGN_CENTER_VERTICAL);
plform->Add(m_plane_tilt_axis);
@@ -1297,10 +1306,10 @@ DesignPanel::DesignPanel(wxWindow* parent)
auto* lform = new wxFlexGridSizer(2, 6, 8);
m_loft_mode = new wxChoice(m_form, wxID_ANY);
m_loft_mode->Append("New");
m_loft_mode->Append("Add");
m_loft_mode->Append("Cut");
m_loft_mode->Append("Intersect");
m_loft_mode->Append(_L("New"));
m_loft_mode->Append(_L("Add"));
m_loft_mode->Append(_L("Cut"));
m_loft_mode->Append(_L("Intersect"));
m_loft_mode->SetSelection(0);
lform->Add(new wxStaticText(m_form, wxID_ANY, _L("Mode")), 0, wxALIGN_CENTER_VERTICAL);
lform->Add(m_loft_mode);
@@ -1365,15 +1374,6 @@ DesignPanel::DesignPanel(wxWindow* parent)
vrow->Add(new wxStaticText(m_form, wxID_ANY, _L("Value")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 8);
vrow->Add(m_value_input, 0, wxALIGN_CENTER_VERTICAL);
m_box_value->Add(vrow, 0, wxLEFT | wxRIGHT | wxTOP, 12);
auto* row = new wxBoxSizer(wxHORIZONTAL);
auto* ok = new wxButton(m_form, wxID_ANY, _L("✓ Confirm"));
ok->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { confirm_value(); });
auto* no = new wxButton(m_form, wxID_ANY, _L("✗ Cancel"));
no->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { cancel_value(); });
row->Add(ok, 0, wxRIGHT, 8);
row->Add(no, 0);
m_box_value->Add(row, 0, wxALL, 12);
}
root->Add(m_box_value, 0, wxEXPAND);
@@ -1387,7 +1387,7 @@ DesignPanel::DesignPanel(wxWindow* parent)
auto* prow = new wxBoxSizer(wxHORIZONTAL);
prow->Add(new wxStaticText(m_form, wxID_ANY, _L("Plane")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 8);
m_draw_plane = new wxChoice(m_form, wxID_ANY);
m_draw_plane->Append("XY"); m_draw_plane->Append("XZ"); m_draw_plane->Append("YZ");
m_draw_plane->Append(_L("XY")); m_draw_plane->Append(_L("XZ")); m_draw_plane->Append(_L("YZ"));
m_draw_plane->SetSelection(0);
prow->Add(m_draw_plane, 0, wxALIGN_CENTER_VERTICAL);
m_box_sketch_session->Add(prow, 0, wxLEFT | wxRIGHT | wxTOP, 12);
@@ -2526,7 +2526,7 @@ void DesignPanel::on_add_thread()
{
bool internal = m_thread_internal->GetValue();
if (internal && m_doc.body.IsNull()) {
m_status->SetLabel(_L("Internal thread needs a body — add a solid first"));
m_status->SetLabel(_L("Thread needs a solid body — add or import one first"));
return;
}
SketchPlane plane = thread_plane();
@@ -2556,7 +2556,7 @@ void DesignPanel::on_add_revolve()
}
const BooleanMode mode = static_cast<BooleanMode>(m_revolve_mode->GetSelection());
if (mode != BooleanMode::New && m_doc.body.IsNull()) {
m_status->SetLabel(_L("Add/Cut/Intersect revolve needs an existing body"));
m_status->SetLabel(_L("Revolve needs a solid body — add or import one first"));
return;
}
m_feature_counter++;
@@ -2587,7 +2587,7 @@ void DesignPanel::on_add_sweep()
}
const BooleanMode mode = static_cast<BooleanMode>(m_sweep_mode->GetSelection());
if (mode != BooleanMode::New && m_doc.body.IsNull()) {
m_status->SetLabel(_L("Add/Cut/Intersect sweep needs an existing body"));
m_status->SetLabel(_L("Sweep needs a solid body — add or import one first"));
return;
}
m_feature_counter++;
@@ -2615,7 +2615,7 @@ void DesignPanel::on_add_loft()
}
const BooleanMode mode = static_cast<BooleanMode>(m_loft_mode->GetSelection());
if (mode != BooleanMode::New && m_doc.body.IsNull()) {
m_status->SetLabel(_L("Add/Cut/Intersect loft needs an existing body"));
m_status->SetLabel(_L("Loft needs a solid body — add or import one first"));
return;
}
m_feature_counter++;
@@ -2633,7 +2633,7 @@ void DesignPanel::on_add_loft()
void DesignPanel::on_add_pattern()
{
if (m_doc.bodies.empty()) {
m_status->SetLabel(_L("Pattern needs a body — add a solid first"));
m_status->SetLabel(_L("Pattern needs a solid body — add or import one first"));
return;
}
const bool circular = (m_pattern_type->GetSelection() == 1);
@@ -2684,7 +2684,7 @@ void DesignPanel::populate_body_choices(int as_of_feature)
void DesignPanel::on_add_boolean()
{
if (m_doc.bodies.size() < 2) {
m_status->SetLabel(_L("Boolean needs two bodies"));
m_status->SetLabel(_L("Boolean needs two solid bodies — add or import a second one"));
return;
}
const int sel = m_bool_op->GetSelection();
@@ -2705,7 +2705,7 @@ void DesignPanel::on_add_boolean()
void DesignPanel::on_add_cut()
{
if (m_doc.bodies.empty()) {
m_status->SetLabel(_L("Cut needs a body"));
m_status->SetLabel(_L("Cut needs a solid body — add or import one first"));
return;
}
m_feature_counter++;
@@ -2724,7 +2724,7 @@ void DesignPanel::populate_plane_choices(wxChoice* c) const
if (!c) return;
const int keep = c->GetSelection();
c->Clear();
c->Append("XY"); c->Append("XZ"); c->Append("YZ");
c->Append(_L("XY")); c->Append(_L("XZ")); c->Append(_L("YZ"));
for (const auto& dp : m_doc.resolve_datum_planes())
c->Append(wxString::FromUTF8(dp.first));
c->SetSelection((keep >= 0 && keep < int(c->GetCount())) ? keep : 0);
@@ -2798,7 +2798,7 @@ void DesignPanel::on_add_plane()
void DesignPanel::on_add_shell()
{
if (m_doc.body.IsNull()) {
m_status->SetLabel(_L("Shell needs a body — add a solid first"));
m_status->SetLabel(_L("Shell needs a solid body — add or import one first"));
return;
}
const int face = (m_sel_solid_face >= 0) ? m_sel_solid_face : -1;
@@ -2818,7 +2818,7 @@ void DesignPanel::on_add_shell()
void DesignPanel::on_add_draft()
{
if (m_doc.body.IsNull()) {
m_status->SetLabel(_L("Draft needs a body — add a solid first"));
m_status->SetLabel(_L("Draft needs a solid body — add or import one first"));
return;
}
if (m_sel_solid_face < 0) {
@@ -5621,6 +5621,7 @@ void DesignPanel::cancel_tool()
// a feature card, the Insert placement, the Sketch session, or the Constrain session.
void DesignPanel::tool_confirm()
{
if (m_value_cont) { confirm_value(); return; } // value card owns ribbon ✓ while a value is pending
if (m_viewport && m_viewport->moving_body()) { // keep the placement, drop the gizmo
m_viewport->clear_move_gizmo();
m_move_body = -1;
@@ -5650,6 +5651,7 @@ void DesignPanel::tool_confirm()
// drawn-but-uncommitted Sketch, or exits Constrain.
void DesignPanel::tool_cancel()
{
if (m_value_cont) { cancel_value(); return; } // value card owns ribbon ✗ while a value is pending
if (m_viewport && m_viewport->moving_body()) { // revert to the pose at move-start
sync_body_xform();
if (m_move_body >= 0 && m_move_body < int(m_body_xform.size()))