Keep Z and elevation of the pieces left by BREAK (#1270)

Found while testing crates/engine/src/cmd/modify.rs:1457 (break_entity; helpers `line()`/`arc()`/`lwpoly()` in crates/engine/src/cmd/helpers.rs:10-23)

Co-authored-by: Claude Haiku 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
This commit is contained in:
authored and GitHub committed 2026-10-19 02:55:28 -04:00
1 parent b0ed57f1c7
commit 006f71f650
2 files changed
+92 -8

No files matched your search

+19 -8
View File
@@ -1437,6 +1437,16 @@ fn run_explode(s: &mut Session, p: &Value) -> Result<Value> {
Ok(json!({ "handles": r.iter().map(|h| h.hex()).collect::<Vec<_>>() }))
}
/// 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<Vec<Handle>> {
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<Segment> = 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
}
+73
View File
@@ -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<EntityKind> {
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);
}
}