One tool window for sketch Fillet and the three sketch Chamfers (#789)

Sketch Fillet and the three sketch Chamfers share one tool window, with its own icons, labels and a scenario.
This commit is contained in:
William Dunn authored and GitHub committed 2026-10-13 14:32:13 -04:00
1 parent dbfb689f6c
commit bdcfe3ff1d
13 files changed
+567 -70

No files matched your search

+23 -57
View File
@@ -357,7 +357,6 @@ fn mode(id: &str) -> Option<Mode> {
"sketch.text" => Mode::Points(1),
"sketch.edit_text" => Mode::Entities(1),
"sketch.blend_curve" => Mode::Points(2),
"sketch.fillet" | "sketch.chamfer.equal_distance" | "sketch.chamfer.distance_angle" | "sketch.chamfer.two_distance" => Mode::Entities(1),
"sketch.constraint.curvature" | "sketch.circle.two_tangent" => Mode::Entities(2),
"sketch.circle.three_tangent" => Mode::Entities(3),
"sketch.constrainer" | "sketch.constraint.polygon" => Mode::Entities(0),
@@ -414,10 +413,6 @@ pub fn hint(id: &str) -> Option<String> {
"sketch.text" => "Text: click where the text starts",
"sketch.edit_text" => "Edit Text: click a letter interior or outline",
"sketch.blend_curve" => "Blend curve: click the ends of two curves",
"sketch.fillet" => "Fillet: click the corner of two lines",
"sketch.chamfer.equal_distance" | "sketch.chamfer.distance_angle" | "sketch.chamfer.two_distance" => {
"Chamfer: click the corner of two lines"
}
"sketch.constraint.curvature" => "Curvature: click two curves that share an end",
"sketch.circle.two_tangent" => "2-tangent circle: click two curves (the circle goes near the second click)",
"sketch.circle.three_tangent" => "3-tangent circle: click three curves",
@@ -464,9 +459,30 @@ fn sketch_entity(app: &SolveApp, hits: &[Hit]) -> Option<(String, bool)> {
})
}
/// The two editable curves that meet at sketch point `pi`, in curve order, which is the order
/// the corner commands resolve them in from a `point` parameter. None unless exactly two meet
/// there and each is one the command can work on: a line always, an arc only for a fillet.
pub(crate) fn corner_curves(sk: &solidcraft_engine::sketch::Sketch, pi: usize, arcs_ok: bool) -> Option<(usize, usize)> {
use solidcraft_engine::sketch::CurveKind;
let curves: Vec<_> = sk
.curves
.iter()
.enumerate()
.filter(|(ci, c)| !sk.is_linked_curve(*ci) && c.kind.ends().is_some_and(|(a, b)| a == pi || b == pi))
.take(3)
.collect();
if curves.iter().any(|(_, c)| !matches!(c.kind, CurveKind::Line { .. }) && !(arcs_ok && matches!(c.kind, CurveKind::Arc { .. }))) {
return None;
}
match curves[..] {
[(a, _), (b, _)] => Some((a, b)),
_ => None,
}
}
/// Corner tools accept endpoints shared by two editable curves. Pick among those endpoints
/// directly so the origin, a circle centre or an overlapping loose point cannot hide a corner.
pub(super) fn corner_point(app: &SolveApp, proj: &Proj, pos: Pos2, cmd: &str) -> Option<(Vec2, String)> {
pub(crate) fn corner_point(app: &SolveApp, proj: &Proj, pos: Pos2, arcs_ok: bool) -> Option<(Vec2, String)> {
let st = app.session.world_state();
let ss = st.sketch(active_sketch(app)?)?;
let sk = &ss.sketch;
@@ -478,21 +494,7 @@ pub(super) fn corner_point(app: &SolveApp, proj: &Proj, pos: Pos2, cmd: &str) ->
if distance >= 8.0 {
return None;
}
let curves: Vec<_> = sk
.curves
.iter()
.enumerate()
.filter(|(ci, c)| !sk.is_linked_curve(*ci) && c.kind.ends().is_some_and(|(a, b)| a == pi || b == pi))
.take(3)
.collect();
if curves.len() != 2
|| curves.iter().any(|(_, c)| {
!matches!(c.kind, solidcraft_engine::sketch::CurveKind::Line { .. })
&& (cmd != "sketch.fillet" || !matches!(c.kind, solidcraft_engine::sketch::CurveKind::Arc { .. }))
})
{
return None;
}
corner_curves(sk, pi, arcs_ok)?;
Some((distance, point.pos, point.id.clone()))
})
.min_by(|a, b| a.0.total_cmp(&b.0))
@@ -517,21 +519,6 @@ fn curve_len(app: &SolveApp, id: &str) -> f64 {
ss.sketch.curve_index(id).map(|c| ss.sketch.polyline(c).windows(2).map(|w| w[0].dist(w[1])).sum::<f64>()).unwrap_or(10.0)
}
/// Lines meeting at a sketch point.
fn corner_len(app: &SolveApp, point: &str) -> f64 {
let st = app.session.world_state();
let Some(ss) = active_sketch(app).and_then(|s| st.sketch(s)) else { return 10.0 };
let sk = &ss.sketch;
let Some(q) = sk.resolve_point(point) else { return 10.0 };
sk.curves
.iter()
.enumerate()
.filter(|(_, c)| c.kind.uses(q) && matches!(c.kind, solidcraft_engine::sketch::CurveKind::Line { .. }))
.map(|(i, _)| sk.polyline(i).windows(2).map(|w| w[0].dist(w[1])).sum::<f64>())
.fold(f64::INFINITY, f64::min)
.min(1e6)
}
fn round_nice(x: f64) -> f64 {
if !(x.is_finite() && x > 0.0) {
return 1.0;
@@ -700,12 +687,6 @@ pub fn on_click(app: &mut SolveApp, tool: &mut Tool, proj: &Proj, pos: Pos2) {
place_point(app, tool, p);
}
Mode::Entities(n) => {
if cmd == "sketch.fillet" || cmd.starts_with("sketch.chamfer.") {
if let Some((_, id)) = corner_point(app, proj, pos, cmd) {
run_corner(app, cmd, &id);
}
return;
}
if cmd == "sketch.edit_text" {
let selection = hits.first().and_then(|hit| match hit {
Hit::SketchCurve { sketch, id, .. } if Some(*sketch) == active_sketch(app) => {
@@ -863,21 +844,6 @@ fn run_points(app: &mut SolveApp, t: &Tool) {
let _ = app.run(t.cmd, params);
}
fn run_corner(app: &mut SolveApp, cmd: &str, point: &str) {
let size = round_nice(corner_len(app, point) * 0.2);
let params = match cmd {
"sketch.fillet" => json!({"point": point, "radius": size}),
"sketch.chamfer.distance_angle" => json!({"point": point, "distance": size, "angle": 45}),
"sketch.chamfer.two_distance" => json!({"point": point, "distance": size, "distance2": size}),
_ => json!({"point": point, "distance": size}),
};
if let Ok(v) = app.run(cmd, params)
&& let Some(p) = v["param"].as_str()
{
app.dialog = Some(crate::dialogs::Dialog::edit_param(&app.session, p));
}
}
fn run_entities(app: &mut SolveApp, t: &Tool) {
let k = &t.picks;
let params = match t.cmd {