mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-31 05:07:51 -06:00
Live Paint: the Live Paint tools and livePaint.fill skip hidden faces
A face hidden on its own (for example with Object > Hide after Live Paint Selection picks it) while its Live Paint group stays visible was still highlighted, selected by Live Paint Selection and filled by the Live Paint Bucket, and it kept the face of a group behind it from being picked. live_paint_at and lp_part_at now take the front-most face that is not hidden (visible_face_at). A point on a hidden face falls through to the visible face of a group behind it; where there is none, an edge pick takes the nearest visible edge as it does outside the faces. livePaint.fill also skips hidden faces. With group, a point on a hidden face is an error. With ids, it fills the face there of another Live Paint group among the objects, or else fails and does not merge the group. Docs: docs/mcp.md.
This commit is contained in:
@@ -102,7 +102,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Live Paint Bucket",
|
||||
[],
|
||||
None,
|
||||
"{group?: id | ids?|id?: [id…] (paths to make live first, as livePaint.make does), point: [x,y], color?|swatch?|gradient?|none? (default: current fill), tint?: 0..100, keepModel?: bool (both as paint.setFill)} fill the face containing point → {group, face}",
|
||||
"{group?: id | ids?|id?: [id…] (paths to make live first, as livePaint.make does), point: [x,y], color?|swatch?|gradient?|none? (default: current fill), tint?: 0..100, keepModel?: bool (both as paint.setFill)} fill the face containing point, skipping hidden faces → {group, face}",
|
||||
has_doc,
|
||||
lp_fill
|
||||
),
|
||||
@@ -764,13 +764,24 @@ fn lp_fill(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
}
|
||||
g
|
||||
}
|
||||
None => match roots.iter().copied().find(|r| d.node(*r).is_some_and(|n| is_live_paint(n) && face_at(n, pt).is_some())) {
|
||||
Some(g) => g,
|
||||
None => make_live(d, sel, &roots)?.0,
|
||||
},
|
||||
None => {
|
||||
let live_at = |face: fn(&Node, Point) -> Option<&Arc<Node>>| {
|
||||
roots.iter().copied().find(|r| d.node(*r).is_some_and(|n| is_live_paint(n) && face(n, pt).is_some()))
|
||||
};
|
||||
match (live_at(b::visible_face_at), live_at(face_at)) {
|
||||
(Some(g), _) => g,
|
||||
// At a point on a hidden face of a Live Paint group among the objects, the command returns an error
|
||||
// and does not merge the objects.
|
||||
(None, Some(_)) => return Err(EngineError::Other("no Live Paint face at that point".into())),
|
||||
(None, None) => make_live(d, sel, &roots)?.0,
|
||||
}
|
||||
}
|
||||
};
|
||||
let fid =
|
||||
d.node(gid).and_then(|n| face_at(n, pt)).map(|f| f.id).ok_or_else(|| EngineError::Other("no Live Paint face at that point".into()))?;
|
||||
let fid = d
|
||||
.node(gid)
|
||||
.and_then(|n| b::visible_face_at(n, pt))
|
||||
.map(|f| f.id)
|
||||
.ok_or_else(|| EngineError::Other("no Live Paint face at that point".into()))?;
|
||||
d.node_mut(fid).ok_or(EngineError::NoNode(fid))?.appearance.set_fill(paint.clone());
|
||||
Ok((gid, fid))
|
||||
})?;
|
||||
|
||||
@@ -689,6 +689,32 @@ fn live_paint_stroke_edge_skips_hidden_edges() {
|
||||
assert!(s.execute("livePaint.strokeEdge", &json!({"group": g.0, "point": [99, 50]})).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn live_paint_fill_skips_hidden_faces() {
|
||||
let mut s = session();
|
||||
let g = live(&mut s);
|
||||
// The face from x=50 to x=100 is hidden on its own; the group stays visible.
|
||||
let hidden = vectorcraft_tools::builder::face_at(&node(&s, g), vectorcraft_geom::Point::new(75.0, 50.0)).unwrap().id;
|
||||
s.execute("object.setProps", &json!({"ids": [hidden.0], "visible": false})).unwrap();
|
||||
let green = Paint::solid(Color::from_hex("#00ff00").unwrap());
|
||||
let children = node(&s, g).children().unwrap().len();
|
||||
// livePaint.fill at a point on the hidden face returns an error and fills no face, with `group`
|
||||
// and with the group in `ids`, and it does not rebuild the group.
|
||||
for p in [json!({"group": g.0, "point": [75, 50], "color": "#00ff00"}), json!({"ids": [g.0], "point": [75, 50], "color": "#00ff00"})] {
|
||||
assert!(s.execute("livePaint.fill", &p).is_err(), "{p}");
|
||||
let face = node(&s, hidden);
|
||||
assert!(!face.visible && face.appearance.fill_paint() != green, "{p}");
|
||||
assert_eq!(node(&s, g).children().unwrap().len(), children, "{p}");
|
||||
}
|
||||
// Given another Live Paint group with a face at that point as well, that face is filled.
|
||||
let other = rect(&mut s, 60.0, 40.0, 20.0, 20.0);
|
||||
let other = NodeId(s.execute("livePaint.make", &json!({"ids": [other.0]})).unwrap()["id"].as_u64().unwrap());
|
||||
let r = s.execute("livePaint.fill", &json!({"ids": [g.0, other.0], "point": [75, 50], "color": "#00ff00"})).unwrap();
|
||||
assert_eq!(r["group"], other.0);
|
||||
assert_eq!(node(&s, NodeId(r["face"].as_u64().unwrap())).appearance.fill_paint(), green);
|
||||
assert!(!node(&s, hidden).visible);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn live_paint_release_and_expand() {
|
||||
let mut s = session();
|
||||
|
||||
Reference in New Issue
Block a user