Selection inside sketches: double-click a curve selects the chain joined to it end to end (sketch.chain; shared or coincident ends), dimensions and constraint glyphs are selectable like other entities (Sel::SketchConstraint: Ctrl/Shift toggle, window and crossing boxes take them, Delete removes them with the rest), boxes leave out the origin, and the selection filter has sketch curves, points, dimensions and constraints

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
bherbruckandClaude Opus 5.5 committed 2026-10-08 08:39:22 +00:00
1 parent f7703ca720
commit d2068e07a3
12 files changed
+195 -52

No files matched your search

+1 -1
View File
@@ -65,7 +65,7 @@ fn apply(s: &mut Session, p: &Value) -> Result<Value> {
let mut bodies: Vec<String> = Vec::new();
for x in &items {
match x {
Sel::SketchCurve { id } | Sel::SketchPoint { id } => entities.push(id.clone()),
Sel::SketchCurve { id } | Sel::SketchPoint { id } | Sel::SketchConstraint { id } => entities.push(id.clone()),
Sel::Feature { id } => {
if s.doc.feature(*id).is_none() {
return Err(bad(cmd, format!("no feature {id}")));
+12
View File
@@ -163,6 +163,10 @@ pub static COMMANDS: &[CommandSpec] = &[
CommandSpec::new("sketch.options", "Sketch Palette Options", sketch_options).icon("sketch").params(
"sketch?: id|name (default active); any of show_profile, show_points, show_dimensions, show_constraints, show_projected, grid, snap, slice, sketch_3d: bool. Returns them all",
),
CommandSpec::new("sketch.chain", "Chain", chain_cmd)
.enabled(in_sketch)
.noundo()
.params("curve: id: the curves joined to it end to end (what a double-click selects)"),
CommandSpec::new("sketch.solve", "Solve Sketch", solve_cmd)
.enabled(in_sketch)
.noundo()
@@ -1660,6 +1664,14 @@ fn sketch_options(s: &mut Session, p: &Value) -> Result<Value> {
Ok(json!({"options": v, "sketch": info}))
}
fn chain_cmd(s: &mut Session, p: &Value) -> Result<Value> {
let cmd = "sketch.chain";
let id = target_sketch(s, p, cmd)?;
let sk = s.doc.sketch(id)?;
let c = curve_ref(sk, p.get("curve"), cmd, "curve")?;
Ok(json!({"curves": ids_of(sk, &sk.chain(c))}))
}
fn solve_cmd(s: &mut Session, p: &Value) -> Result<Value> {
let id = target_sketch(s, p, "sketch.solve")?;
let st = s.model.state();
+38
View File
@@ -526,3 +526,41 @@ fn fully_constrained_feedback_is_per_entity() {
assert!(lines.iter().filter(|l| l.1 == crate::view::colors::SKETCH_FIXED).count() >= 4);
assert!(lines.iter().any(|l| l.1 == crate::view::colors::SKETCH));
}
// ---------------------------------------------------------------------------------------------
// Selection inside sketches
#[test]
fn chain_follows_shared_and_coincident_ends() {
let mut s = new_sketch();
run(&mut s, "ShapeRectangleTwoPoint", json!({"p0": [0, 0], "p1": [20, 10]}));
run(&mut s, "DrawPolyline", json!({"points": [[40, 0], [50, 0]]}));
run(&mut s, "DrawPolyline", json!({"points": [[50.5, 0], [60, 5]]}));
run(&mut s, "CircleCenterRadius", json!({"center": [80, 0], "radius": 3}));
let chain = |s: &mut Session, c: &str| -> Vec<String> {
let v = run(s, "sketch.chain", json!({"curve": c}));
v["curves"].as_array().map(|a| a.iter().filter_map(|x| x.as_str().map(str::to_string)).collect()).unwrap_or_default()
};
assert_eq!(chain(&mut s, "l3"), ["l3", "l1", "l2", "l4"]);
assert_eq!(chain(&mut s, "l5"), ["l5"]);
assert_eq!(chain(&mut s, "c1"), ["c1"]);
// Joined by a coincident constraint, the two lines are one chain.
run(&mut s, "ConstraintCoincident", json!({"a": "l5.end", "b": "l6.start"}));
assert_eq!(chain(&mut s, "l6"), ["l6", "l5"]);
assert!(s.execute("sketch.chain", &json!({"curve": "nope"})).is_err());
}
#[test]
fn constraints_and_dimensions_can_be_selected_and_deleted_together() {
let mut s = new_sketch();
run(&mut s, "DrawPolyline", json!({"points": [[0, 0], [30, 0]]}));
run(&mut s, "ConstraintHorizontalVertical", json!({"line": "l1"}));
run(&mut s, "SketchDimension", json!({"entities": ["l1"], "value": 30}));
let ids: Vec<String> = sketch(&s).constraints.iter().map(|c| c.id.clone()).collect();
assert_eq!(ids.len(), 2);
let items: Vec<Value> = ids.iter().map(|id| json!({"type": "sketch_constraint", "id": id})).collect();
run(&mut s, "select.set", json!({ "items": items }));
assert_eq!(s.selection.len(), 2);
run(&mut s, "selection.delete", json!({"items": items}));
assert!(sketch(&s).constraints.is_empty());
}
+4
View File
@@ -94,6 +94,10 @@ pub enum Sel {
SketchPoint {
id: String,
},
/// A constraint or dimension (by id) of the active sketch.
SketchConstraint {
id: String,
},
/// A closed profile of a sketch (by index).
Profile {
sketch: u64,