Curvature-continuous (G2) fillets for straight planar edges (#862)
Fillet gets a Curvature (G2) radius type (type: "curvature", alias "g2"): a quintic blend whose size is the equivalent circular setback, for convex and concave straight edges between planar faces with a perpendicular planar end face at each end. Unsupported cases are refused rather than falling back to a circular fillet, and a blend that would cross a loop in its end face is refused. Persisted as a fillet style under format 19; labels in en/de/ja/ko/zh. Co-authored-by: midasdf <midasdf@users.noreply.github.com>
This commit is contained in:
1 parent
3669d4b3d0
commit
6e84c03946
18 files changed
+323
-13
No files matched your search
@@ -689,7 +689,16 @@ impl Dialog {
|
||||
Kind::Extrude { to_offset, .. } if to_object_on(&self.inputs) => ("To Offset", ValueKind::Length, to_offset),
|
||||
Kind::Extrude { distance, .. } => ("Distance", ValueKind::Length, distance),
|
||||
Kind::Revolve { angle, .. } => ("Angle", ValueKind::Angle, angle),
|
||||
Kind::Fillet { radius, chamfer, .. } => (if *chamfer { "Distance" } else { "Radius" }, ValueKind::Length, radius),
|
||||
Kind::Fillet { radius, chamfer, .. } => {
|
||||
let label = if *chamfer {
|
||||
"Distance"
|
||||
} else if matches!(self.extra.get("type").and_then(Value::as_str), Some("curvature" | "g2")) {
|
||||
"Equivalent Radius"
|
||||
} else {
|
||||
"Radius"
|
||||
};
|
||||
(label, ValueKind::Length, radius)
|
||||
}
|
||||
Kind::Shell { thickness } => ("Thickness", ValueKind::Length, thickness),
|
||||
Kind::Draft { angle } => ("Angle", ValueKind::Angle, angle),
|
||||
Kind::Hole { diameter, .. } => ("Diameter", ValueKind::Length, diameter),
|
||||
@@ -2059,7 +2068,13 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
&mut d.extra,
|
||||
"fi_type",
|
||||
"type",
|
||||
&[("Constant", "constant"), ("Chord Length", "chord"), ("Variable", "variable"), ("Spline", "spline")],
|
||||
&[
|
||||
("Constant", "constant"),
|
||||
("Chord Length", "chord"),
|
||||
("Variable", "variable"),
|
||||
("Spline", "spline"),
|
||||
("Curvature (G2)", "curvature"),
|
||||
],
|
||||
);
|
||||
ui.end_row();
|
||||
}
|
||||
@@ -2069,6 +2084,7 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
match rtype {
|
||||
1 => "Chord Length",
|
||||
2 | 3 => "Start Radius",
|
||||
4 => "Equivalent Radius",
|
||||
_ => "Radius",
|
||||
}
|
||||
} else if *ctype == 0 {
|
||||
@@ -4027,6 +4043,9 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
|
||||
if let FeatureKind::Fillet { style, .. } = &f.kind {
|
||||
match style {
|
||||
FilletStyle::Constant => {}
|
||||
FilletStyle::Curvature => {
|
||||
d.extra.insert("type".into(), json!("curvature"));
|
||||
}
|
||||
FilletStyle::Chord => {
|
||||
d.extra.insert("type".into(), json!("chord"));
|
||||
}
|
||||
@@ -4043,7 +4062,7 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
|
||||
d.extra.insert("knots".into(), json!(knots));
|
||||
d.extra.insert("radii".into(), json!(radii));
|
||||
}
|
||||
FilletStyle::Constant | FilletStyle::Chord => {}
|
||||
FilletStyle::Constant | FilletStyle::Chord | FilletStyle::Curvature => {}
|
||||
}
|
||||
// The start was the first edge's last point: ends swapped.
|
||||
let first_end = edges.first().and_then(|p| edge_sel(s, *p)).and_then(|x| match x {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
//! type, whether the command starts from a shortcut, the toolbar or a menu.
|
||||
|
||||
use serde_json::json;
|
||||
use solidcraft_engine::doc::expr::Kind as ValueKind;
|
||||
use solidcraft_engine::geom::Vec3;
|
||||
use solidcraft_engine::{Sel, Session};
|
||||
|
||||
@@ -561,6 +562,34 @@ fn a_hole_is_mirrored_as_a_feature() {
|
||||
assert_eq!(cmds[0].1["features"], json!(["Hole1"]));
|
||||
}
|
||||
|
||||
/// The canvas names the G2 size correctly and edits the same value when the feature is reopened.
|
||||
#[test]
|
||||
fn curvature_canvas_value_uses_equivalent_radius_when_created_and_reopened() {
|
||||
for style in ["curvature", "g2"] {
|
||||
let mut app = boxed();
|
||||
let e = edge(&app, Vec3::new(20.0, 0.0, 20.0));
|
||||
let mut d = start(&mut app, vec![e], "solid.fillet");
|
||||
d.extra.insert("type".into(), json!(style));
|
||||
let (label, kind, value) = d.primary().unwrap();
|
||||
assert_eq!((label, kind), ("Equivalent Radius", ValueKind::Length));
|
||||
*value = "3 mm".into();
|
||||
let (command, params) = apply_commands(&app, &d).unwrap().remove(0);
|
||||
assert_eq!(params["radius"], json!("3 mm"), "the canvas edits the actual command value");
|
||||
app.session.execute(&command, ¶ms).unwrap();
|
||||
let feature = app.session.doc.features.last().unwrap().id;
|
||||
let mut reopened = crate::dialogs::for_feature(&app, feature, None).unwrap();
|
||||
let (label, kind, value) = reopened.primary().unwrap();
|
||||
assert_eq!((label, kind), ("Equivalent Radius", ValueKind::Length));
|
||||
assert_eq!(value.as_str(), "3 mm");
|
||||
}
|
||||
for (command, label) in [("solid.fillet", "Radius"), ("solid.chamfer", "Distance")] {
|
||||
let mut app = boxed();
|
||||
let e = edge(&app, Vec3::new(20.0, 0.0, 20.0));
|
||||
let mut d = start(&mut app, vec![e], command);
|
||||
assert_eq!(d.primary().unwrap().0, label);
|
||||
}
|
||||
}
|
||||
|
||||
/// The radius arrow points into the body: dragging toward the material grows the round.
|
||||
#[test]
|
||||
fn fillet_and_chamfer_arrows_point_into_the_body() {
|
||||
|
||||
Reference in new issue
Block a user