* Add fast design containers with lossless deduplicated payloads * Keep display caching opt-in to avoid default save overhead
99 lines
4.5 KiB
Rust
99 lines
4.5 KiB
Rust
//! The XZ origin plane (#78): sketch x = +X, y = −Z, normal +Y, so a positive extrude from XZ
|
||
//! (or from a plane offset from it) goes +Y. Designs saved before format 6 used y = +Z and
|
||
//! normal −Y; opening one keeps its geometry exactly where it was saved.
|
||
|
||
use serde_json::{Value, json};
|
||
|
||
use super::run;
|
||
use crate::*;
|
||
|
||
/// A 10 × 5 rectangle on XZ extruded 3, and a 4 × 4 square on a plane offset 7 from XZ
|
||
/// extruded 2.
|
||
fn build(s: &mut Session) {
|
||
run(s, "sketch.create", json!({"plane": "XZ", "name": "Base"}));
|
||
run(s, "sketch.line", json!({"points": [[0, 0], [10, 0], [10, 5], [0, 5]], "closed": true}));
|
||
run(s, "sketch.finish", json!({}));
|
||
run(s, "solid.extrude", json!({"sketch": "Base", "distance": 3, "body_name": "Slab"}));
|
||
run(s, "construct.plane.offset", json!({"base": "XZ", "offset": "7 mm", "name": "Shelf"}));
|
||
run(s, "sketch.create", json!({"plane": "Shelf", "name": "Top"}));
|
||
run(s, "sketch.line", json!({"points": [[20, 1], [24, 1], [24, 5], [20, 5]], "closed": true}));
|
||
run(s, "sketch.finish", json!({}));
|
||
run(s, "solid.extrude", json!({"sketch": "Top", "distance": 2, "body_name": "Post", "operation": "new"}));
|
||
}
|
||
|
||
/// Volume and world bounding box (min, max) of each body.
|
||
fn bodies(s: &mut Session) -> Vec<(String, f64, [f64; 3], [f64; 3])> {
|
||
let m = run(s, "inspect.measure", json!({}));
|
||
let v3 = |v: &Value| -> [f64; 3] { [0, 1, 2].map(|i| v[i].as_f64().unwrap()) };
|
||
m["bodies"]
|
||
.as_array()
|
||
.unwrap()
|
||
.iter()
|
||
.map(|b| (b["name"].as_str().unwrap().to_string(), b["volume_mm3"].as_f64().unwrap(), v3(&b["bbox"]["min"]), v3(&b["bbox"]["max"])))
|
||
.collect()
|
||
}
|
||
|
||
fn close(a: [f64; 3], b: [f64; 3]) -> bool {
|
||
a.iter().zip(b).all(|(x, y)| (x - y).abs() < 1e-6)
|
||
}
|
||
|
||
fn expect(got: &[(String, f64, [f64; 3], [f64; 3])], want: &[(&str, f64, [f64; 3], [f64; 3])]) {
|
||
assert_eq!(got.len(), want.len(), "{got:?}");
|
||
for ((name, vol, min, max), (wname, wvol, wmin, wmax)) in got.iter().zip(want) {
|
||
assert_eq!(name, wname);
|
||
assert!((vol - wvol).abs() < 1e-6 * wvol, "{name}: volume {vol} vs {wvol}");
|
||
assert!(close(*min, *wmin) && close(*max, *wmax), "{name}: bbox {min:?}..{max:?}, expected {wmin:?}..{wmax:?}");
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn positive_extrude_from_xz_goes_plus_y() {
|
||
let mut s = Session::default();
|
||
build(&mut s);
|
||
expect(
|
||
&bodies(&mut s),
|
||
&[
|
||
// Sketch y = −Z; extrude along +Y.
|
||
("Slab", 150.0, [0.0, 0.0, -5.0], [10.0, 3.0, 0.0]),
|
||
// The offset plane sits at Y = +7 and its extrude continues to +Y.
|
||
("Post", 32.0, [20.0, 7.0, -5.0], [24.0, 9.0, -1.0]),
|
||
],
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn format_5_xz_design_opens_where_it_was_saved() {
|
||
let dir = std::env::temp_dir().join(format!("solidcraft-xz78-{}", std::process::id()));
|
||
std::fs::create_dir_all(&dir).unwrap();
|
||
let path = dir.join("old-xz.solidcraft");
|
||
let mut s = Session::default();
|
||
build(&mut s);
|
||
// This fixture edits the historical JSON metadata directly; request JSON explicitly.
|
||
run(&mut s, "file.save_as", json!({"path": path.to_string_lossy(), "format": "json"}));
|
||
// Stamp the file as format 5: its XZ references then mean the old frame (sketch y = +Z,
|
||
// normal −Y), which is what a build before format 6 wrote and evaluated.
|
||
let mut v: Value = serde_json::from_str(&std::fs::read_to_string(&path).unwrap()).unwrap();
|
||
assert!(v.to_string().contains("\"XZ\""), "the saved design names the XZ plane");
|
||
v["format"] = Value::from("solidcraft/5");
|
||
std::fs::write(&path, v.to_string()).unwrap();
|
||
|
||
let mut old = Session::default();
|
||
run(&mut old, "doc.open", json!({"path": path.to_string_lossy()}));
|
||
let want = [
|
||
// The old frame: the same sketch coordinates land at +Z and the extrude goes −Y.
|
||
("Slab", 150.0, [0.0, -3.0, 0.0], [10.0, 0.0, 5.0]),
|
||
("Post", 32.0, [20.0, -9.0, 1.0], [24.0, -7.0, 5.0]),
|
||
];
|
||
expect(&bodies(&mut old), &want);
|
||
|
||
// Saving the migrated design writes the current format and keeps the geometry.
|
||
let resaved = dir.join("resaved.solidcraft");
|
||
run(&mut old, "file.save_as", json!({"path": resaved.to_string_lossy(), "format": "json"}));
|
||
let text = std::fs::read_to_string(&resaved).unwrap();
|
||
assert!(text.contains(&format!("\"solidcraft/{}\"", solidcraft_doc::format::FORMAT)), "resaved at the current format");
|
||
let mut again = Session::default();
|
||
run(&mut again, "doc.open", json!({"path": resaved.to_string_lossy()}));
|
||
expect(&bodies(&mut again), &want);
|
||
let _ = std::fs::remove_dir_all(&dir);
|
||
}
|