* WIP deep rename (not for push) * WIP2 * WIP3 * WIP4 wxs fix * rustfmt * Rename the remaining identifiers in merged crash-report code Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
66 lines
3.2 KiB
Rust
66 lines
3.2 KiB
Rust
//! Commands that take a plane accept construction planes by name or id, like the origin planes
|
|
//! (user report: `solid.mirror` refused a construction plane although its docs list plane
|
|
//! names).
|
|
|
|
use serde_json::json;
|
|
|
|
use super::run;
|
|
use crate::*;
|
|
|
|
fn min_x(s: &Session, body: &str) -> f64 {
|
|
let b = s.model.state().body(body).map(|b| b.body.clone()).unwrap();
|
|
solidcraft_kernel::measure(&b).unwrap().bbox.min.x
|
|
}
|
|
|
|
#[test]
|
|
fn mirror_across_a_construction_plane_follows_it() {
|
|
let mut s = Session::default();
|
|
run(&mut s, "parameters.change", json!({"name": "w", "expression": "100 mm"}));
|
|
let pl = run(&mut s, "construct.plane.offset", json!({"base": "YZ", "offset": "w / 2", "name": "Mid"}));
|
|
run(&mut s, "solid.box", json!({"length": 10, "width": 10, "height": 10, "body_name": "Ledger"}));
|
|
let before = s.model.state().bodies.len();
|
|
// By name: the copy lands at x = 90…100.
|
|
run(&mut s, "solid.mirror", json!({"bodies": ["Ledger"], "plane": "Mid"}));
|
|
let st = s.model.state();
|
|
assert_eq!(st.bodies.len(), before + 1);
|
|
let copy = st.bodies.last().map(|b| b.name.clone()).unwrap();
|
|
assert!((min_x(&s, ©) - 90.0).abs() < 1e-6, "{}", min_x(&s, ©));
|
|
// Parametric: moving the plane moves the copy.
|
|
run(&mut s, "parameters.change", json!({"name": "w", "expression": "60 mm"}));
|
|
assert!((min_x(&s, ©) - 50.0).abs() < 1e-6, "{}", min_x(&s, ©));
|
|
// By id, and case-insensitively by name.
|
|
let id = pl["feature"].as_u64().unwrap();
|
|
run(&mut s, "solid.mirror", json!({"bodies": ["Ledger"], "plane": id.to_string()}));
|
|
run(&mut s, "solid.mirror", json!({"bodies": ["Ledger"], "plane": "mid"}));
|
|
let st = s.model.state();
|
|
assert_eq!(st.bodies.len(), before + 3);
|
|
for b in st.bodies.iter().skip(before) {
|
|
assert!((min_x(&s, &b.name) - 50.0).abs() < 1e-6, "{}", b.name);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn unknown_mirror_planes_are_refused_with_the_choices() {
|
|
let mut s = Session::default();
|
|
run(&mut s, "solid.box", json!({"length": 10, "width": 10, "height": 10, "body_name": "B"}));
|
|
let e = s.execute("solid.mirror", &json!({"bodies": ["B"], "plane": "Nope"})).unwrap_err().to_string();
|
|
assert!(e.contains("Nope"), "{e}");
|
|
let e = s.execute("solid.mirror", &json!({"bodies": ["B"], "plane": 5})).unwrap_err().to_string();
|
|
assert!(e.contains("construction plane"), "{e}");
|
|
// A plane object with an unreadable origin is refused, not put at the world origin.
|
|
assert!(s.execute("solid.mirror", &json!({"bodies": ["B"], "plane": {"origin": ["x", 0, 0], "normal": [1, 0, 0]}})).is_err());
|
|
// A feature that is not a plane is no plane.
|
|
assert!(s.execute("solid.mirror", &json!({"bodies": ["B"], "plane": "Box1"})).is_err());
|
|
assert_eq!(s.model.state().bodies.len(), 1);
|
|
}
|
|
|
|
/// Section Analysis cuts along a construction plane too.
|
|
#[test]
|
|
fn section_by_a_construction_plane() {
|
|
let mut s = Session::default();
|
|
run(&mut s, "construct.plane.offset", json!({"base": "XY", "offset": 7, "name": "Cut"}));
|
|
let r = run(&mut s, "inspect.section", json!({"plane": "Cut"}));
|
|
assert_eq!(r["section"]["origin"][2].as_f64(), Some(7.0), "{r}");
|
|
assert!(s.execute("inspect.section", &json!({"plane": "Nope"})).is_err());
|
|
}
|