mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-15 10:05:42 +00:00
Live Paint: Merge keeps a painted edge's look on every piece an added path cuts
Merge carried each old edge's paint only to the new edge nearest the old edge's middle, so when an added path cut a repainted edge, the other piece went back to its source's stroke. Every new edge whose middle lies on an old edge now takes that edge's paint, with its rows fitted to its part of the edge, as Gap Options does.
This commit is contained in:
@@ -85,6 +85,9 @@ pub(super) fn lp_make(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
Ok(json!({ "id": id.0, "faces": faces, "edges": edges }))
|
||||
}
|
||||
|
||||
/// How far, in points, the middle of an edge Merge makes may be from the old edge it lies on.
|
||||
const ON_OLD_EDGE: f64 = 1e-3;
|
||||
|
||||
/// What Merge carries from a face or edge to the part it makes again in its place, another path:
|
||||
/// the appearance, visibility, opacity and blending mode. Gradients fitted to the old outline keep
|
||||
/// that fit, and the strokes' pen pressure, tilt, bearing and rotation stay behind.
|
||||
@@ -125,18 +128,11 @@ fn merge_live(d: &mut Document, sel: &mut Selection, roots: &[NodeId]) -> Result
|
||||
.ok_or_else(|| EngineError::Other("Live Paint Merge: select a Live Paint group and the paths to add".into()))?;
|
||||
check_live_paint_takes(d, roots)?;
|
||||
let old = d.node(target).cloned().ok_or(EngineError::NoNode(target))?;
|
||||
// Paint to carry over: (point inside face, paint) for faces that draw, and (point on edge,
|
||||
// paint, outline) for every edge.
|
||||
// Paint to carry over: (point inside face, paint) for faces that draw; edges are matched to the
|
||||
// old edges below.
|
||||
let keep_faces: Vec<(Point, Carried)> =
|
||||
b::faces(&old).iter().filter(|f| draws(f)).filter_map(|f| Some((po::interior_point(f.path_data()?)?, Carried::of(f)))).collect();
|
||||
let keep_edges: Vec<(Point, Carried, PathData)> = b::edges(&old)
|
||||
.iter()
|
||||
.filter_map(|e| {
|
||||
let pd = e.path_data()?;
|
||||
let sp = pd.subpaths.first().filter(|sp| sp.segment_count() > 0)?;
|
||||
Some((kurbo::ParamCurve::eval(&sp.segment(0), 0.5), Carried::of(e), pd.clone()))
|
||||
})
|
||||
.collect();
|
||||
let old_parts = old.clone();
|
||||
let mut sources = source_nodes(&old);
|
||||
for r in roots {
|
||||
if *r != target {
|
||||
@@ -162,15 +158,24 @@ fn merge_live(d: &mut Document, sel: &mut Selection, roots: &[NodeId]) -> Result
|
||||
carried.onto(Arc::make_mut(f));
|
||||
}
|
||||
}
|
||||
// An edge that an added path cuts from an old one takes the old edge's rows fitted to its part.
|
||||
for (pt, carried, outline) in keep_edges {
|
||||
if let Some(eid) = edge_near(&g, pt, 0.5).map(|e| e.id)
|
||||
&& let Some(ch) = g.children_mut()
|
||||
&& let Some(e) = ch.iter_mut().find(|c| c.id == eid)
|
||||
{
|
||||
let e = Arc::make_mut(e);
|
||||
carried.onto(e);
|
||||
fit_to_old_edge(e, &outline);
|
||||
// Each edge whose middle lies on an old edge, every piece of an edge an added path cuts
|
||||
// included, takes the old edge's paint, with its rows fitted to its part of the old edge.
|
||||
if let Some(ch) = g.children_mut() {
|
||||
for c in ch.iter_mut().filter(|c| c.name.as_deref() == Some(EDGE_NAME)) {
|
||||
let Some(mid) = c
|
||||
.path_data()
|
||||
.and_then(|pd| pd.subpaths.first())
|
||||
.filter(|sp| sp.segment_count() > 0)
|
||||
.map(|sp| kurbo::ParamCurve::eval(&sp.segment(0), 0.5))
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Some(was) = edge_near(&old_parts, mid, ON_OLD_EDGE) else { continue };
|
||||
let e = Arc::make_mut(c);
|
||||
Carried::of(was).onto(e);
|
||||
if let Some(outline) = was.path_data() {
|
||||
fit_to_old_edge(e, outline);
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut merged = Beside::replacing(d, roots);
|
||||
|
||||
@@ -2812,6 +2812,27 @@ fn live_paint_gap_options_and_merge_fit_an_edge_look_to_each_piece() {
|
||||
let arrows: Vec<_> = pieces.iter().map(|p| (p.2.start_arrow.clone(), p.2.end_arrow.clone())).collect();
|
||||
assert_eq!(arrows, vec![(Some(Arrowhead::Triangle), None), (None, Some(Arrowhead::Circle))]);
|
||||
assert!(pieces.iter().all(|p| p.2.brush.is_none()), "a brush spreads over the whole line");
|
||||
// A repainted edge that Merge cuts keeps its paint on every piece.
|
||||
let mut s = session();
|
||||
let l = line(&mut s, (0.0, 50.0), (100.0, 50.0));
|
||||
s.execute("select.set", &json!({"ids": [l.0]})).unwrap();
|
||||
let g = NodeId(s.execute("livePaint.make", &json!({})).unwrap()["id"].as_u64().unwrap());
|
||||
s.execute("livePaint.strokeEdge", &json!({"group": g.0, "point": [50, 50], "color": "#ff0000", "width": 3})).unwrap();
|
||||
let cut = line(&mut s, (50.0, 0.0), (50.0, 100.0));
|
||||
s.execute("livePaint.merge", &json!({"ids": [g.0, cut.0]})).unwrap();
|
||||
let gn = node(&s, g);
|
||||
let red = Paint::solid(Color::from_hex("#ff0000").unwrap());
|
||||
let mut on_line: Vec<(f64, bool)> = vectorcraft_tools::builder::edges(&gn)
|
||||
.iter()
|
||||
.filter_map(|e| {
|
||||
let sp = e.path_data()?.subpaths.first()?;
|
||||
let (a, z) = (sp.anchors.first()?.p, sp.anchors.last()?.p);
|
||||
((a.y - 50.0).abs() < 1e-6 && (z.y - 50.0).abs() < 1e-6)
|
||||
.then(|| ((a.x.min(z.x) * 1e3).round() / 1e3, e.appearance.stroke().is_some_and(|st| st.paint == red)))
|
||||
})
|
||||
.collect();
|
||||
on_line.sort_by(|p, q| p.0.total_cmp(&q.0));
|
||||
assert_eq!(on_line, vec![(0.0, true), (50.0, true)], "both pieces keep the edge's paint");
|
||||
}
|
||||
|
||||
/// The edge Make builds from a closed path that nothing cuts starts at the anchor the path starts
|
||||
|
||||
+2
-2
@@ -3614,8 +3614,8 @@ already walks Image Trace. Short agent entry points:
|
||||
the path (Align Dashes to Corners is turned off on it), the arrowheads stay only at the path's own ends, and a width
|
||||
profile or brush stays only on an edge that runs the whole path. A gradient applied along a stroke starts again on
|
||||
each cut edge, and an inside or outside stroke is drawn centered on an edge that is an open piece of a closed path.
|
||||
When `livePaint.gapOptions` or `livePaint.merge` cuts an edge into pieces, a piece that takes the edge's rows takes
|
||||
them fitted to its part of the edge in the same way. A face or edge given no such row has one unpainted row. Rows that do not draw stay with the stored sources, which
|
||||
When `livePaint.gapOptions` or `livePaint.merge` cuts an edge into pieces, each piece takes the edge's paint, with
|
||||
its rows fitted to its part of the edge in the same way. A face or edge given no such row has one unpainted row. Rows that do not draw stay with the stored sources, which
|
||||
`livePaint.release` returns unchanged. The source's object effects and the strokes' pen pressure are not carried.
|
||||
Expand keeps those face fills.
|
||||
`livePaint.expand` keeps each face and edge that draws, with all its fill and stroke rows: it is shown, its opacity
|
||||
|
||||
Reference in New Issue
Block a user