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
+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