M3.13: swatch.newGroup falls back to "Color Group" for a blank name, never gives a swatch its own group's name, and takes mesh point colours from the artwork

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bflatastic 2026-10-04 17:45:49 +07:00
parent e9e4b94ed0
commit d1d133bf09
3 changed files with 31 additions and 4 deletions

View File

@ -119,7 +119,7 @@ pub fn color_name(c: Color) -> String {
}
/// Every colour in the subtree of `n` with the swatch it links to: solid fills, strokes and text
/// runs (with their link) and gradient stops (unlinked), in paint order.
/// runs (with their link), then gradient stops and mesh points (unlinked), in paint order.
pub fn node_colors(n: &Node, f: &mut dyn FnMut(&Color, Option<&str>)) {
n.walk(&mut |m| {
for p in node_paints(m) {
@ -129,6 +129,9 @@ pub fn node_colors(n: &Node, f: &mut dyn FnMut(&Color, Option<&str>)) {
_ => {}
}
}
if let NodeKind::Mesh(mesh) = &m.kind {
mesh.points.iter().for_each(|p| f(&p.color, None));
}
});
}

View File

@ -35,7 +35,7 @@ pub fn specs() -> Vec<CommandSpec> {
"New Color Group",
["Window", "Swatches"],
None,
"{name?, swatches?: [names] (solid colours move into the group; gradients, patterns and None stay), colors?: [colour] (added as new swatches), fromArtwork?: false (add the unique colours of the selected art: fills, strokes, text and gradient stops; a colour linked to a global swatch moves that swatch into the group), toGlobal?: true (with fromArtwork: the new swatches are global and the selected art's matching unlinked colours link to them), includeTints?: false (with fromArtwork: tints of global swatches also get swatches of their own)} → {name, swatches: [names in the group], linked: paints linked}",
"{name?, swatches?: [names] (solid colours move into the group; gradients, patterns and None stay), colors?: [colour] (added as new swatches), fromArtwork?: false (add the unique colours of the selected art: fills, strokes, text, gradient stops and mesh points; a colour linked to a global swatch moves that swatch into the group), toGlobal?: true (with fromArtwork: the new swatches are global and the selected art's matching unlinked colours link to them), includeTints?: false (with fromArtwork: tints of global swatches also get swatches of their own)} → {name, swatches: [names in the group], linked: paints linked}",
has_doc,
swatch_new_group
),
@ -168,7 +168,7 @@ fn swatch_new_group(s: &mut Session, p: &Value) -> Result<Value> {
let from_art = bool_or(p, "fromArtwork", false);
let to_global = from_art && bool_or(p, "toGlobal", true);
let tints = bool_or(p, "includeTints", false);
let requested = str_param(p, "name").map(str::to_string);
let requested = name_param(p, "name");
let (name, members, linked) = s.edit("New Color Group", |d, sel| {
// New swatches: (colour, global).
let mut new: Vec<(Color, bool)> = colors.iter().map(|c| (*c, false)).collect();
@ -193,7 +193,7 @@ fn swatch_new_group(s: &mut Session, p: &Value) -> Result<Value> {
// The global swatches made from the art's colours, which its unlinked paints link to.
let mut links: Vec<(Color, String)> = vec![];
for (c, global) in new {
let nm = unique_name(&color_name(c), |n| d.swatch_name_taken(n) || group.swatches.iter().any(|sw| sw.name == n));
let nm = unique_name(&color_name(c), |n| n == name || d.swatch_name_taken(n) || group.swatches.iter().any(|sw| sw.name == n));
if global {
links.push((c, nm.clone()));
}

View File

@ -324,3 +324,27 @@ fn tints_of_global_swatches_get_swatches_only_when_asked() {
assert_eq!(with["swatches"], json!(["Brand", "R=255 G=0 B=0", "R=0 G=255 B=0", "R=149 G=183 B=216"]));
assert_eq!(doc(&s).node(b).unwrap().appearance.stroke_paint(), Paint::Solid { color: tint, swatch: Some("Brand".into()) }, "tints stay linked");
}
#[test]
fn group_names_are_never_blank_and_never_shared_with_their_swatches() {
let mut s = session();
art(&mut s);
// A blank name falls back to the default; a swatch never takes its own group's name.
let r = run(&mut s, "swatch.newGroup", json!({"name": " ", "colors": ["#0000ff"]}));
assert_eq!(r["name"], "Color Group");
let r = run(&mut s, "swatch.newGroup", json!({"name": "R=255 G=0 B=0", "fromArtwork": true}));
assert_eq!(r["name"], "R=255 G=0 B=0");
assert_eq!(r["swatches"], json!(["Brand", "R=255 G=0 B=0 2", "R=0 G=255 B=0"]));
}
#[test]
fn a_group_from_a_mesh_holds_its_point_colours() {
let mut s = session();
let a = rect(&mut s);
run(&mut s, "paint.setFill", json!({"ids": [a.0], "color": "#ff0000"}));
run(&mut s, "select.set", json!({"ids": [a.0]}));
run(&mut s, "object.mesh.create", json!({"rows": 2, "cols": 2, "appearance": "center", "highlight": 100}));
let r = run(&mut s, "swatch.newGroup", json!({"fromArtwork": true}));
let names = r["swatches"].as_array().unwrap();
assert!(names.contains(&json!("R=255 G=0 B=0")) && names.contains(&json!("R=255 G=255 B=255")), "{r}");
}