mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-09 02:34:09 +00:00
Merge pull request #5197 from dotCooCoo/fix/cleanup-leaves-hidden-locked-art
Artwork Cleanup: Simplify Anchors and Deep Ungroup leave hidden and locked art alone
This commit is contained in:
@@ -37,7 +37,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Simplify Anchors (Keep Shape)…",
|
||||
["Object", "Plug-ins", "Artwork Cleanup"],
|
||||
None,
|
||||
"{tolerance?: 1..100 (20; higher removes more anchors and lets the outline move further from where it was, up to tolerance × 0.02 pt), protectCorners?: bool (false; anchors where the path turns by more than 30° stay), ids?: [id…] (default: the selection)} remove the anchors each path under the objects can do without, refitting the handles beside each so the outline keeps its shape, as one undo step; open ends stay → {ids, before, after} anchor counts",
|
||||
"{tolerance?: 1..100 (20; higher removes more anchors and lets the outline move further from where it was, up to tolerance × 0.02 pt), protectCorners?: bool (false; anchors where the path turns by more than 30° stay), ids?: [id…] (default: the selection)} remove the anchors each path under the objects can do without, refitting the handles beside each so the outline keeps its shape, as one undo step; open ends stay, and locked and hidden paths are left out → {ids, before, after} anchor counts",
|
||||
has_selection,
|
||||
simplify
|
||||
),
|
||||
@@ -73,7 +73,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Deep Ungroup…",
|
||||
["Object", "Plug-ins", "Artwork Cleanup"],
|
||||
None,
|
||||
"{includeClipGroups?: bool (false: clipping groups, and the art inside them, stay as they are; true: they are ungrouped too and their clipping paths become plain paths), ids?: [id…] (default: the selection)} ungroup every level of the groups among the objects, as one undo step; each object keeps the opacity of the groups it was in → {ids (the objects left, selected), ungrouped (groups)}",
|
||||
"{includeClipGroups?: bool (false: clipping groups, and the art inside them, stay as they are; true: they are ungrouped too and their clipping paths become plain paths), ids?: [id…] (default: the selection)} ungroup every level of the groups among the objects, as one undo step; each object keeps the opacity of the groups it was in; locked and hidden groups stay whole, and locked and hidden objects stay locked or hidden and are not selected → {ids (the objects selected: those left, or with no group to take apart the selection as it was), ungrouped (groups)}",
|
||||
has_selection,
|
||||
deep_ungroup
|
||||
),
|
||||
@@ -91,7 +91,8 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
v
|
||||
}
|
||||
|
||||
/// The paths (not guides) under `roots`, compound path members included.
|
||||
/// The paths (not guides) under `roots`, compound path members included; locked and hidden
|
||||
/// paths, and those in locked or hidden groups, stay out.
|
||||
fn paths_under(d: &Document, roots: &[NodeId]) -> Vec<NodeId> {
|
||||
let mut out = vec![];
|
||||
for n in roots.iter().filter_map(|id| d.node(*id)) {
|
||||
@@ -101,6 +102,8 @@ fn paths_under(d: &Document, roots: &[NodeId]) -> Vec<NodeId> {
|
||||
}
|
||||
});
|
||||
}
|
||||
let editable = d.editable();
|
||||
out.retain(|id| editable(*id));
|
||||
out
|
||||
}
|
||||
|
||||
@@ -256,9 +259,9 @@ fn ungroupable(n: &Node, clips: bool) -> bool {
|
||||
|
||||
/// `n` with every level of grouping Deep Ungroup takes apart removed, into `out`; `count` counts
|
||||
/// the groups. Members take on the opacity of the groups they were in, and a clipping path
|
||||
/// becomes a plain path.
|
||||
/// becomes a plain path. A locked or hidden group stays whole.
|
||||
fn flatten(n: Node, clips: bool, out: &mut Vec<Node>, count: &mut usize) {
|
||||
if !ungroupable(&n, clips) {
|
||||
if !ungroupable(&n, clips) || n.locked || !n.visible {
|
||||
out.push(n);
|
||||
return;
|
||||
}
|
||||
@@ -282,6 +285,11 @@ fn deep_ungroup(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let clips = switch_or(p, "includeClipGroups", false, C)?;
|
||||
let roots = checked_ids_or_selection(s, p, C)?;
|
||||
let d = &s.doc()?.doc;
|
||||
// Locked and hidden objects, and those in locked or hidden groups, stay as they are.
|
||||
let roots: Vec<NodeId> = {
|
||||
let editable = d.editable();
|
||||
roots.into_iter().filter(|id| editable(*id)).collect()
|
||||
};
|
||||
let (mut items, mut selected, mut count) = (vec![], vec![], 0);
|
||||
for id in &roots {
|
||||
match d.node(*id).filter(|n| ungroupable(n, clips)) {
|
||||
@@ -294,13 +302,18 @@ fn deep_ungroup(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
None => selected.push(*id),
|
||||
}
|
||||
}
|
||||
// With no group to take apart, the document and its undo history stay as they are.
|
||||
if !items.is_empty() {
|
||||
let ids = selected.clone();
|
||||
s.edit("Deep Ungroup", |d, sel| {
|
||||
// With no group to take apart, the document, the selection and the undo history stay as they
|
||||
// are, and the reply lists the selection.
|
||||
if items.is_empty() {
|
||||
selected = s.doc()?.selection.objects.to_vec();
|
||||
} else {
|
||||
selected = s.edit("Deep Ungroup", move |d, sel| {
|
||||
d.insert_beside(items, true)?;
|
||||
sel.set(ids);
|
||||
Ok(())
|
||||
// Members that are locked or hidden themselves keep that, and are not selected.
|
||||
let editable = d.editable();
|
||||
selected.retain(|id| editable(*id));
|
||||
sel.set(selected.iter().copied());
|
||||
Ok(selected)
|
||||
})?;
|
||||
}
|
||||
Ok(json!({ "ids": selected.iter().map(|i| i.0).collect::<Vec<_>>(), "ungrouped": count }))
|
||||
|
||||
@@ -104,6 +104,39 @@ fn simplify_keeps_the_shape_with_fewer_anchors_in_one_step() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simplify_leaves_locked_and_hidden_paths_alone() {
|
||||
let mut s = session();
|
||||
let mut ids = vec![];
|
||||
for x in [20.0, 140.0, 260.0] {
|
||||
ids.push(id_of(&s.execute("shape.ellipse", &json!({"x": x, "y": 50, "width": 100, "height": 100})).unwrap()));
|
||||
s.execute("object.path.addAnchorPoints", &json!({})).unwrap();
|
||||
s.execute("object.path.addAnchorPoints", &json!({})).unwrap();
|
||||
}
|
||||
select(&mut s, &ids);
|
||||
let g = id_of(&s.execute("object.group", &json!({})).unwrap());
|
||||
change(&mut s, ids[1], |n| n.locked = true);
|
||||
change(&mut s, ids[2], |n| n.visible = false);
|
||||
select(&mut s, &[g]);
|
||||
let r = s.execute("addon.cleanup.simplify", &json!({})).unwrap();
|
||||
assert_eq!(r["ids"], json!([ids[0].0]));
|
||||
assert_eq!(r["before"], 16);
|
||||
assert!(anchors(&s, ids[0]) < 16);
|
||||
assert_eq!((anchors(&s, ids[1]), anchors(&s, ids[2])), (16, 16));
|
||||
// Named by `ids`, or in a locked group, a path stays too, with no undo step.
|
||||
let steps = undo_steps(&s);
|
||||
s.execute("addon.cleanup.simplify", &json!({"ids": [ids[1].0, ids[2].0]})).unwrap();
|
||||
assert_eq!((anchors(&s, ids[1]), anchors(&s, ids[2])), (16, 16));
|
||||
assert_eq!(undo_steps(&s), steps);
|
||||
change(&mut s, ids[1], |n| n.locked = false);
|
||||
change(&mut s, g, |n| n.locked = true);
|
||||
let steps = undo_steps(&s);
|
||||
let r = s.execute("addon.cleanup.simplify", &json!({"ids": [g.0]})).unwrap();
|
||||
assert_eq!((r["before"].as_u64(), r["after"].as_u64()), (Some(0), Some(0)));
|
||||
assert_eq!(anchors(&s, ids[1]), 16);
|
||||
assert_eq!(undo_steps(&s), steps);
|
||||
}
|
||||
|
||||
/// Each kind: what makes the issue in a fresh document (beside a clean rectangle).
|
||||
fn make_issue(s: &mut Session, kind: &str) {
|
||||
match kind {
|
||||
@@ -333,6 +366,51 @@ fn deep_ungroup_takes_apart_every_level_but_clipping_groups() {
|
||||
assert!(matches!(d.node(clip).unwrap().kind, NodeKind::Path { clipping: false, .. }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deep_ungroup_keeps_hidden_art_hidden_and_locked_art_locked() {
|
||||
let mut s = session();
|
||||
let a = rect(&mut s, 10.0, 10.0, 20.0, 20.0);
|
||||
let b = rect(&mut s, 40.0, 10.0, 20.0, 20.0);
|
||||
let c = rect(&mut s, 70.0, 10.0, 20.0, 20.0);
|
||||
let e = rect(&mut s, 100.0, 10.0, 20.0, 20.0);
|
||||
select(&mut s, &[b]);
|
||||
let hidden = id_of(&s.execute("object.group", &json!({})).unwrap());
|
||||
select(&mut s, &[c]);
|
||||
let locked = id_of(&s.execute("object.group", &json!({})).unwrap());
|
||||
change(&mut s, hidden, |n| n.visible = false);
|
||||
change(&mut s, locked, |n| n.locked = true);
|
||||
change(&mut s, e, |n| n.locked = true);
|
||||
s.edit("t", |d, _| {
|
||||
let w = d.alloc_id();
|
||||
Ok(d.wrap(&[a, hidden, locked, e], |ch| Node::group(w, ch))?)
|
||||
})
|
||||
.unwrap();
|
||||
let outer = s.doc().unwrap().doc.layers[0].children().unwrap()[0].id;
|
||||
select(&mut s, &[outer]);
|
||||
let r = s.execute("addon.cleanup.deepUngroup", &json!({})).unwrap();
|
||||
assert_eq!(r["ungrouped"], 1);
|
||||
assert_eq!(r["ids"], json!([a.0]));
|
||||
let d = &s.doc().unwrap().doc;
|
||||
let layer: Vec<NodeId> = d.layers[0].children().unwrap().iter().map(|n| n.id).collect();
|
||||
assert_eq!(layer, [a, hidden, locked, e], "the hidden and locked groups stay whole");
|
||||
assert!(!d.node(hidden).unwrap().visible && d.node(b).unwrap().visible);
|
||||
assert!(d.node(locked).unwrap().locked && d.node(e).unwrap().locked);
|
||||
assert_eq!(s.doc().unwrap().selection.objects, [a]);
|
||||
// Named by `ids`, a locked group stays as it is, with no undo step.
|
||||
let steps = undo_steps(&s);
|
||||
let r = s.execute("addon.cleanup.deepUngroup", &json!({"ids": [locked.0]})).unwrap();
|
||||
assert_eq!(r["ungrouped"], 0);
|
||||
assert!(node(&s, locked).is_some());
|
||||
assert_eq!(undo_steps(&s), steps);
|
||||
// With nothing ungrouped, the selection stays and the reply lists it.
|
||||
assert_eq!(r["ids"], json!([a.0]));
|
||||
let f = rect(&mut s, 130.0, 10.0, 20.0, 20.0);
|
||||
select(&mut s, &[a]);
|
||||
let r = s.execute("addon.cleanup.deepUngroup", &json!({"ids": [f.0]})).unwrap();
|
||||
assert_eq!((r["ids"].clone(), r["ungrouped"].clone()), (json!([a.0]), json!(0)));
|
||||
assert_eq!(s.doc().unwrap().selection.objects, [a]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejoin_joins_pieces_within_the_gap_only() {
|
||||
let mut s = session();
|
||||
|
||||
+5
-3
@@ -5970,8 +5970,9 @@ Plug-ins › Artwork Cleanup and in its panel (`window.panel {panel: "artworkCle
|
||||
`addon.cleanup.simplify {tolerance?, protectCorners?, ids?}` removes the anchors each selected path can do without and
|
||||
refits the handles beside each one, so the outline keeps its shape. `tolerance` runs from 1 to 100 (20 by default); the
|
||||
outline stays within tolerance × 0.02 pt of where it was, measured against the original outline, so removals don't add
|
||||
up. With `protectCorners`, anchors where the path turns by more than 30° stay. Open ends always stay. It reports the
|
||||
anchor counts `before` and `after`, as one undo step.
|
||||
up. With `protectCorners`, anchors where the path turns by more than 30° stay. Open ends always stay. Locked and hidden
|
||||
paths, and paths in locked or hidden groups, are left out. It reports the anchor counts `before` and `after`, as one
|
||||
undo step.
|
||||
|
||||
The health check looks for issues in the selection, or in the whole document while nothing is selected (`scope:
|
||||
"selection"|"document"` picks one). Locked and hidden objects, and the insides of live or generated art (blends,
|
||||
@@ -6005,7 +6006,8 @@ envelopes, graphs, symbols), are left out.
|
||||
|
||||
`addon.cleanup.deepUngroup {includeClipGroups?, ids?}` takes apart every level of grouping in the selection. Clipping
|
||||
groups stay whole unless `includeClipGroups` is true; their clipping paths then become plain paths. Objects keep the
|
||||
opacity of the groups they were in.
|
||||
opacity of the groups they were in. Locked and hidden groups stay whole, and locked and hidden objects stay locked or
|
||||
hidden and are left out of the selection.
|
||||
|
||||
`addon.cleanup.rejoin {gap?, ids?}` welds the selected open paths whose ends lie within `gap` (0 to 6 pt, 0.1 by
|
||||
default) back into paths. Each end joins one other end: where several are in reach, the one the path turns least to go
|
||||
|
||||
Reference in New Issue
Block a user