From 4e10c31fdd78fa43b825eca6dc9d6c3b45d12eff Mon Sep 17 00:00:00 2001 From: HiroKenEz <59478073+HiroKenEz@users.noreply.github.com> Date: Thu, 15 Oct 2026 19:32:10 +0200 Subject: [PATCH] sketch.create: refuse a face (or origin/x_dir/y_dir) next to plane (#1447) sketch.create refuses a face (or origin/x_dir/y_dir) given next to plane, instead of silently ignoring one of them. --- crates/engine/src/cmd/sketch.rs | 12 ++++++++++++ crates/engine/src/cmd/sketch_face_tests.rs | 21 +++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/crates/engine/src/cmd/sketch.rs b/crates/engine/src/cmd/sketch.rs index bc19ee39..f1e5d9b3 100644 --- a/crates/engine/src/cmd/sketch.rs +++ b/crates/engine/src/cmd/sketch.rs @@ -623,6 +623,18 @@ pub(super) fn face_contains(m: &solidcraft_geom::Mesh, fi: usize, p: Vec3, tol: fn create_sketch(s: &mut Session, p: &Value) -> Result { let cmd = "sketch.create"; + // These name the plane, so they belong inside `plane`. Next to it they were ignored and the + // sketch went to XY (or the plane given) without a word. + for (key, example) in [ + ("face", r#"{"plane": {"face": [x, y, z]}}"#), + ("origin", r#"{"plane": {"origin": [x, y, z], "x_dir": [..], "y_dir": [..]}}"#), + ("x_dir", r#"{"plane": {"origin": [x, y, z], "x_dir": [..], "y_dir": [..]}}"#), + ("y_dir", r#"{"plane": {"origin": [x, y, z], "x_dir": [..], "y_dir": [..]}}"#), + ] { + if p.get(key).is_some() { + return Err(bad(cmd, format!("`{key}` belongs inside `plane`: {example}"))); + } + } create_sketch_with_plane(s, p, plane_ref(s, p, cmd)?, None) } diff --git a/crates/engine/src/cmd/sketch_face_tests.rs b/crates/engine/src/cmd/sketch_face_tests.rs index 3d5f226b..42a0e129 100644 --- a/crates/engine/src/cmd/sketch_face_tests.rs +++ b/crates/engine/src/cmd/sketch_face_tests.rs @@ -677,3 +677,24 @@ fn every_flat_face_of_one_sketch_extruded_up_and_down_takes_an_attached_sketch() assert_eq!(sketch_and_extrude_on_every_flat_face(&logo, &dir, "logo"), 12); let _ = std::fs::remove_dir_all(&dir); } + +/// A face given next to `plane` instead of inside it was ignored and the sketch went to XY: +/// it is refused, with where it belongs, and nothing is added. Inside `plane` it attaches. +#[test] +fn a_face_next_to_plane_is_refused_not_ignored() { + let mut s = Session::default(); + run(&mut s, "solid.box", json!({"length": 20, "width": 20, "height": 10})); + let features = s.doc.features.len(); + for p in [ + json!({"face": [10, 10, 10]}), + json!({"plane": "XY", "face": [10, 10, 10]}), + json!({"origin": [0, 0, 5], "x_dir": [1, 0, 0], "y_dir": [0, 1, 0]}), + ] { + let e = s.execute("sketch.create", &p).unwrap_err().to_string(); + assert!(e.contains("belongs inside `plane`"), "{p}: {e}"); + } + assert_eq!(s.doc.features.len(), features); + assert!(s.active_sketch.is_none()); + let r = run(&mut s, "sketch.create", json!({"plane": {"face": [10, 10, 10]}})); + assert_eq!(r["attachment"], "exact_face", "{r}"); +}