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()))
+50 -54
View File
@@ -1,7 +1,10 @@
#include "DesignSketchTool.hpp"
#include "GLCanvas3D.hpp"
#include "GUI_App.hpp"
#include "ImGuiWrapper.hpp"
#include "Plater.hpp"
#include <imgui/imgui.h>
#include "libslic3r/BuildVolume.hpp"
#include "Camera.hpp"
#include "3DScene.hpp"
@@ -1882,7 +1885,12 @@ std::string DesignSketchTool::dim_text(const DimAnnot& a) const
// locale at startup, so snprintf("%.1f") can emit a comma. Normalise it.
for (char& ch : buf)
if (ch == ',') ch = '.';
return std::string(buf);
std::string out(buf);
if (a.kind != DimType::Angle) {
const bool use_in = wxGetApp().app_config->get_bool("use_inches");
out += use_in ? " in" : " mm";
}
return out;
}
void DesignSketchTool::apply_segment_length(double len)
@@ -4898,50 +4906,43 @@ void glyph_strokes(char c, std::vector<std::pair<Vec2d, Vec2d>>& out, double& ad
}
} // namespace
void DesignSketchTool::draw_text(GLModel& model, const std::string& s, const Vec2d& center,
double height, const ColorRGBA& color)
void DesignSketchTool::draw_dim_label(const std::string& txt, const Vec2d& plane_center)
{
std::vector<std::vector<std::pair<Vec2d, Vec2d>>> glyphs;
std::vector<double> advs;
double total = 0.0;
for (size_t i = 0; i < s.size(); ++i) {
std::vector<std::pair<Vec2d, Vec2d>> gs;
double adv = 0.5;
if ((unsigned char)s[i] == 0xC3 && i + 1 < s.size() && (unsigned char)s[i + 1] == 0x98) {
glyph_strokes('0', gs, adv); // 'Ø' = '0' + slash
gs.emplace_back(Vec2d(0.0, 0.0), Vec2d(0.6, 1.0));
++i;
} else if ((unsigned char)s[i] == 0xC2 && i + 1 < s.size() && (unsigned char)s[i + 1] == 0xB0) {
// '°' degree sign: a small open ring high in the cell, approximated by a
// short polyline loop (the stroke font has no curves primitive here).
const Vec2d c(0.18, 0.85); const double r = 0.16;
const int N = 8; Vec2d prev = c + Vec2d(r, 0);
for (int k = 1; k <= N; ++k) {
const double t = 2.0 * 3.14159265358979 * k / N;
const Vec2d cur = c + Vec2d(r * std::cos(t), r * std::sin(t));
gs.emplace_back(prev, cur); prev = cur;
}
adv = 0.42;
++i;
} else {
glyph_strokes(s[i], gs, adv);
}
glyphs.push_back(std::move(gs));
advs.push_back(adv);
total += adv;
}
const Vec2d origin = center - Vec2d(total * height * 0.5, height * 0.5);
std::vector<std::pair<Vec2d, Vec2d>> world;
double pen = 0.0;
for (size_t g = 0; g < glyphs.size(); ++g) {
for (const auto& seg : glyphs[g]) {
const Vec2d a = origin + Vec2d((seg.first.x() + pen) * height, seg.first.y() * height);
const Vec2d b = origin + Vec2d((seg.second.x() + pen) * height, seg.second.y() * height);
world.emplace_back(a, b);
}
pen += advs[g];
}
draw_strokes(model, world, std::max(height * 0.08, 0.02), color);
if (txt.empty()) return;
const Camera& cam = wxGetApp().plater()->get_camera();
const wxPoint sp = world_to_screen_px(cam, m_plane.to_world(plane_center));
if (sp.x < 0 && sp.y < 0) return;
ImGuiWrapper* imgui = wxGetApp().imgui();
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(1.0f, 1.0f));
imgui->set_next_window_pos((float)sp.x, (float)sp.y, ImGuiCond_Always, 0.5f, 0.5f);
imgui->set_next_window_bg_alpha(0.0f);
const std::string win = "##sketchdim" + std::to_string(m_dim_label_seq++);
imgui->begin(win, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration
| ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoFocusOnAppearing
| ImGuiWindowFlags_NoNav);
ImDrawList* dl = ImGui::GetWindowDrawList();
const ImVec2 pos = ImGui::GetCursorScreenPos();
const ImVec2 ts = ImGui::CalcTextSize(txt.c_str());
const ImGuiStyle& st = ImGui::GetStyle();
dl->AddRectFilled(ImVec2(pos.x - st.FramePadding.x, pos.y + st.FramePadding.y),
ImVec2(pos.x + ts.x + 2.0f * st.FramePadding.x,
pos.y + ts.y + 2.0f * st.FramePadding.y),
ImGuiWrapper::to_ImU32(ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f)));
ImGui::SetCursorScreenPos(ImVec2(pos.x + st.FramePadding.x, pos.y));
imgui->text(txt);
imgui->end();
ImGui::PopStyleVar(3);
}
void DesignSketchTool::draw_text(GLModel& /*model*/, const std::string& s, const Vec2d& center,
double /*height*/, const ColorRGBA& /*color*/)
{
// ponytail: all sketch labels now render as Measure-gizmo-style ImGui labels for visual
// parity with the Prepare/Preview tabs; the old vector-font path (glyph_strokes/draw_strokes
// for text) is retired. Leader lines/arrows still draw via draw_strokes at the call sites.
draw_dim_label(s, center);
}
// Draw every placed dimension: extension lines, the offset dimension line, arrowheads
@@ -4968,21 +4969,15 @@ bool DesignSketchTool::draw_dim_quote(const DimAnnot& a, double th, const ColorR
if (L < 1e-6) return false;
const Vec2d u = d / L;
const Vec2d nrm(-u.y(), u.x());
const double side = (a.side != 0.0) ? a.side : 1.0;
const double off = side * std::max(L * 0.18, 8.0);
const Vec2d A2 = pa + nrm * off, B2 = pb + nrm * off;
const Vec2d ext = nrm * (off + side * 2.0);
segs.emplace_back(pa, pa + ext); // extension lines
segs.emplace_back(pb, pb + ext);
segs.emplace_back(A2, B2); // dimension line
segs.emplace_back(pa, pb); // single point-to-point dimension line
const double as = std::max(L * 0.04, 2.0);
auto arrow = [&](const Vec2d& tip, const Vec2d& dir) {
const Vec2d back = tip + dir * as;
segs.emplace_back(tip, back + nrm * (as * 0.5));
segs.emplace_back(tip, back - nrm * (as * 0.5));
};
arrow(A2, u); arrow(B2, -u);
out_label = (A2 + B2) * 0.5 + nrm * (side * (th * 0.7 + 1.5));
arrow(pa, u); arrow(pb, -u);
out_label = (pa + pb) * 0.5 + nrm * (th * 0.8);
} else if (a.kind == DimType::Diameter || a.kind == DimType::Radius) {
if (a.ea < 0 || a.ea >= int(m_entities.size())) return false;
const SketchEntity& e = m_entities[a.ea];
@@ -5049,7 +5044,7 @@ bool DesignSketchTool::draw_dim_quote(const DimAnnot& a, double th, const ColorR
void DesignSketchTool::render_dimensions(double unit_per_px)
{
if (m_dimensions.empty()) return;
const ColorRGBA dimcol(0.30f, 0.88f, 0.66f, 1.0f); // teal-green CAD quote
const ColorRGBA dimcol(0.85f, 0.85f, 0.85f, 1.0f); // neutral leader (Measure parity)
// Label text is a CONSTANT screen size (like real CAD), not scaled to geometry,
// so a long line doesn't get huge text. ~15 px tall in plane units at this zoom.
const double th = std::max(15.0 * unit_per_px, 1e-4);
@@ -6276,6 +6271,7 @@ void DesignSketchTool::confirm_transform()
void DesignSketchTool::render(GLCanvas3D& canvas)
{
m_dim_label_seq = 0;
(void)canvas;
if (!has_display()) {
if (on_readout) on_readout(std::string()); // nothing to show -> hide HUD
+2
View File
@@ -570,6 +570,7 @@ private:
double hw, const ColorRGBA& color);
void draw_text(GLModel& model, const std::string& s, const Vec2d& center,
double height, const ColorRGBA& color); // GL stroke font
void draw_dim_label(const std::string& txt, const Vec2d& plane_center);
// Entity builders: append to m_entities (honoring the construction flag).
void push_line(const Vec2d& a, const Vec2d& b);
@@ -817,6 +818,7 @@ private:
GLModel m_line_model;
GLModel m_vertex_model;
GLModel m_highlight_model;
int m_dim_label_seq{0};
GLModel m_fill_model; // translucent face fill for closed regions
std::vector<DisplaySketch> m_display_sketches; // committed sketches drawn persistently
int m_display_pick{-1}; // FEATURE index of the click-selected display sketch (-1 none)