mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-06 20:02:16 +00:00
* claim: #2987 * Flange on a plain solid says how to start sheet metal (#2987) Flange, Flat Pattern, and Export DXF on a design with no sheet metal body now say how to make one: Flange > Base, or Convert to Sheet Metal on a plate's large flat face. An edge flange checks for a sheet metal body before it adds a feature. The message is in the catalogs, and engine messages that match a catalog entry are shown in the interface language. * Dialogs show a refusal without the engine's parameter prefix (#2987) The Flange dialog showed "invalid parameters for `sheet.flange`: " in front of the new guidance. The dialog already names its command, so the red refusal text leaves that prefix out and follows the interface language when the message is in the catalogs. Create Issue still gets the full text.
This commit is contained in:
@@ -1366,6 +1366,7 @@
|
||||
"settings.reset_intro": "These settings don't work on this computer and would keep SolidCraft from starting, so they were changed:",
|
||||
"settings.reset_title": "Settings were changed",
|
||||
"settings.supersampling_note": "Draws the view at a higher resolution and filters it down: smoother edges, lines and grid on screens below 4K. It costs GPU time: about 2.25× the pixels at 1.5× and 4× at 2×.",
|
||||
"sheet.no_body": "there is no sheet metal body: start one with Flange > Base, or turn a plate into sheet metal with Convert to Sheet Metal (pick its large flat face)",
|
||||
"shortcut.json_error": "parameters must be JSON: {error}",
|
||||
"shortcut.no_command": "no command `{id}`; the text before the JSON must be a command id",
|
||||
"shortcut.pin": "Pin to the top of the S box",
|
||||
|
||||
@@ -1366,6 +1366,7 @@
|
||||
"settings.reset_intro": "次の設定はこのコンピューターでは使用できず、SolidCraft が起動できなくなるため変更されました:",
|
||||
"settings.reset_title": "設定が変更されました",
|
||||
"settings.supersampling_note": "ビューを高い解像度で描画してから縮小します。4K 未満の画面でエッジ、線、グリッドが滑らかになります。GPU の負荷が増えます(1.5× で約 2.25 倍、2× で 4 倍のピクセル)。",
|
||||
"sheet.no_body": "板金ボディがありません。フランジ > Base で作成するか、板金に変換でプレートを板金に変換してください(広い平面を選択)。",
|
||||
"shortcut.json_error": "パラメータはJSON形式で指定してください: {error}",
|
||||
"shortcut.no_command": "コマンド`{id}`がありません。JSONの前のテキストはコマンドIDである必要があります",
|
||||
"shortcut.pin": "Sボックスの先頭にピン留め",
|
||||
|
||||
@@ -1366,6 +1366,7 @@
|
||||
"settings.reset_intro": "这些设置在此计算机上无法正常工作,会导致 SolidCraft 无法启动,因此已更改:",
|
||||
"settings.reset_title": "设置已更改",
|
||||
"settings.supersampling_note": "以更高分辨率绘制视图再缩小:在低于 4K 的屏幕上边、线和网格更平滑。会消耗 GPU 时间:1.5× 时约 2.25 倍像素,2× 时 4 倍。",
|
||||
"sheet.no_body": "没有钣金实体:请使用法兰 > Base 创建,或使用转换为钣金将板材转换为钣金(选择其较大的平面)。",
|
||||
"shortcut.json_error": "参数必须采用 JSON 格式:{error}",
|
||||
"shortcut.no_command": "没有命令 `{id}`;JSON 前的文本必须是命令 ID",
|
||||
"shortcut.pin": "固定到 S 框顶部",
|
||||
|
||||
@@ -206,6 +206,9 @@ fn flange(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
if !matches!(position.as_str(), "inside" | "outside" | "middle") {
|
||||
return Err(bad(cmd, "`position` must be inside, outside or middle"));
|
||||
}
|
||||
if s.model.state().sheets.is_empty() {
|
||||
return Err(no_sheet_body(cmd));
|
||||
}
|
||||
let body = str_(p, "body").map(str::to_string);
|
||||
// The corner relief these flanges make, in place of the rule's; null is not given.
|
||||
let corner_relief_shape = match p.get("corner_relief_shape").filter(|v| !v.is_null()) {
|
||||
@@ -451,11 +454,18 @@ fn list_rules(s: &mut Session, _p: &Value) -> Result<Value> {
|
||||
Ok(json!({"rules": rules, "active": active}))
|
||||
}
|
||||
|
||||
fn no_sheet_body(cmd: &str) -> EngineError {
|
||||
bad(
|
||||
cmd,
|
||||
"there is no sheet metal body: start one with Flange > Base, or turn a plate into sheet metal with Convert to Sheet Metal (pick its large flat face)",
|
||||
)
|
||||
}
|
||||
|
||||
fn sheet_of(s: &Session, p: &Value, cmd: &str) -> Result<SheetBody> {
|
||||
let st = s.model.state();
|
||||
match str_(p, "body") {
|
||||
Some(b) => st.sheets.iter().find(|x| x.body == b).cloned().ok_or_else(|| bad(cmd, format!("`{b}` is not a sheet metal body"))),
|
||||
None => st.sheets.last().cloned().ok_or_else(|| bad(cmd, "there is no sheet metal body")),
|
||||
None => st.sheets.last().cloned().ok_or_else(|| no_sheet_body(cmd)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -816,7 +826,7 @@ fn export_dxf(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
}
|
||||
let sheets = &s.model.state().sheets;
|
||||
if sheets.is_empty() {
|
||||
return Err(bad(cmd, "there is no sheet metal body"));
|
||||
return Err(no_sheet_body(cmd));
|
||||
}
|
||||
// Every file is made before the first is written.
|
||||
let texts = sheets
|
||||
|
||||
@@ -37,6 +37,27 @@ fn rectangle_tool(s: &mut Session, name: &str, p0: [f64; 2], p1: [f64; 2]) {
|
||||
run(s, "sketch.finish", json!({}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_solid_sheet_operations_explain_how_to_start_sheet_metal() {
|
||||
let mut s = Session::default();
|
||||
run(&mut s, "solid.box", json!({"length": 100, "width": 60, "height": 2.5}));
|
||||
let before = s.doc.to_json();
|
||||
for (command, params) in [
|
||||
("sheet.flange", json!({"type": "edge", "edges": [[50, 0, 2.5]], "height": 15})),
|
||||
("sheet.flat_pattern", json!({})),
|
||||
("sheet.export_dxf", json!({"per_body": true})),
|
||||
] {
|
||||
let error = s.execute(command, ¶ms).unwrap_err();
|
||||
let crate::EngineError::BadParams { cmd, msg } = error else { panic!("{error}") };
|
||||
assert_eq!(cmd, command);
|
||||
assert_eq!(
|
||||
msg,
|
||||
"there is no sheet metal body: start one with Flange > Base, or turn a plate into sheet metal with Convert to Sheet Metal (pick its large flat face)"
|
||||
);
|
||||
assert_eq!(s.doc.to_json(), before);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn side_edge_flanges_across_a_bend_keep_their_relief_through_rebuild_and_reopen() {
|
||||
use solidcraft_geom::Vec3;
|
||||
|
||||
@@ -2078,7 +2078,7 @@ fn error_rows(app: &mut SolveApp, ctx: &egui::Context, d: &mut Dialog, ui: &mut
|
||||
return;
|
||||
}
|
||||
ui.vertical(|ui| {
|
||||
crate::ui_kit::text(ui, &error, crate::ui_kit::Text::Report("error", true));
|
||||
crate::ui_kit::text(ui, &shown_error(&error), crate::ui_kit::Text::Report("error", true));
|
||||
let response = crate::ui_kit::button(ui, "Create Issue…", crate::ui_kit::Button::Normal);
|
||||
crate::scenario::publish_handle("dialog:create_issue", response.rect.center());
|
||||
if response.clicked() {
|
||||
@@ -2088,6 +2088,14 @@ fn error_rows(app: &mut SolveApp, ctx: &egui::Context, d: &mut Dialog, ui: &mut
|
||||
}
|
||||
}
|
||||
|
||||
/// A refusal as the dialog shows it: without the engine's "invalid parameters for `id`: "
|
||||
/// prefix (the dialog already names the command), and in the interface language when the
|
||||
/// message is in the catalogs.
|
||||
fn shown_error(error: &str) -> String {
|
||||
let msg = error.strip_prefix("invalid parameters for `").and_then(|rest| rest.split_once("`: ")).map_or(error, |(_, msg)| msg);
|
||||
crate::i18n::tr_label(msg).to_string()
|
||||
}
|
||||
|
||||
/// Find the Boundary Fill cells again when the tool bodies changed (keeping the choices of
|
||||
/// cells that are still there).
|
||||
fn update_fill_cells(app: &SolveApp, d: &mut Dialog) {
|
||||
@@ -4944,6 +4952,15 @@ mod refused_ok_tests {
|
||||
assert_eq!(h.app.dialog.as_ref().and_then(|d| d.error.as_deref()), Some("hole depth must be positive"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_refusal_is_shown_without_the_engine_prefix() {
|
||||
assert_eq!(
|
||||
super::shown_error("invalid parameters for `sheet.flange`: there is no sheet metal body: start one with Flange > Base"),
|
||||
"there is no sheet metal body: start one with Flange > Base"
|
||||
);
|
||||
assert_eq!(super::shown_error("hole depth must be positive"), "hole depth must be positive");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_error_stays_while_the_inputs_do() {
|
||||
let mut h = hole_dialog();
|
||||
|
||||
@@ -500,6 +500,17 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_sheet_metal_guidance_uses_the_message_catalog() {
|
||||
let _scope = scope("ja");
|
||||
let message = "there is no sheet metal body: start one with Flange > Base, or turn a plate into sheet metal with Convert to Sheet Metal (pick its large flat face)";
|
||||
let error = format!("invalid parameters for `sheet.flange`: {message}");
|
||||
let translated = tr("sheet.no_body");
|
||||
assert_ne!(translated, "sheet.no_body");
|
||||
assert_ne!(translated, message);
|
||||
assert_eq!(crate::friendly_error("sheet.flange", &error), format!("{}: {translated}", tr_label("Flange")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keys_and_engine_labels_translate_with_english_fallback() {
|
||||
let ja = Locale::for_language("ja");
|
||||
|
||||
@@ -1908,12 +1908,13 @@ pub fn command_label(id: &str) -> &str {
|
||||
solidcraft_engine::find_command(id).map_or(id, |c| c.label)
|
||||
}
|
||||
|
||||
/// An engine error with the command's label instead of its id: "Shell: offset must be …". The
|
||||
/// label follows the interface language; the engine's sentence stays as the engine wrote it.
|
||||
/// An engine error with the command's label instead of its id: "Shell: offset must be …".
|
||||
/// The command label and catalogued engine messages follow the interface language.
|
||||
pub fn friendly_error(id: &str, e: &str) -> String {
|
||||
let label = i18n::tr_label(command_label(id));
|
||||
let msg = e.replace(&format!("invalid parameters for `{id}`: "), "").replace(&format!("{id}: "), "").replace(&format!("`{id}`"), label);
|
||||
if msg.starts_with(label) { msg } else { format!("{label}: {msg}") }
|
||||
let msg = i18n::tr_label(&msg);
|
||||
if msg.starts_with(label) { msg.to_string() } else { format!("{label}: {msg}") }
|
||||
}
|
||||
|
||||
pub fn now_ms() -> f64 {
|
||||
|
||||
Reference in New Issue
Block a user