mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-15 07:10:13 +00:00
Live Paint: Merge fits a carried edge look to the piece it lands on
When Merge added a path that cut a painted edge, the piece holding the old edge's middle took the old edge's stroke rows unchanged, so its dashes started again and it kept both arrowheads. It now takes them fitted to its part of the old edge, as Gap Options does.
This commit is contained in:
@@ -779,18 +779,27 @@ fn repaint_from(g: &mut Node, old: &Node) {
|
||||
// An edge cut from the old one takes the old edge's dashes, arrowheads, width profile
|
||||
// and brush as they fall on its part of the old edge.
|
||||
if c.name.as_deref() == Some(EDGE_NAME)
|
||||
&& n.appearance.items.iter().any(depends_on_place)
|
||||
&& let Some(old_path) = was.path_data()
|
||||
{
|
||||
let tracks: Vec<Track> = old_path.subpaths.iter().map(Track::new).collect();
|
||||
let run = n.path_data_mut().and_then(|path| edge_run(&tracks, path));
|
||||
let mut a = (*n.appearance).clone();
|
||||
for r in &mut a.items {
|
||||
fit_to_edge(r, run);
|
||||
}
|
||||
n.appearance = a.into();
|
||||
fit_to_old_edge(&mut n, old_path);
|
||||
}
|
||||
*c = Arc::new(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Fit the stroke rows of edge `n`, which it took from an old edge whose outline is `old`, to the
|
||||
/// part of the old edge that `n` runs, as [`fit_to_edge`] fits a source's rows to an edge cut from
|
||||
/// it; `n` is reversed when it runs against the old edge.
|
||||
fn fit_to_old_edge(n: &mut Node, old: &PathData) {
|
||||
if !n.appearance.items.iter().any(depends_on_place) {
|
||||
return;
|
||||
}
|
||||
let tracks: Vec<Track> = old.subpaths.iter().map(Track::new).collect();
|
||||
let run = n.path_data_mut().and_then(|path| edge_run(&tracks, path));
|
||||
let mut a = (*n.appearance).clone();
|
||||
for r in &mut a.items {
|
||||
fit_to_edge(r, run);
|
||||
}
|
||||
n.appearance = a.into();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use super::{
|
||||
Appearance, Arc, Beside, Document, EDGE_NAME, EngineError, FACE_NAME, GapOptions, GapSize, LIVE_PAINT_NAME, Node, NodeId, PathData, Point, Rect,
|
||||
Result, SOURCES_NAME, Selection, Session, SubPath, Value, b, bad, build_children, edge_near, face_at, finite, ids_json, is_live_paint, json,
|
||||
number_or, object_param, paint_from, po, point_param, repaint_from, replace_roots, root_ids, source_nodes, source_shapes, switch_or,
|
||||
Result, SOURCES_NAME, Selection, Session, SubPath, Value, b, bad, build_children, edge_near, face_at, finite, fit_to_old_edge, ids_json,
|
||||
is_live_paint, json, number_or, object_param, paint_from, po, point_param, repaint_from, replace_roots, root_ids, source_nodes, source_shapes,
|
||||
switch_or,
|
||||
};
|
||||
|
||||
/// `options` as stored on a Live Paint group: none for the defaults.
|
||||
@@ -125,15 +126,15 @@ fn merge_live(d: &mut Document, sel: &mut Selection, roots: &[NodeId]) -> Result
|
||||
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) for every edge.
|
||||
// paint, outline) for every edge.
|
||||
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)> = b::edges(&old)
|
||||
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)))
|
||||
Some((kurbo::ParamCurve::eval(&sp.segment(0), 0.5), Carried::of(e), pd.clone()))
|
||||
})
|
||||
.collect();
|
||||
let mut sources = source_nodes(&old);
|
||||
@@ -161,12 +162,15 @@ fn merge_live(d: &mut Document, sel: &mut Selection, roots: &[NodeId]) -> Result
|
||||
carried.onto(Arc::make_mut(f));
|
||||
}
|
||||
}
|
||||
for (pt, carried) in keep_edges {
|
||||
// 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)
|
||||
{
|
||||
carried.onto(Arc::make_mut(e));
|
||||
let e = Arc::make_mut(e);
|
||||
carried.onto(e);
|
||||
fit_to_old_edge(e, &outline);
|
||||
}
|
||||
}
|
||||
let mut merged = Beside::replacing(d, roots);
|
||||
|
||||
@@ -2753,10 +2753,10 @@ fn live_paint_make_fits_dashes_and_arrowheads_to_each_edge() {
|
||||
assert_eq!(edges, vec![rows]);
|
||||
}
|
||||
|
||||
/// When Gap Options cuts an edge in two, each piece keeps the edge's dashes going on from where it
|
||||
/// starts along the edge, an arrowhead only at the end of the edge it ends at, and no brush.
|
||||
/// When Gap Options or Merge cuts an edge in two, each piece keeps the edge's dashes going on from
|
||||
/// where it starts along the edge, an arrowhead only at the end of the edge it ends at, and no brush.
|
||||
#[test]
|
||||
fn live_paint_gap_options_fits_an_edge_look_to_each_piece() {
|
||||
fn live_paint_gap_options_and_merge_fit_an_edge_look_to_each_piece() {
|
||||
use vectorcraft_doc::{Arrowhead, Dash};
|
||||
let mut s = session();
|
||||
let l = line(&mut s, (0.0, 50.0), (100.0, 50.0));
|
||||
@@ -2770,7 +2770,7 @@ fn live_paint_gap_options_fits_an_edge_look_to_each_piece() {
|
||||
let rows = set_rows(&mut s, l, vec![row]);
|
||||
s.execute("select.set", &json!({"ids": [l.0, stub.0]})).unwrap();
|
||||
let g = NodeId(s.execute("livePaint.make", &json!({})).unwrap()["id"].as_u64().unwrap());
|
||||
let dashed = |s: &Session| {
|
||||
let dashed = |s: &Session, g: NodeId| {
|
||||
let mut out: Vec<(f64, f64, vectorcraft_doc::StrokeLayer)> = vectorcraft_tools::builder::edges(&node(s, g))
|
||||
.iter()
|
||||
.filter_map(|e| {
|
||||
@@ -2782,12 +2782,12 @@ fn live_paint_gap_options_fits_an_edge_look_to_each_piece() {
|
||||
out.sort_by(|p, q| p.0.total_cmp(&q.0));
|
||||
out
|
||||
};
|
||||
let before = dashed(&s);
|
||||
let before = dashed(&s, g);
|
||||
assert_eq!(before.len(), 1, "nothing cuts the line yet");
|
||||
assert_eq!(vec![AppearanceItem::Stroke(before[0].2.clone())], rows);
|
||||
let r = s.execute("livePaint.gapOptions", &json!({"id": g.0, "closeGaps": true})).unwrap();
|
||||
assert_eq!(r["closed"], json!(1));
|
||||
let pieces = dashed(&s);
|
||||
let pieces = dashed(&s, g);
|
||||
let ends: Vec<(f64, f64)> = pieces.iter().map(|p| (p.0, p.1)).collect();
|
||||
assert_eq!(ends, vec![(0.0, 50.0), (50.0, 100.0)], "the line is cut where the gap closes");
|
||||
let offsets: Vec<f64> = pieces.iter().map(|p| (p.2.dash.as_ref().unwrap().offset * 1e3).round() / 1e3).collect();
|
||||
@@ -2795,6 +2795,23 @@ fn live_paint_gap_options_fits_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");
|
||||
// Merge: the piece of the edge that an added path cuts off, which takes the edge's look, gets
|
||||
// it fitted to where it lies along the edge in the same way.
|
||||
let mut s = session();
|
||||
let l = line(&mut s, (0.0, 50.0), (100.0, 50.0));
|
||||
set_rows(&mut s, l, rows.clone());
|
||||
s.execute("select.set", &json!({"ids": [l.0]})).unwrap();
|
||||
let g = NodeId(s.execute("livePaint.make", &json!({})).unwrap()["id"].as_u64().unwrap());
|
||||
let cut = line(&mut s, (25.0, 0.0), (25.0, 100.0));
|
||||
s.execute("livePaint.merge", &json!({"ids": [g.0, cut.0]})).unwrap();
|
||||
let pieces = dashed(&s, g);
|
||||
let ends: Vec<(f64, f64)> = pieces.iter().map(|p| (p.0, p.1)).collect();
|
||||
assert_eq!(ends, vec![(0.0, 25.0), (25.0, 100.0)], "the added line cuts the edge");
|
||||
let offsets: Vec<f64> = pieces.iter().map(|p| (p.2.dash.as_ref().unwrap().offset * 1e3).round() / 1e3).collect();
|
||||
assert_eq!(offsets, vec![2.0, 27.0], "the dashes go on along the line");
|
||||
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");
|
||||
}
|
||||
|
||||
/// 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` cuts an edge into pieces, each piece takes the edge's 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
|
||||
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
|
||||
`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