feat: corner reliefs at sheet flange corners, chosen by the rule (#488)
Sheet Metal Rules get a Corner Relief Shape (notch, round or rectangular), sized by the rule's corner relief, cut where two bends meet at a corner of the base. Notch, the default, keeps existing designs as they were. File format 16. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
c2a9c71436
commit
d6af554c6d
11 files changed
+830
-15
No files matched your search
@@ -32,6 +32,9 @@ const RULE_FIELDS: [(&str, &str); 8] = [
|
||||
("hem_gap", "Hem Gap"),
|
||||
("gap", "Flange Gap"),
|
||||
];
|
||||
/// The corner relief shapes the manager offers (the rule's `corner_relief_shape`) and their labels.
|
||||
const RELIEF_SHAPES: [&str; 3] = ["notch", "round", "rectangular"];
|
||||
const RELIEF_SHAPE_LABELS: [&str; 3] = ["Notch", "Round", "Rectangular"];
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Sm {
|
||||
@@ -73,6 +76,8 @@ pub enum Sm {
|
||||
pick: usize,
|
||||
name: String,
|
||||
values: Vec<String>,
|
||||
/// The corner relief shape (index into `RELIEF_SHAPES`).
|
||||
relief: usize,
|
||||
active: bool,
|
||||
loaded: Option<usize>,
|
||||
},
|
||||
@@ -148,7 +153,7 @@ pub fn start(app: &SolveApp, id: &str) -> Option<(Kind, Vec<SelInput>)> {
|
||||
let active = app.session.doc.sheet.active.clone();
|
||||
let rules = rule_list(app);
|
||||
let pick = active.and_then(|a| rules.iter().position(|r| r.name == a)).unwrap_or(0);
|
||||
(Sm::Rules { pick, name: String::new(), values: Vec::new(), active: true, loaded: None }, vec![])
|
||||
(Sm::Rules { pick, name: String::new(), values: Vec::new(), relief: 0, active: true, loaded: None }, vec![])
|
||||
}
|
||||
"sheet.flat_pattern" => (Sm::FlatPattern { data: None, key: None, status: None }, vec![SelInput::new("Body (last if none)", BODIES, false)]),
|
||||
_ => return None,
|
||||
@@ -291,7 +296,7 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Sm, inputs: &mut [Sel
|
||||
}
|
||||
Sm::Unfold { .. } => {}
|
||||
Sm::Convert { rule } => rule_combo(app, ui, "sm_conv_rule", rule),
|
||||
Sm::Rules { pick, name, values, active, loaded } => {
|
||||
Sm::Rules { pick, name, values, relief, active, loaded } => {
|
||||
let rules = rule_list(app);
|
||||
let mut names: Vec<String> = rules.iter().map(|r| r.name.clone()).collect();
|
||||
names.push("New rule…".into());
|
||||
@@ -312,6 +317,7 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Sm, inputs: &mut [Sel
|
||||
base.hem_gap.clone(),
|
||||
base.gap.clone(),
|
||||
];
|
||||
*relief = RELIEF_SHAPES.iter().position(|s| s.eq_ignore_ascii_case(base.corner_relief_shape.trim())).unwrap_or(0);
|
||||
*active = app.session.doc.sheet_rule(None).name == base.name || *pick >= rules.len();
|
||||
if *pick >= rules.len() {
|
||||
*name = format!("Rule{}", rules.len() + 1);
|
||||
@@ -323,7 +329,13 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Sm, inputs: &mut [Sel
|
||||
ui.text_edit_singleline(name);
|
||||
ui.end_row();
|
||||
}
|
||||
for ((_, label), v) in RULE_FIELDS.iter().zip(values.iter_mut()) {
|
||||
for ((key, label), v) in RULE_FIELDS.iter().zip(values.iter_mut()) {
|
||||
if *key == "corner_relief" {
|
||||
// The shape goes with its size, the row below.
|
||||
row_label(ui, "Corner Relief Shape");
|
||||
combo(ui, "sm_relief_shape", &RELIEF_SHAPE_LABELS, relief);
|
||||
ui.end_row();
|
||||
}
|
||||
row_label(ui, label);
|
||||
enter |= field(ui, v);
|
||||
ui.end_row();
|
||||
@@ -581,7 +593,7 @@ pub fn commands(app: &SolveApp, k: &Sm, inputs: &[SelInput], extra: &Map<String,
|
||||
}
|
||||
("sheet.convert", p)
|
||||
}
|
||||
Sm::Rules { pick, name, values, active, .. } => {
|
||||
Sm::Rules { pick, name, values, relief, active, .. } => {
|
||||
let rules = rule_list(app);
|
||||
let n = match rules.get(*pick) {
|
||||
Some(r) => r.name.clone(),
|
||||
@@ -590,7 +602,7 @@ pub fn commands(app: &SolveApp, k: &Sm, inputs: &[SelInput], extra: &Map<String,
|
||||
if n.is_empty() {
|
||||
return Err("give the rule a name".into());
|
||||
}
|
||||
let mut p = json!({"name": n, "active": active});
|
||||
let mut p = json!({"name": n, "active": active, "corner_relief_shape": RELIEF_SHAPES.get(*relief).copied().unwrap_or("notch")});
|
||||
for ((k, _), v) in RULE_FIELDS.iter().zip(values) {
|
||||
if !v.trim().is_empty() {
|
||||
p[*k] = json!(v);
|
||||
|
||||
@@ -311,6 +311,47 @@ fn bend_table_picker_writes_the_current_body() {
|
||||
assert_eq!(app.session.undo.len(), undo);
|
||||
}
|
||||
|
||||
/// The rule manager's shape selector: picked in the dialog, the shape reaches the rule and the part
|
||||
/// made with it, and the dialog shows it the next time.
|
||||
#[test]
|
||||
fn rules_dialog_picks_the_corner_relief_shape() {
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.app
|
||||
.run(
|
||||
"sheet.manage_rules",
|
||||
json!({"name": "AL2", "thickness": "2 mm", "k_factor": 0.5, "bend_radius": "1 mm", "gap": "0.5 mm", "active": true}),
|
||||
)
|
||||
.unwrap();
|
||||
h.app.run("sketch.create", json!({"plane": "XY", "name": "Base"})).unwrap();
|
||||
h.app.run("sketch.rectangle.two_point", json!({"p0": [0, 0], "p1": [120, 90]})).unwrap();
|
||||
h.app.run("sketch.finish", json!({})).unwrap();
|
||||
h.app.run("sheet.flange", json!({"sketch": "Base"})).unwrap();
|
||||
h.app.run("sheet.flange", json!({"edges": [[60, 0, 2], [120, 45, 2], [60, 90, 2], [0, 45, 2]], "height": 20, "angle": "72 deg"})).unwrap();
|
||||
// The rounds in the base of the tray, and the shape the open dialog shows.
|
||||
let rounds = |app: &SolveApp| {
|
||||
let state = app.session.model.state();
|
||||
state.sheets.last().map(|s| s.base.outer.segs.iter().filter(|g| matches!(g, solidcraft_engine::geom::Seg2::Arc { .. })).count())
|
||||
};
|
||||
let shown = |app: &SolveApp| match app.dialog.as_ref().map(|d| &d.kind) {
|
||||
Some(Kind::Sheet(Sm::Rules { relief, .. })) => Some(*relief),
|
||||
_ => None,
|
||||
};
|
||||
assert_eq!(rounds(&h.app), Some(0));
|
||||
h.step(&json!({"start": "sheet.manage_rules"})).unwrap();
|
||||
h.frames(3);
|
||||
assert_eq!(shown(&h.app), Some(0), "a rule starts with the notch");
|
||||
h.step(&json!({"click": {"handle": "dialog:Corner Relief Shape"}})).unwrap();
|
||||
h.step(&json!({"click": {"handle": "dialog:Corner Relief Shape:Round"}})).unwrap();
|
||||
assert_eq!(shown(&h.app), Some(1));
|
||||
h.step(&json!({"widget": "OK"})).unwrap();
|
||||
assert!(h.app.dialog.is_none(), "{:?}", h.app.dialog);
|
||||
assert_eq!(h.app.session.doc.sheet_rule(None).corner_relief_shape, "round");
|
||||
assert_eq!(rounds(&h.app), Some(4), "one round at each corner of the tray");
|
||||
h.step(&json!({"start": "sheet.manage_rules"})).unwrap();
|
||||
h.frames(3);
|
||||
assert_eq!(shown(&h.app), Some(1), "the dialog shows the rule's shape");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pocket_depth_labels_follow_inches_without_changing_flat_data() {
|
||||
let mut app = plate();
|
||||
|
||||
Reference in new issue
Block a user