diff --git a/crates/engine/src/cmd/modify.rs b/crates/engine/src/cmd/modify.rs index c2a5d24de..8d818ece9 100644 --- a/crates/engine/src/cmd/modify.rs +++ b/crates/engine/src/cmd/modify.rs @@ -1437,6 +1437,16 @@ fn run_explode(s: &mut Session, p: &Value) -> Result { Ok(json!({ "handles": r.iter().map(|h| h.hex()).collect::>() })) } +/// Gives a rebuilt arc piece its source centre Z, or a rebuilt polyline piece its source elevation. +fn with_z(mut k: EntityKind, z: f64) -> EntityKind { + match &mut k { + EntityKind::Arc(a) => a.center.z = z, + EntityKind::LwPolyline(p) => p.elevation = z, + _ => {} + } + k +} + /// Break `h` between two points (or at one point when p1 == p2). pub(crate) fn break_entity(s: &mut Session, h: Handle, p1: Vec2, p2: Vec2) -> Result> { if super::curves::is_locked(s, h) { @@ -1450,12 +1460,13 @@ pub(crate) fn break_entity(s: &mut Session, h: Handle, p1: Vec2, p2: Vec2) -> Re if t1 > t2 { std::mem::swap(&mut t1, &mut t2); } + let at3 = |t: f64| ln.at(t).to3(l.a.z + (l.b.z - l.a.z) * t); let mut v = Vec::new(); if t1 > 1e-12 { - v.push(line(ln.a, ln.at(t1))); + v.push(EntityKind::Line(cadcraft_doc::Line { a: l.a, b: at3(t1) })); } if t2 < 1.0 - 1e-12 { - v.push(line(ln.at(t2), ln.b)); + v.push(EntityKind::Line(cadcraft_doc::Line { a: at3(t2), b: l.b })); } v } @@ -1466,7 +1477,7 @@ pub(crate) fn break_entity(s: &mut Session, h: Handle, p1: Vec2, p2: Vec2) -> Re let a1 = c.center.xy().angle_to(p1); let a2 = c.center.xy().angle_to(p2); // AutoCAD removes the CCW portion from the first to the second point. - vec![arc(&Arc::new(c.center.xy(), c.radius, a2, a1))] + vec![with_z(arc(&Arc::new(c.center.xy(), c.radius, a2, a1)), c.center.z)] } EntityKind::Arc(a) => { let ga = Arc::new(a.center.xy(), a.radius, a.start, a.end); @@ -1477,10 +1488,10 @@ pub(crate) fn break_entity(s: &mut Session, h: Handle, p1: Vec2, p2: Vec2) -> Re } let mut v = Vec::new(); if t1 > 1e-12 { - v.push(arc(&Arc::new(ga.center, ga.radius, ga.start, ga.start + t1))); + v.push(with_z(arc(&Arc::new(ga.center, ga.radius, ga.start, ga.start + t1)), a.center.z)); } if t2 < ga.sweep() - 1e-12 { - v.push(arc(&Arc::new(ga.center, ga.radius, ga.start + t2, ga.end))); + v.push(with_z(arc(&Arc::new(ga.center, ga.radius, ga.start + t2, ga.end)), a.center.z)); } v } @@ -1510,17 +1521,17 @@ pub(crate) fn break_entity(s: &mut Session, h: Handle, p1: Vec2, p2: Vec2) -> Re // vertices, goes; what is left runs from the second point round to the first. let doubled: Vec = segs.iter().chain(segs.iter()).copied().collect(); let end = if t1 > t2 { t1 } else { t1 + total }; - vec![lwpoly(sub_polyline(&doubled, t2, end), false)] + vec![with_z(lwpoly(sub_polyline(&doubled, t2, end), false), p.elevation)] } else { if t1 > t2 { std::mem::swap(&mut t1, &mut t2); } let mut v = Vec::new(); if t1 > 1e-12 { - v.push(lwpoly(sub_polyline(&segs, 0.0, t1), false)); + v.push(with_z(lwpoly(sub_polyline(&segs, 0.0, t1), false), p.elevation)); } if t2 < total - 1e-12 { - v.push(lwpoly(sub_polyline(&segs, t2, total), false)); + v.push(with_z(lwpoly(sub_polyline(&segs, t2, total), false), p.elevation)); } v } diff --git a/crates/engine/tests/break_keeps_z.rs b/crates/engine/tests/break_keeps_z.rs new file mode 100644 index 000000000..f064c4770 --- /dev/null +++ b/crates/engine/tests/break_keeps_z.rs @@ -0,0 +1,73 @@ +//! BREAK and BREAKATPOINT keep the Z / elevation of the pieces they leave. + +use cadcraft_engine::Session; +use cadcraft_engine::doc::{Arc, Common, Entity, EntityKind, Handle, Line, LwPolyline}; +use cadcraft_engine::geom::{PolyVertex, Vec2, Vec3}; +use serde_json::{Value, json}; + +fn add(s: &mut Session, kind: EntityKind) -> String { + let space = s.space(); + let d = s.doc_mut().unwrap(); + let handle = d.new_handle(); + d.space_mut(&space).unwrap().push(Entity { handle, common: Common::default(), kind }); + handle.hex() +} + +fn pieces(s: &Session, r: &Value) -> Vec { + let d = s.doc().unwrap(); + r["handles"].as_array().unwrap().iter().map(|h| d.entity(Handle::parse_hex(h.as_str().unwrap()).unwrap()).unwrap().kind.clone()).collect() +} + +#[test] +fn break_keeps_line_z() { + let mut s = Session::new(); + let h = add(&mut s, EntityKind::Line(Line { a: Vec3::new(0.0, 0.0, 5.0), b: Vec3::new(10.0, 0.0, 5.0) })); + let r = s.execute("break", &json!({"handle": h, "p1": [4, 0], "p2": [6, 0]})).unwrap(); + let ps = pieces(&s, &r); + assert_eq!(ps.len(), 2); + for p in ps { + let EntityKind::Line(l) = p else { panic!("not a line: {p:?}") }; + assert_eq!((l.a.z, l.b.z), (5.0, 5.0)); + } +} + +#[test] +fn breakatpoint_keeps_line_z() { + let mut s = Session::new(); + let h = add(&mut s, EntityKind::Line(Line { a: Vec3::new(0.0, 0.0, 5.0), b: Vec3::new(10.0, 0.0, 5.0) })); + let r = s.execute("breakatpoint", &json!({"handle": h, "at": [4, 0]})).unwrap(); + let ps = pieces(&s, &r); + assert_eq!(ps.len(), 2); + for p in ps { + let EntityKind::Line(l) = p else { panic!("not a line: {p:?}") }; + assert_eq!((l.a.z, l.b.z), (5.0, 5.0)); + } +} + +#[test] +fn break_keeps_polyline_elevation() { + let mut s = Session::new(); + let vertices = vec![PolyVertex::new(Vec2::new(0.0, 0.0)), PolyVertex::new(Vec2::new(10.0, 0.0))]; + let h = add(&mut s, EntityKind::LwPolyline(LwPolyline { vertices, closed: false, const_width: 0.0, elevation: 7.0, plinegen: false })); + let r = s.execute("break", &json!({"handle": h, "p1": [4, 0], "p2": [6, 0]})).unwrap(); + let ps = pieces(&s, &r); + assert_eq!(ps.len(), 2); + for p in ps { + let EntityKind::LwPolyline(pl) = p else { panic!("not a polyline: {p:?}") }; + assert_eq!(pl.elevation, 7.0); + } +} + +#[test] +fn break_keeps_arc_centre_z() { + let mut s = Session::new(); + let h = add(&mut s, EntityKind::Arc(Arc { center: Vec3::new(0.0, 0.0, 3.0), radius: 5.0, start: 0.0, end: std::f64::consts::PI })); + let p1 = [5.0 * 0.5f64.sqrt(), 5.0 * 0.5f64.sqrt()]; + let r = s.execute("break", &json!({"handle": h, "p1": p1, "p2": [0, 5]})).unwrap(); + let ps = pieces(&s, &r); + assert_eq!(ps.len(), 2); + for p in ps { + let EntityKind::Arc(a) = p else { panic!("not an arc: {p:?}") }; + assert_eq!(a.center.z, 3.0); + } +}