Continuity in words: surface dialogs and Continuity Analysis show no G codes

Patch, Boundary and Blend Surface list Position, Tangent, Curvature and Curvature change; each
order's code (G0 to G3) and what it means is in the row's hover text. The Continuity Analysis
legend, its seam markers and its status line use the same words (Crease for a G0 seam). The
segments now draw their labels translated. The new labels carry over each catalog's existing
translation with the code taken off, so no language loses its wording; a word another label
already translates keeps that translation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKMoxpM7Vuv8Rb6E3K2
This commit is contained in:
jyviko
2026-10-27 11:12:59 -04:00
committed by Irakli Kourtis
parent 1acbe2806f
commit 28e3b8a192
5 changed files with 276 additions and 26 deletions
@@ -0,0 +1,219 @@
{
"en": {
"ui.continuity_position": "Position",
"ui.continuity_tangent": "Tangent",
"ui.continuity_curvature": "Curvature",
"ui.continuity_curvature_change": "Curvature change",
"ui.continuity_crease": "Crease",
"ui.continuity_curvature_change_tip": "Curvature change (G3): the curvature also changes smoothly across the seam.",
"ui.continuity_curvature_tip": "Curvature-continuous (G2): reflections run through without a kink.",
"ui.continuity_position_tip": "Position (G0): the surfaces meet, with a crease where they do.",
"ui.continuity_tangent_tip": "Tangent (G1): no crease, though the curvature jumps across the seam."
},
"ar": {
"ui.continuity_position": "الموضع",
"ui.continuity_tangent": "مماس",
"ui.continuity_curvature": "الانحناء",
"ui.continuity_curvature_change": "تغيّر الانحناء",
"ui.continuity_crease": "تجعّد",
"ui.continuity_curvature_change_tip": "تغيّر الانحناء (G3): يتغير الانحناء أيضًا بسلاسة عبر خط الالتقاء.",
"ui.continuity_curvature_tip": "متصل الانحناء (G2): تمر الانعكاسات دون انكسار.",
"ui.continuity_position_tip": "الموضع (G0): تلتقي الأسطح، مع تجعّد عند التقائها.",
"ui.continuity_tangent_tip": "مماس (G1): لا تجعّد، لكن الانحناء يقفز عبر خط الالتقاء."
},
"cs": {
"ui.continuity_position": "Poloha",
"ui.continuity_tangent": "Tečnost",
"ui.continuity_curvature": "Křivost",
"ui.continuity_curvature_change": "Změna křivosti",
"ui.continuity_crease": "Hrana"
},
"da": {
"ui.continuity_position": "Positionskontinuitet",
"ui.continuity_tangent": "Tangentkontinuitet",
"ui.continuity_curvature": "Krumning",
"ui.continuity_curvature_change": "Ændring i krumning",
"ui.continuity_crease": "Knæk"
},
"de": {
"ui.continuity_position": "Position",
"ui.continuity_tangent": "Tangential",
"ui.continuity_curvature": "Krümmung",
"ui.continuity_curvature_change": "Krümmungsänderung",
"ui.continuity_crease": "Knick",
"ui.continuity_curvature_change_tip": "Krümmungsänderung (G3): Auch die Krümmung ändert sich an der Naht glatt.",
"ui.continuity_curvature_tip": "Krümmungsstetig (G2): Spiegelungen laufen ohne Knick hindurch.",
"ui.continuity_position_tip": "Position (G0): Die Flächen treffen sich, mit einem Knick an der Naht.",
"ui.continuity_tangent_tip": "Tangential (G1): kein Knick, aber die Krümmung springt an der Naht."
},
"es": {
"ui.continuity_position": "Posición",
"ui.continuity_tangent": "Tangente",
"ui.continuity_curvature": "Curvatura",
"ui.continuity_curvature_change": "Cambio de curvatura",
"ui.continuity_crease": "Arista"
},
"fr": {
"ui.continuity_position": "Position",
"ui.continuity_tangent": "Tangence",
"ui.continuity_curvature": "Courbure",
"ui.continuity_curvature_change": "Variation de courbure",
"ui.continuity_crease": "Angle vif"
},
"gl": {
"ui.continuity_position": "Posición",
"ui.continuity_tangent": "Tanxente",
"ui.continuity_curvature": "Curvatura",
"ui.continuity_curvature_change": "Cambio de curvatura",
"ui.continuity_crease": "Aresta"
},
"he": {
"ui.continuity_position": "מיקום",
"ui.continuity_tangent": "משיק",
"ui.continuity_curvature": "עקמומיות",
"ui.continuity_curvature_change": "שינוי עקמומיות",
"ui.continuity_crease": "שבר",
"ui.continuity_curvature_change_tip": "שינוי עקמומיות (G3): גם העקמומיות משתנה בצורה חלקה לאורך התפר.",
"ui.continuity_curvature_tip": "רציף בעקמומיות (G2): ההשתקפויות עוברות בלי שבר.",
"ui.continuity_position_tip": "מיקום (G0): המשטחים נפגשים, עם שבר במקום המפגש.",
"ui.continuity_tangent_tip": "משיק (G1): אין שבר, אך העקמומיות קופצת לאורך התפר."
},
"hu": {
"ui.continuity_position": "Pozíció",
"ui.continuity_tangent": "Érintő",
"ui.continuity_curvature": "Görbület",
"ui.continuity_curvature_change": "Görbületváltozás",
"ui.continuity_crease": "Törés"
},
"id": {
"ui.continuity_position": "Posisi",
"ui.continuity_tangent": "Tangen",
"ui.continuity_curvature": "Kelengkungan",
"ui.continuity_curvature_change": "Perubahan kelengkungan",
"ui.continuity_crease": "Lipatan"
},
"it": {
"ui.continuity_position": "Posizione",
"ui.continuity_tangent": "Tangente",
"ui.continuity_curvature": "Curvatura",
"ui.continuity_curvature_change": "Variazione di curvatura",
"ui.continuity_crease": "Spigolo vivo"
},
"ja": {
"ui.continuity_position": "位置",
"ui.continuity_tangent": "接線",
"ui.continuity_curvature": "曲率",
"ui.continuity_curvature_change": "曲率変化",
"ui.continuity_crease": "折れ",
"ui.continuity_curvature_change_tip": "曲率変化 (G3): 境目で曲率の変化もなめらかです。",
"ui.continuity_curvature_tip": "曲率連続 (G2): 映り込みが折れずに通ります。",
"ui.continuity_position_tip": "位置 (G0): 面はつながりますが、境目に折れがあります。",
"ui.continuity_tangent_tip": "接線 (G1): 折れはありませんが、境目で曲率が飛びます。"
},
"ko": {
"ui.continuity_position": "위치",
"ui.continuity_tangent": "접선",
"ui.continuity_curvature": "곡률",
"ui.continuity_curvature_change": "곡률 변화",
"ui.continuity_crease": "꺾임",
"ui.continuity_curvature_change_tip": "곡률 변화(G3): 이음매에서 곡률의 변화도 부드럽습니다.",
"ui.continuity_curvature_tip": "곡률 연속(G2): 반사가 꺾임 없이 이어집니다.",
"ui.continuity_position_tip": "위치(G0): 면이 만나지만, 만나는 곳에 꺾임이 있습니다.",
"ui.continuity_tangent_tip": "접선(G1): 꺾임은 없지만, 이음매에서 곡률이 급변합니다."
},
"ms": {
"ui.continuity_position": "Kedudukan",
"ui.continuity_tangent": "Tangen",
"ui.continuity_curvature": "Kelengkungan",
"ui.continuity_curvature_change": "Perubahan kelengkungan",
"ui.continuity_crease": "Lipatan"
},
"nb": {
"ui.continuity_position": "Posisjon",
"ui.continuity_tangent": "Tangent",
"ui.continuity_curvature": "Krumning",
"ui.continuity_curvature_change": "Krumningsendring",
"ui.continuity_crease": "Knekk"
},
"nl": {
"ui.continuity_position": "Positie",
"ui.continuity_tangent": "Tangentieel",
"ui.continuity_curvature": "Kromming",
"ui.continuity_curvature_change": "Krommingsverandering",
"ui.continuity_crease": "Knik"
},
"pl": {
"ui.continuity_position": "Położenie",
"ui.continuity_tangent": "Styczność",
"ui.continuity_curvature": "Krzywizna",
"ui.continuity_curvature_change": "Zmiana krzywizny",
"ui.continuity_crease": "Załamanie"
},
"pt-BR": {
"ui.continuity_position": "Posição",
"ui.continuity_tangent": "Tangente",
"ui.continuity_curvature": "Curvatura",
"ui.continuity_curvature_change": "Variação de curvatura",
"ui.continuity_crease": "Vinco"
},
"ro": {
"ui.continuity_position": "Poziție",
"ui.continuity_tangent": "Tangent",
"ui.continuity_curvature": "Curbură",
"ui.continuity_curvature_change": "Variația curburii",
"ui.continuity_crease": "Muchie"
},
"ru": {
"ui.continuity_position": "Положение",
"ui.continuity_tangent": "Касание",
"ui.continuity_curvature": "Кривизна",
"ui.continuity_curvature_change": "Изменение кривизны",
"ui.continuity_crease": "Излом"
},
"sv": {
"ui.continuity_position": "Läge",
"ui.continuity_tangent": "Tangent",
"ui.continuity_curvature": "Krökning",
"ui.continuity_curvature_change": "Krökningsändring",
"ui.continuity_crease": "Veck"
},
"tr": {
"ui.continuity_position": "Konum",
"ui.continuity_tangent": "Teğet",
"ui.continuity_curvature": "Eğrilik",
"ui.continuity_curvature_change": "Eğrilik değişimi",
"ui.continuity_crease": "Kırık"
},
"uk": {
"ui.continuity_position": "Положення",
"ui.continuity_tangent": "Дотична",
"ui.continuity_curvature": "Кривизна",
"ui.continuity_curvature_change": "Зміна кривини",
"ui.continuity_crease": "Злам"
},
"vi": {
"ui.continuity_position": "Vị trí liên tục",
"ui.continuity_tangent": "Tiếp tuyến",
"ui.continuity_curvature": "Độ cong",
"ui.continuity_curvature_change": "Thay đổi độ cong",
"ui.continuity_crease": "Nếp gập"
},
"zh": {
"ui.continuity_position": "位置",
"ui.continuity_tangent": "相切",
"ui.continuity_curvature": "曲率",
"ui.continuity_curvature_change": "曲率变化",
"ui.continuity_crease": "折痕",
"ui.continuity_curvature_change_tip": "曲率变化 (G3):曲率在接缝处的变化也是平滑的。",
"ui.continuity_curvature_tip": "曲率连续 (G2):反射平滑穿过,没有折痕。",
"ui.continuity_position_tip": "位置 (G0):曲面相接,但接缝处有折痕。",
"ui.continuity_tangent_tip": "相切 (G1):没有折痕,但曲率在接缝处突变。"
},
"zh-TW": {
"ui.continuity_position": "位置",
"ui.continuity_tangent": "相切",
"ui.continuity_curvature": "曲率",
"ui.continuity_curvature_change": "曲率變化",
"ui.continuity_crease": "摺痕"
}
}
+27 -14
View File
@@ -138,12 +138,29 @@ impl Surf {
}
/// How a patch on edges meets the faces beside its loop (labels, and the `continuity` parameter).
const PATCH_CONTINUITY: [(&str, &str); 3] = [("Position (G0)", "g0"), ("Tangent (G1)", "g1"), ("Curvature (G2)", "g2")];
/// Labels are plain words; the order's code (G0, G1, G2) is in each one's hover text.
const PATCH_CONTINUITY: [(&str, &str); 3] = [("ui.continuity_position", "g0"), ("ui.continuity_tangent", "g1"), ("ui.continuity_curvature", "g2")];
/// How much of the surface at its edges each end of a blend repeats (labels, and the
/// `continuity` words).
const BLEND_CONTINUITY: [(&str, &str); 4] =
[("Position (G0)", "g0"), ("Tangent (G1)", "g1"), ("Curvature (G2)", "g2"), ("Curvature change (G3)", "g3")];
[("ui.continuity_position", "g0"), ("ui.continuity_tangent", "g1"), ("ui.continuity_curvature", "g2"), ("ui.continuity_curvature_change", "g3")];
/// Each continuity's hover text, in the order of [`BLEND_CONTINUITY`]: where its code (G0 … G3)
/// is shown.
const CONTINUITY_TIPS: [&str; 4] =
["ui.continuity_position_tip", "ui.continuity_tangent_tip", "ui.continuity_curvature_tip", "ui.continuity_curvature_change_tip"];
/// A Continuity row's segments over `orders` (the segments draw their text as given).
fn continuity_segments(ui: &mut egui::Ui, selected: &mut usize, orders: &[(&str, &str)]) -> egui::Response {
let labels: Vec<&str> = orders.iter().map(|(label, _)| crate::i18n::tr(label)).collect();
ui.scope(|ui| ui_kit::segmented(ui, selected, &labels, Default::default())).response
}
/// The hover text of a list of `orders` continuities: a line for each.
fn continuity_tip(orders: usize) -> String {
CONTINUITY_TIPS.iter().take(orders).map(|tip| crate::i18n::tr(tip)).collect::<Vec<_>>().join("\n")
}
/// What a boundary surface becomes: a body of its own, or part of the body of its edges.
const BOUNDARY_OPS: [&str; 2] = ["New Body", "Join"];
@@ -275,31 +292,27 @@ pub fn rows(ui: &mut egui::Ui, k: &mut Surf) -> bool {
ui_kit::options(ui, "untrim_options", &mut [("Restore outer boundary", outer), ("In place (surfaces)", in_place)]);
}
Surf::Patch { continuity } => {
form_row(ui, "Continuity", |ui| {
ui_kit::segmented(ui, continuity, &PATCH_CONTINUITY.map(|(label, _)| label), Default::default());
});
form_row(ui, "Continuity", |ui| continuity_segments(ui, continuity, &PATCH_CONTINUITY))
.on_hover_text(continuity_tip(PATCH_CONTINUITY.len()));
}
Surf::Blend { continuity, bulge, stitch } => {
let [from, to] = continuity;
let [from_bulge, to_bulge] = bulge;
form_row(ui, "Start continuity", |ui| {
combo(ui, "bl_cont0", &BLEND_CONTINUITY.map(|(label, _)| label), from);
});
form_row(ui, "Start continuity", |ui| ui_kit::dropdown(ui, "bl_cont0", from, &BLEND_CONTINUITY.map(|(label, _)| label)))
.on_hover_text(continuity_tip(BLEND_CONTINUITY.len()));
form_row(ui, "Start bulge", |ui| {
ui_kit::scalar_field(ui, from_bulge, -0.9..=3.0);
});
form_row(ui, "End continuity", |ui| {
combo(ui, "bl_cont1", &BLEND_CONTINUITY.map(|(label, _)| label), to);
});
form_row(ui, "End continuity", |ui| ui_kit::dropdown(ui, "bl_cont1", to, &BLEND_CONTINUITY.map(|(label, _)| label)))
.on_hover_text(continuity_tip(BLEND_CONTINUITY.len()));
form_row(ui, "End bulge", |ui| {
ui_kit::scalar_field(ui, to_bulge, -0.9..=3.0);
});
ui_kit::options(ui, "blend_options", &mut [("Stitch to both bodies", stitch)]);
}
Surf::Boundary { continuity, join } => {
form_row(ui, "Continuity", |ui| {
ui_kit::segmented(ui, continuity, &PATCH_CONTINUITY.map(|(label, _)| label), Default::default());
});
form_row(ui, "Continuity", |ui| continuity_segments(ui, continuity, &PATCH_CONTINUITY))
.on_hover_text(continuity_tip(PATCH_CONTINUITY.len()));
form_row(ui, "Operation", |ui| {
let mut op = usize::from(*join);
combo(ui, "bo_op", &BOUNDARY_OPS, &mut op);
+8 -1
View File
@@ -2232,7 +2232,8 @@ fn patch_dialog_takes_continuity_and_points_and_brings_them_back() {
#[test]
fn patch_dialog_rows_are_translated() {
for (lang, continuity, through) in [("en", "Continuity", "Pass through"), ("de", "Stetigkeit", "Durch Punkte"), ("ja", "連続性", "通過点")]
for (lang, continuity, through, tangent) in
[("en", "Continuity", "Pass through", "Tangent"), ("de", "Stetigkeit", "Durch Punkte", "Tangential"), ("ja", "連続性", "通過点", "接線")]
{
let mut h = crate::scenario::Harness::new();
h.app.preferences.language = lang.into();
@@ -2241,6 +2242,12 @@ fn patch_dialog_rows_are_translated() {
h.frames(3);
assert!(h.widget(continuity).is_some(), "{lang}: the Continuity row must show {continuity:?}");
assert!(h.widget(through).is_some(), "{lang}: the points input must show {through:?}");
// The continuity in the language too, in words (the code is in the hover text).
if let Some(crate::dialogs::Dialog { kind: Kind::Surface(crate::dialogs_surface::Surf::Patch { continuity }), .. }) = h.app.dialog.as_mut() {
*continuity = 1;
}
h.frames(2);
assert!(h.widget(tangent).is_some(), "{lang}: the Tangent segment must show {tangent:?}");
}
}
@@ -21,7 +21,7 @@ fn the_button_shades_every_seam_pins_an_edge_and_turns_off() {
assert_eq!(h.app.session.continuity.len(), 12, "every edge of the box");
assert_eq!(h.app.tool.as_ref().map(|t| t.cmd), Some("inspect.continuity"));
h.step(&json!({"click": {"world": [10, 0, 20]}})).unwrap();
assert_eq!(marks(), ["G0 90.000\u{b0}"], "{:?}", h.app.status);
assert_eq!(marks(), ["Crease 90.000\u{b0}"], "{:?}", h.app.status);
assert_eq!(h.app.session.continuity.len(), 12, "the clicked edge is measured again, not added twice");
h.step(&json!({"start": "inspect.continuity"})).unwrap();
assert!(h.app.session.continuity.is_empty() && h.app.tool.is_none());
@@ -29,7 +29,7 @@ fn the_button_shades_every_seam_pins_an_edge_and_turns_off() {
assert!(marks().is_empty(), "the markers go with the tool");
}
/// A round's tangent seam reads G1 with its curvature jump (1 / 4 mm against the flat side).
/// A round's tangent seam reads Tangent with its curvature jump (1 / 4 mm against the flat side).
#[test]
fn a_tangent_seam_is_labelled_with_its_curvature_jump() {
let mut h = crate::scenario::Harness::new();
@@ -41,7 +41,7 @@ fn a_tangent_seam_is_labelled_with_its_curvature_jump() {
h.step(&json!({"click": {"world": [16, 0, 10]}})).unwrap();
let labels = marks();
assert_eq!(labels.len(), 1, "{labels:?} {:?}", h.app.status);
assert!(labels[0].starts_with("G1 0.000\u{b0} \u{394}k 0.2500/mm"), "{labels:?}");
assert!(labels[0].starts_with("Tangent 0.000\u{b0} \u{394}k 0.2500/mm"), "{labels:?}");
}
/// The legend shows while seams are shaded and goes with the analysis.
+19 -8
View File
@@ -181,9 +181,9 @@ fn continuity_label(app: &SolveApp, seam: &Value) -> String {
let jump = seam["curvature_jump_per_mm"]["value"].as_f64().map(|k| format!("\u{394}k {k:.4}/mm")).unwrap_or_default();
match seam["verdict"].as_str().unwrap_or_default() {
"gap" => format!("Gap {}", seam["gap_mm"]["value"].as_f64().map(|g| crate::prefs::show_mm(app, g, 1)).unwrap_or_default()),
"sharp" => format!("G0 {}", deg("normal_angle_deg")),
"curvature_jump" => format!("G1 {} {jump}", deg("normal_angle_deg")),
"smooth" => format!("G2 {} {jump}", deg("normal_angle_deg")),
"sharp" => format!("{} {}", crate::i18n::tr("ui.continuity_crease"), deg("normal_angle_deg")),
"curvature_jump" => format!("{} {} {jump}", crate::i18n::tr("ui.continuity_tangent"), deg("normal_angle_deg")),
"smooth" => format!("{} {} {jump}", crate::i18n::tr("ui.continuity_curvature"), deg("normal_angle_deg")),
other => other.to_string(),
}
}
@@ -198,7 +198,12 @@ fn continuity_legend(app: &SolveApp, painter: &egui::Painter, proj: &Proj) {
let [smooth, jump, sharp, gap] = tk.continuity;
// Stable ids: this Gap is a hole between faces, not the sheet-metal gap (`ui.sheet_gap`).
let tr = crate::i18n::tr;
let rows = [(tr("ui.continuity_g2"), smooth), (tr("ui.continuity_g1"), jump), (tr("ui.continuity_g0"), sharp), (tr("ui.continuity_gap"), gap)];
let rows = [
(tr("ui.continuity_curvature"), smooth),
(tr("ui.continuity_tangent"), jump),
(tr("ui.continuity_crease"), sharp),
(tr("ui.continuity_gap"), gap),
];
let font = egui::FontId::proportional(tk.font_body);
let (row, pad, swatch) = (18.0, 8.0, 22.0);
let text_width = rows.iter().map(|(label, _)| painter.layout_no_wrap(label.to_string(), font.clone(), tk.text).size().x).fold(0.0, f32::max);
@@ -303,10 +308,16 @@ pub fn start_hook(app: &mut SolveApp, id: &str) -> bool {
}
let Ok(v) = app.run(id, json!({})) else { return false };
let summary = &v["summary"];
let counts: Vec<String> = [("smooth", "G2"), ("curvature_jump", "G1"), ("sharp", "G0"), ("gap", "gap")]
.iter()
.filter_map(|(key, class)| summary[*key].as_u64().filter(|n| *n > 0).map(|n| format!("{n} {class}")))
.collect();
// Each class in the legend's words.
let counts: Vec<String> = [
("smooth", "ui.continuity_curvature"),
("curvature_jump", "ui.continuity_tangent"),
("sharp", "ui.continuity_crease"),
("gap", "ui.continuity_gap"),
]
.iter()
.filter_map(|(key, class)| summary[*key].as_u64().filter(|n| *n > 0).map(|n| format!("{n} {}", crate::i18n::tr(class))))
.collect();
app.set_status(format!("Continuity: {} (click the button again to turn it off)", counts.join(", ")), false);
return true;
}