Viewport: a legend for the Draft, Accessibility and Curvature Map analyses (#1299)
While Draft, Accessibility or Curvature Map analysis is on, a legend at the bottom left of the viewport says what each colour means and which values it stands for, with the theme's analysis colours and stripes. Labels translated in en/de/ja/ko/zh. Fixes #973.
This commit is contained in:
1 parent
873a65fd4d
commit
6ef0b22118
6 files changed
+113
No files matched your search
@@ -293,6 +293,10 @@
|
||||
"ui.quantity": "Anzahl",
|
||||
"ui.quit": "Beenden",
|
||||
"ui.radius": "Radius",
|
||||
"ui.pull_direction": "Entformungsrichtung",
|
||||
"ui.tool_direction": "Werkzeugrichtung",
|
||||
"ui.reachable": "Erreichbar",
|
||||
"ui.blocked": "Verdeckt",
|
||||
"ui.radius_type": "Radius-Typ",
|
||||
"ui.rectangle": "Rechteck",
|
||||
"ui.rectangular_pattern": "Rechteckmuster",
|
||||
|
||||
@@ -592,6 +592,10 @@
|
||||
"ui.quit": "Quit",
|
||||
"ui.radial_context_menu": "Radial context menu",
|
||||
"ui.radius": "Radius",
|
||||
"ui.pull_direction": "Pull direction",
|
||||
"ui.tool_direction": "Tool direction",
|
||||
"ui.reachable": "Reachable",
|
||||
"ui.blocked": "Blocked",
|
||||
"ui.radius_type": "Radius Type",
|
||||
"ui.rectangle": "Rectangle",
|
||||
"ui.rectangular_pattern": "Rectangular Pattern",
|
||||
|
||||
@@ -592,6 +592,10 @@
|
||||
"ui.quit": "終了",
|
||||
"ui.radial_context_menu": "パイ メニュー",
|
||||
"ui.radius": "半径",
|
||||
"ui.pull_direction": "抜き方向",
|
||||
"ui.tool_direction": "工具方向",
|
||||
"ui.reachable": "到達可能",
|
||||
"ui.blocked": "到達不可",
|
||||
"ui.radius_type": "半径タイプ",
|
||||
"ui.rectangle": "長方形",
|
||||
"ui.rectangular_pattern": "矩形パターン",
|
||||
|
||||
@@ -293,6 +293,10 @@
|
||||
"ui.quantity": "수량",
|
||||
"ui.quit": "종료",
|
||||
"ui.radius": "반지름",
|
||||
"ui.pull_direction": "빼기 방향",
|
||||
"ui.tool_direction": "공구 방향",
|
||||
"ui.reachable": "도달 가능",
|
||||
"ui.blocked": "막힘",
|
||||
"ui.radius_type": "반지름 유형",
|
||||
"ui.rectangle": "직사각형",
|
||||
"ui.rectangular_pattern": "직사각형 패턴",
|
||||
|
||||
@@ -590,6 +590,10 @@
|
||||
"ui.quit": "退出",
|
||||
"ui.radial_context_menu": "环形上下文菜单",
|
||||
"ui.radius": "半径",
|
||||
"ui.pull_direction": "拔模方向",
|
||||
"ui.tool_direction": "刀具方向",
|
||||
"ui.reachable": "可到达",
|
||||
"ui.blocked": "被遮挡",
|
||||
"ui.radius_type": "半径类型",
|
||||
"ui.rectangle": "矩形",
|
||||
"ui.rectangular_pattern": "矩形阵列",
|
||||
|
||||
@@ -1371,6 +1371,76 @@ fn analysis_uniform(app: &SolveApp) -> [f32; 8] {
|
||||
}
|
||||
}
|
||||
|
||||
/// While Draft, Accessibility or Curvature Map analysis is on, what each of its colours means and
|
||||
/// the values it stands for, at the bottom left of the viewport (#973). The colours alone don't
|
||||
/// say, and with colour vision loss they can be hard to tell apart.
|
||||
fn analysis_legend(app: &SolveApp, painter: &egui::Painter, rect: Rect) {
|
||||
use solidcraft_engine::SurfaceAnalysis as A;
|
||||
let t = Tokens::get();
|
||||
let tr = crate::i18n::tr_label;
|
||||
let col = |c: [f32; 3]| {
|
||||
let u = |x: f32| (x.clamp(0.0, 1.0) * 255.0).round() as u8;
|
||||
Color32::from_rgb(u(c[0]), u(c[1]), u(c[2]))
|
||||
};
|
||||
let dir = |v: Vec3| format!("({:.2}, {:.2}, {:.2})", v.x, v.y, v.z);
|
||||
let a = &t.analysis;
|
||||
// (title, the direction it measures along, rows of colour, text, hatched).
|
||||
let (title, along, rows): (&str, Option<String>, Vec<(Color32, String, bool)>) = match app.session.analysis {
|
||||
Some(A::Draft { pull, angle }) => (
|
||||
tr("Draft Analysis"),
|
||||
Some(format!("{} {}", tr("Pull direction"), dir(pull))),
|
||||
vec![
|
||||
(col(a.draft[0]), format!("≥ {angle:.1}°"), false),
|
||||
(col(a.draft[1]), format!("−{angle:.1}° … {angle:.1}°"), false),
|
||||
(col(a.draft[2]), format!("≤ −{angle:.1}°"), a.hatch),
|
||||
],
|
||||
),
|
||||
Some(A::Access { dir: d }) => (
|
||||
tr("Accessibility Analysis"),
|
||||
Some(format!("{} {}", tr("Tool direction"), dir(d))),
|
||||
vec![(col(a.access[0]), tr("Reachable").to_string(), false), (col(a.access[1]), tr("Blocked").to_string(), a.hatch)],
|
||||
),
|
||||
// The shader's scale reaches its middle at twice the analysis radius.
|
||||
Some(A::Curvature { radius }) => (
|
||||
tr("Curvature Map Analysis"),
|
||||
None,
|
||||
vec![
|
||||
(col(a.curvature[0]), tr("Flat").to_string(), false),
|
||||
(col(a.curvature[1]), format!("{} {}", tr("Radius"), crate::prefs::show_mm(app, 2.0 * radius, 1)), false),
|
||||
(col(a.curvature[2]), format!("{} ≤ {}", tr("Radius"), crate::prefs::show_mm(app, radius, 1)), false),
|
||||
],
|
||||
),
|
||||
_ => return,
|
||||
};
|
||||
let (pad, row) = (8.0, 18.0);
|
||||
let h = pad * 2.0 + 18.0 + along.as_ref().map_or(0.0, |_| row) + row * rows.len() as f32;
|
||||
let r = Rect::from_min_size(pos2(rect.left() + 12.0, rect.bottom() - 12.0 - h), vec2(230.0, h));
|
||||
painter.rect(r, 6.0, t.overlay, Stroke::new(1.0, t.border), egui::StrokeKind::Inside);
|
||||
crate::scenario::publish_handle("analysis:legend", r.center());
|
||||
let mut y = r.top() + pad;
|
||||
painter.text(pos2(r.left() + pad, y), Align2::LEFT_TOP, title, FontId::proportional(13.0), t.text);
|
||||
y += 18.0;
|
||||
if let Some(along) = along {
|
||||
painter.text(pos2(r.left() + pad, y), Align2::LEFT_TOP, along, FontId::proportional(11.5), t.text_dim);
|
||||
y += row;
|
||||
}
|
||||
for (c, text, hatched) in rows {
|
||||
let sw = Rect::from_min_size(pos2(r.left() + pad, y + 2.0), vec2(12.0, 12.0));
|
||||
painter.rect_filled(sw, 2.0, c);
|
||||
if hatched {
|
||||
// The stripes the viewport adds to these faces (colour vision modes).
|
||||
for k in [3.0, 7.0, 11.0] {
|
||||
painter.line_segment(
|
||||
[pos2(sw.left() + k - 3.0, sw.bottom()), pos2(sw.left() + k + 1.0, sw.top())],
|
||||
Stroke::new(1.2, Color32::from_black_alpha(110)),
|
||||
);
|
||||
}
|
||||
}
|
||||
painter.text(pos2(sw.right() + 8.0, y), Align2::LEFT_TOP, text, FontId::proportional(12.0), t.text);
|
||||
y += row;
|
||||
}
|
||||
}
|
||||
|
||||
fn section_clip(app: &SolveApp) -> [f32; 4] {
|
||||
match section_plane(app) {
|
||||
Some((o, n)) => [n.x as f32, n.y as f32, n.z as f32, n.dot(o) as f32],
|
||||
@@ -2382,6 +2452,7 @@ pub fn show(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
} else {
|
||||
cpu_render(app, ui.ctx(), &painter, rect, &proj);
|
||||
}
|
||||
analysis_legend(app, &painter, rect);
|
||||
|
||||
// ---- interaction ----
|
||||
let add = mods.shift || mods.command || mods.ctrl;
|
||||
@@ -4602,6 +4673,28 @@ mod tests {
|
||||
assert_eq!(d.items().len(), 1, "the other half toggles the same wall");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn colour_analyses_show_a_legend() {
|
||||
// #973: Draft, Accessibility and Curvature Map say what their colours mean; Zebra and no
|
||||
// analysis draw no legend.
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.app.home.open = false;
|
||||
h.app.run("solid.box", json!({"length":20,"width":20,"height":20})).unwrap();
|
||||
let legend = |h: &mut crate::scenario::Harness| {
|
||||
h.frames(2);
|
||||
crate::scenario::handle_at("analysis:legend").is_some()
|
||||
};
|
||||
assert!(!legend(&mut h));
|
||||
for (cmd, shown) in [("inspect.draft", true), ("inspect.accessibility", true), ("inspect.curvature_map", true), ("inspect.zebra", false)] {
|
||||
h.app.session.analysis = None;
|
||||
h.app.run(cmd, json!({})).unwrap();
|
||||
assert!(h.app.session.analysis.is_some(), "{cmd}");
|
||||
assert_eq!(legend(&mut h), shown, "{cmd}");
|
||||
}
|
||||
h.app.session.analysis = None;
|
||||
assert!(!legend(&mut h));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_section_leaves_the_origin_planes_and_axes_whole() {
|
||||
// #1229: the origin planes and axes are reference geometry; the section plane cuts the
|
||||
|
||||
Reference in new issue
Block a user