From 76be75400e09438fd9a2f9c2163e6df363484a50 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 31 Oct 2026 23:23:18 -0700 Subject: [PATCH 1/2] Mesh: Create Gradient Mesh at a point outside the object keeps the path object.mesh.create with `at` ignored a failed line placement and still replaced the path with a bare 1x1 mesh. When the new mesh can't place its lines at the point, the object is left as it is, and when nothing was converted the command fails with "point is outside the object" and adds no undo step. --- crates/engine/src/cmd/live/mesh.rs | 9 ++++++++- crates/engine/src/tests/live.rs | 20 ++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/cmd/live/mesh.rs b/crates/engine/src/cmd/live/mesh.rs index bdba2b037..e00c074bc 100644 --- a/crates/engine/src/cmd/live/mesh.rs +++ b/crates/engine/src/cmd/live/mesh.rs @@ -59,7 +59,14 @@ pub(super) fn mesh_create(s: &mut Session, p: &Value) -> Result { }; let Some(mut m) = m else { continue }; if let Some(q) = at { - index = m.add_lines_at(q, tol); + // A point the new mesh can't place its lines at leaves the object as it is. + match m.add_lines_at(q, tol) { + Some(i) => index = Some(i), + None => { + refused.get_or_insert("point is outside the object"); + continue; + } + } } let mut mn = Node::new(n.id, NodeKind::Mesh(m)); mn.name = n.name.clone(); diff --git a/crates/engine/src/tests/live.rs b/crates/engine/src/tests/live.rs index b46e5b0d0..06f250aad 100644 --- a/crates/engine/src/tests/live.rs +++ b/crates/engine/src/tests/live.rs @@ -1040,6 +1040,26 @@ fn mesh_create_at_point_single_undo() { assert!(b.x0 > -3.0 && b.x1 < 103.0, "{b:?}"); } +/// Create Gradient Mesh with an `at` the new mesh can't place its lines at (outside the object, +/// or past `tolerance`) is an error that leaves the path a path; within `tolerance` it adds them. +#[test] +fn mesh_create_at_a_point_outside_keeps_the_path() { + let mut s = session(); + let a = rect(&mut s, 0.0, 0.0, 100.0, 100.0); + sel(&mut s, &[a]); + let n = undo_len(&s); + for p in [json!({"at": [300, 300]}), json!({"at": [50, 103]}), json!({"at": [50, 110], "tolerance": 3})] { + let r = s.execute("object.mesh.create", &p); + assert!(matches!(&r, Err(EngineError::BadParams { msg, .. }) if msg == "point is outside the object"), "{p}: {r:?}"); + assert!(matches!(node(&s, a).kind, NodeKind::Path { .. }), "{p}: still a path"); + } + assert_eq!(undo_len(&s), n, "nothing changed"); + let r = s.execute("object.mesh.create", &json!({"at": [50, 102], "tolerance": 3})).unwrap(); + let m = mesh_of(&s, a); + assert_eq!((m.rows, m.cols), (1, 2), "a column through the bottom edge"); + assert!(m.points[r["index"].as_u64().unwrap() as usize].p.distance(Point::new(50.0, 100.0)) < 0.5); +} + #[test] fn mesh_expand_to_flat_pieces() { let mut s = session(); From 8a5ea9488e707fc850b8882a0904de57c81520c1 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sun, 1 Nov 2026 07:50:11 -0800 Subject: [PATCH 2/2] Gradient mesh: test Create Gradient Mesh at a point inside one of two selected objects --- crates/engine/src/tests/live.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/crates/engine/src/tests/live.rs b/crates/engine/src/tests/live.rs index 06f250aad..f500e2c39 100644 --- a/crates/engine/src/tests/live.rs +++ b/crates/engine/src/tests/live.rs @@ -1060,6 +1060,22 @@ fn mesh_create_at_a_point_outside_keeps_the_path() { assert!(m.points[r["index"].as_u64().unwrap() as usize].p.distance(Point::new(50.0, 100.0)) < 0.5); } +/// With several objects selected, Create Gradient Mesh at a point turns the objects that hold the +/// point into meshes in one undo step and leaves the others paths, without an error. +#[test] +fn mesh_create_at_a_point_inside_one_of_two_objects_converts_only_that_one() { + let mut s = session(); + let a = rect(&mut s, 0.0, 0.0, 100.0, 100.0); + let b = rect(&mut s, 200.0, 0.0, 300.0, 100.0); + sel(&mut s, &[a, b]); + let n = undo_len(&s); + let r = s.execute("object.mesh.create", &json!({"at": [50, 50]})).unwrap(); + assert_eq!(r["ids"], json!([a.0]), "{r}"); + assert!(matches!(node(&s, a).kind, NodeKind::Mesh(_)), "a holds the point"); + assert!(matches!(node(&s, b).kind, NodeKind::Path { .. }), "b stays a path"); + assert_eq!(undo_len(&s), n + 1, "one undo step"); +} + #[test] fn mesh_expand_to_flat_pieces() { let mut s = session();