Rename SolveCraft to SolidCraft everywhere, with compatibility shims (#389)

* 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>
This commit is contained in:
bherbruckandClaude Opus 5.5 authored and GitHub committed 2026-10-12 17:17:01 -04:00
1 parent 27415e4dd7
commit 296683229d
464 files changed
+3408 -2956

No files matched your search

+67 -67
View File
@@ -55,13 +55,13 @@ fn join_extrude_from_a_chamfered_top_face() {
run(&mut s, "solid.chamfer", json!({"edges": [[10, 0, 20]], "distance": "bevel"}));
let base = volume(&mut s);
let id = run(&mut s, "sketch.create", json!({"plane": {"face": [0, 0, 20]}}))["sketch"].as_u64().unwrap();
let solvecraft_doc::FeatureKind::Sketch { plane: solvecraft_doc::PlaneRef::Face { anchor: Some(anchor), .. }, sketch } =
let solidcraft_doc::FeatureKind::Sketch { plane: solidcraft_doc::PlaneRef::Face { anchor: Some(anchor), .. }, sketch } =
&s.doc.feature(id).unwrap().kind
else {
panic!("the chamfered cap retains its exact original construction support");
};
assert_eq!(anchor.target.origin_feature, source);
assert!(matches!(anchor.target.certificate, Some(solvecraft_doc::config::FaceCertificate::ExtrudeCap { high: true, .. })));
assert!(matches!(anchor.target.certificate, Some(solidcraft_doc::config::FaceCertificate::ExtrudeCap { high: true, .. })));
assert!(!sketch.links.is_empty());
assert!(sketch.links.iter().all(|link| link.auto_face && !link.lost));
// The default linked cap outline is radius8; no explicit fixed plane or
@@ -74,7 +74,7 @@ fn join_extrude_from_a_chamfered_top_face() {
run(&mut s, "parameters.change", json!({"name":"height","expression":format!("{height} mm")}));
run(&mut s, "parameters.change", json!({"name":"bevel","expression":format!("{bevel} mm")}));
let expected = PI * (100. * height - (10. * bevel * bevel - bevel * bevel * bevel / 3.) + (10. - bevel) * (10. - bevel) * 5.);
let loaded = solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap();
let loaded = solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap();
let mut cold = Session::new(loaded);
let warm_volume = volume(&mut s);
let cold_volume = volume(&mut cold);
@@ -88,10 +88,10 @@ fn join_extrude_from_a_chamfered_top_face() {
assert!((solved.plane.origin.z - height).abs() < 1e-8);
assert!(solved.sketch.links.iter().all(|link| link.auto_face && !link.lost));
let body = &state.bodies[0];
assert!(body.body.contains(solvecraft_geom::Vec3::new(0., 0., height + 4.)));
assert!(!body.body.contains(solvecraft_geom::Vec3::new(0., 0., height + 6.)));
assert!(body.body.contains(solvecraft_geom::Vec3::new(10. - bevel - 0.5, 0., height + 4.)));
assert!(!body.body.contains(solvecraft_geom::Vec3::new(10. - bevel + 0.5, 0., height + 4.)));
assert!(body.body.contains(solidcraft_geom::Vec3::new(0., 0., height + 4.)));
assert!(!body.body.contains(solidcraft_geom::Vec3::new(0., 0., height + 6.)));
assert!(body.body.contains(solidcraft_geom::Vec3::new(10. - bevel - 0.5, 0., height + 4.)));
assert!(!body.body.contains(solidcraft_geom::Vec3::new(10. - bevel + 0.5, 0., height + 4.)));
}
}
}
@@ -256,7 +256,7 @@ fn box_fillet_cut_part() {
let l: Vec<String> = r["curves"].as_array().unwrap().iter().map(|v| v.as_str().unwrap().to_string()).collect();
let corner = s.doc.sketch(s.active_sketch.unwrap()).unwrap().curves[0].clone();
let start = match corner.kind {
solvecraft_sketch::CurveKind::Line { a, .. } => s.doc.sketch(s.active_sketch.unwrap()).unwrap().points[a].id.clone(),
solidcraft_sketch::CurveKind::Line { a, .. } => s.doc.sketch(s.active_sketch.unwrap()).unwrap().points[a].id.clone(),
_ => panic!(),
};
run(&mut s, "sketch.constraint.coincident", json!({"a": start, "b": "origin"}));
@@ -293,7 +293,7 @@ fn box_fillet_cut_part() {
assert!(rel(volume(&mut s), expect2) < 5e-4);
// Export STL and STEP.
let dir = std::env::temp_dir().join(format!("solvecraft-test-{}", std::process::id()));
let dir = std::env::temp_dir().join(format!("solidcraft-test-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let stl = dir.join("part.stl");
run(&mut s, "file.export", json!({"path": stl.to_string_lossy()}));
@@ -301,7 +301,7 @@ fn box_fillet_cut_part() {
let step = dir.join("part.step");
run(&mut s, "file.export", json!({"path": step.to_string_lossy()}));
assert!(std::fs::read_to_string(&step).unwrap().contains("ISO-10303-21"));
let design = dir.join("part.solvecraft");
let design = dir.join("part.solidcraft");
run(&mut s, "file.save_as", json!({"path": design.to_string_lossy()}));
let mut s2 = Session::default();
run(&mut s2, "doc.open", json!({"path": design.to_string_lossy()}));
@@ -610,7 +610,7 @@ fn sample_edges_chain_and_faces() {
let chain = m.tangent_chain(e, 2f64.to_radians());
// The plate's top outline: 4 lines and 4 corner arcs (a boolean may leave an edge twice,
// once per face; count distinct ones).
let mut mids: Vec<solvecraft_geom::Vec3> = Vec::new();
let mut mids: Vec<solidcraft_geom::Vec3> = Vec::new();
for i in &chain {
let p = &m.edges[*i];
let mid = p[p.len() / 2];
@@ -707,7 +707,7 @@ fn timeline_edits_reresolve_references() {
/// A 40 × 30 × 20 box exported to a STEP file in a fresh temp directory.
fn step_box_file(tag: &str) -> std::path::PathBuf {
let dir = std::env::temp_dir().join(format!("solvecraft-step-{tag}-{}", std::process::id()));
let dir = std::env::temp_dir().join(format!("solidcraft-step-{tag}-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let mut s = Session::default();
run(&mut s, "solid.box", json!({"length": 40, "width": 30, "height": 20}));
@@ -756,7 +756,7 @@ fn step_open_then_model_on_it() {
// The design saves and reopens without the STEP file.
let before = volume(&mut s);
let design = p.with_extension("solvecraft");
let design = p.with_extension("solidcraft");
run(&mut s, "file.save_as", json!({"path": design.to_string_lossy()}));
std::fs::remove_file(&p).unwrap();
let mut s2 = Session::default();
@@ -802,8 +802,8 @@ fn step_insert_shell_and_errors() {
assert_eq!(*d.doc, *before);
}
fn body_volume(st: &solvecraft_doc::ModelState) -> f64 {
st.bodies.iter().map(|b| solvecraft_kernel::measure(&b.body).unwrap().volume).sum()
fn body_volume(st: &solidcraft_doc::ModelState) -> f64 {
st.bodies.iter().map(|b| solidcraft_kernel::measure(&b.body).unwrap().volume).sum()
}
/// Previews evaluate on a scratch copy: the document, undo history and model are untouched.
@@ -1048,7 +1048,7 @@ fn extrude_a_body_face() {
/// solid features.
#[test]
fn threemf_export_open_insert() {
let dir = std::env::temp_dir().join(format!("solvecraft-3mf-{}", std::process::id()));
let dir = std::env::temp_dir().join(format!("solidcraft-3mf-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let mut s = Session::default();
run(&mut s, "solid.box", json!({"length": 40, "width": 30, "height": 20}));
@@ -1124,12 +1124,12 @@ fn holes_at_sketch_points_threads_and_components() {
assert_eq!(r["components"].as_array().map(|a| a.len()), Some(1));
// Saved and loaded with the components.
let text = s.doc.to_json();
let back = solvecraft_doc::Document::from_json(&text).unwrap();
let back = solidcraft_doc::Document::from_json(&text).unwrap();
assert_eq!(back.components.len(), 2);
let assigned = r["components"][0]["component"].as_u64().unwrap();
let transfer = r["components"][0]["feature"].as_u64().unwrap();
assert!(
matches!(&back.feature(transfer).unwrap().kind, solvecraft_doc::FeatureKind::BodyTransfer { bodies, to, .. } if bodies == &["Shaft"] && *to == assigned)
matches!(&back.feature(transfer).unwrap().kind, solidcraft_doc::FeatureKind::BodyTransfer { bodies, to, .. } if bodies == &["Shaft"] && *to == assigned)
);
let reopened = Session::new(back);
assert_eq!(reopened.model.state().body("Shaft").unwrap().component, assigned);
@@ -1284,11 +1284,11 @@ fn components_occurrences_and_world_placement() {
assert_eq!(s.world_state().body("Base").unwrap().component, 0);
run(&mut s, "timeline.roll_to", json!({}));
// Saved and loaded; older designs get occurrences.
let back = solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap();
let back = solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap();
assert_eq!(back.occurrences.len(), s.doc.occurrences.len());
let mut old: serde_json::Value = serde_json::from_str(&s.doc.to_json()).unwrap();
old.as_object_mut().unwrap().remove("occurrences");
let migrated = solvecraft_doc::Document::from_json(&old.to_string()).unwrap();
let migrated = solidcraft_doc::Document::from_json(&old.to_string()).unwrap();
assert_eq!(migrated.occurrences.len(), migrated.components.len());
}
@@ -1325,7 +1325,7 @@ fn feature_inputs_are_parameters() {
assert!(rel(volume(&mut s), 24.0 * 12.0 * 25.4) < 1e-9);
assert!(s.execute("parameters.import", &json!({"text": "name,expression\nw,1/"})).is_err());
// Saved and reloaded, the names stay.
let back = solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap();
let back = solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap();
assert_eq!(back.features[0].param_names, s.doc.features[0].param_names);
}
@@ -1345,15 +1345,15 @@ fn rolling_back_and_forward_leaves_no_projection_warnings() {
}
/// Export STEP, validate the file, read it back: no warnings, same volume, area and faces.
fn step_round_trip(s: &mut Session, tag: &str) -> solvecraft_kernel::StepImport {
let p = std::env::temp_dir().join(format!("solvecraft-steprt-{tag}-{}.step", std::process::id()));
fn step_round_trip(s: &mut Session, tag: &str) -> solidcraft_kernel::StepImport {
let p = std::env::temp_dir().join(format!("solidcraft-steprt-{tag}-{}.step", std::process::id()));
run(s, "file.export", json!({"path": p.to_string_lossy()}));
let text = std::fs::read_to_string(&p).unwrap();
let _ = std::fs::remove_file(&p);
solvecraft_kernel::step_validate(&text).unwrap();
assert_eq!(solvecraft_kernel::step_orientation_errors(&text).unwrap(), Vec::<String>::new());
solidcraft_kernel::step_validate(&text).unwrap();
assert_eq!(solidcraft_kernel::step_orientation_errors(&text).unwrap(), Vec::<String>::new());
assert!(text.contains("AP242_MANAGED_MODEL_BASED_3D_ENGINEERING_MIM_LF"));
let imp = solvecraft_kernel::step_import(&text).unwrap();
let imp = solidcraft_kernel::step_import(&text).unwrap();
assert!(imp.warnings.is_empty(), "{:?}", imp.warnings);
imp
}
@@ -1367,7 +1367,7 @@ fn sample_plate_step_round_trip() {
assert_eq!(imp.bodies.len(), st.bodies.len());
for (a, b) in st.bodies.iter().zip(&imp.bodies) {
assert_eq!(a.name, b.name);
let (ma, mb) = (solvecraft_kernel::measure(&a.body).unwrap(), solvecraft_kernel::measure(&b.body).unwrap());
let (ma, mb) = (solidcraft_kernel::measure(&a.body).unwrap(), solidcraft_kernel::measure(&b.body).unwrap());
assert!(rel(mb.volume, ma.volume) < 1e-4, "{} vs {}", mb.volume, ma.volume);
assert!(rel(mb.area, ma.area) < 1e-4, "{} vs {}", mb.area, ma.area);
assert!(b.file_faces <= a.body.face_count() && b.file_faces >= ma.merged.faces.min(a.body.face_count()));
@@ -1389,13 +1389,13 @@ fn material_and_appearance_colours_export_to_step() {
assert!(s.execute("appearance.assign", &json!({"bodies": ["RedAbs"], "color": "#12"})).is_err());
let rgb = |c: [u8; 3]| c.map(|x| x as f32 / 255.0);
let want =
[("BrassBox", Some(rgb(solvecraft_doc::material_color("Brass").unwrap()))), ("RedAbs", Some(rgb([0xd0, 0x1c, 0x1c]))), ("Plain", None)];
[("BrassBox", Some(rgb(solidcraft_doc::material_color("Brass").unwrap()))), ("RedAbs", Some(rgb([0xd0, 0x1c, 0x1c]))), ("Plain", None)];
let same = |a: Option<[f32; 3]>, b: Option<[f32; 3]>| match (a, b) {
(Some(a), Some(b)) => a.iter().zip(b).all(|(x, y)| (x - y).abs() < 1e-3),
(None, None) => true,
_ => false,
};
let check = |imp: &solvecraft_kernel::StepImport| {
let check = |imp: &solidcraft_kernel::StepImport| {
for (name, c) in want {
let b = imp.bodies.iter().find(|b| b.name == name).unwrap_or_else(|| panic!("no body {name}"));
assert!(same(b.body.color(), c), "{name}: {:?} vs {c:?}", b.body.color());
@@ -1403,7 +1403,7 @@ fn material_and_appearance_colours_export_to_step() {
};
check(&step_round_trip(&mut s, "colours"));
// Open the export and export again: the file's colours are kept.
let p = std::env::temp_dir().join(format!("solvecraft-colours-{}.step", std::process::id()));
let p = std::env::temp_dir().join(format!("solidcraft-colours-{}.step", std::process::id()));
run(&mut s, "file.export", json!({"path": p.to_string_lossy()}));
let mut s2 = Session::default();
run(&mut s2, "doc.open", json!({"path": p.to_string_lossy()}));
@@ -1429,7 +1429,7 @@ fn face_colours_and_opacity_export() {
assert_eq!(paint.faces[0].face, top);
assert!((paint.faces[0].color[0] - 1.0).abs() < 1e-3 && paint.faces[0].color[1].abs() < 1e-3, "{paint:?}");
// Opened again, the face keeps its colour (shown and exported).
let p = std::env::temp_dir().join(format!("solvecraft-facecol-{}.step", std::process::id()));
let p = std::env::temp_dir().join(format!("solidcraft-facecol-{}.step", std::process::id()));
run(&mut s, "file.export", json!({"path": p.to_string_lossy()}));
let mut s2 = Session::default();
run(&mut s2, "doc.open", json!({"path": p.to_string_lossy()}));
@@ -1448,8 +1448,8 @@ fn iges_export_and_open() {
run(&mut s, "solid.box", json!({"length": 40, "width": 30, "height": 5, "body_name": "Plate"}));
run(&mut s, "solid.cylinder", json!({"radius": 4, "height": 20, "base": [10, 10, 5], "body_name": "Pin"}));
run(&mut s, "appearance.assign", json!({"bodies": ["Pin"], "color": "#ff0000"}));
let want: Vec<f64> = s.world_state().bodies.iter().map(|b| solvecraft_kernel::measure(&b.body).unwrap().volume).collect();
let p = std::env::temp_dir().join(format!("solvecraft-iges-{}.igs", std::process::id()));
let want: Vec<f64> = s.world_state().bodies.iter().map(|b| solidcraft_kernel::measure(&b.body).unwrap().volume).collect();
let p = std::env::temp_dir().join(format!("solidcraft-iges-{}.igs", std::process::id()));
run(&mut s, "file.export", json!({"path": p.to_string_lossy()}));
let mut s2 = Session::default();
run(&mut s2, "doc.open", json!({"path": p.to_string_lossy()}));
@@ -1458,7 +1458,7 @@ fn iges_export_and_open() {
let names: Vec<&str> = st.bodies.iter().map(|b| b.name.as_str()).collect();
assert_eq!(names, ["Plate", "Pin"]);
for (b, v) in st.bodies.iter().zip(&want) {
assert!(rel(solvecraft_kernel::measure(&b.body).unwrap().volume, *v) < 1e-4);
assert!(rel(solidcraft_kernel::measure(&b.body).unwrap().volume, *v) < 1e-4);
}
assert_eq!(st.bodies[1].body.color(), Some([1.0, 0.0, 0.0]));
}
@@ -1473,7 +1473,7 @@ fn split_body_by_a_face() {
run(&mut s, "solid.split_body", json!({"body": "Block", "tool": {"body": "Tool", "point": [9, 10, 10]}}));
let st = s.world_state();
let mut v: Vec<f64> =
st.bodies.iter().filter(|b| b.name.starts_with("Block")).map(|b| solvecraft_kernel::measure(&b.body).unwrap().volume).collect();
st.bodies.iter().filter(|b| b.name.starts_with("Block")).map(|b| solidcraft_kernel::measure(&b.body).unwrap().volume).collect();
v.sort_by(f64::total_cmp);
let core = PI * 16.0 * 30.0;
assert!(v.len() == 2 && rel(v[0], core) < 1e-3 && rel(v[1], 6000.0 - core) < 1e-3, "{v:?}");
@@ -1488,12 +1488,12 @@ fn chord_and_variable_fillets() {
run(&mut s, "solid.box", json!({"length": 10, "width": 20, "height": 30, "body_name": "B"}));
run(&mut s, "solid.fillet", json!({"edges": [[10, 10, 30]], "radius": 4, "type": "chord"}));
let r = 4.0 / 2f64.sqrt();
let v = solvecraft_kernel::measure(&s.world_state().body("B").unwrap().body).unwrap().volume;
let v = solidcraft_kernel::measure(&s.world_state().body("B").unwrap().body).unwrap().volume;
assert!(rel(v, 6000.0 - (1.0 - PI / 4.0) * r * r * 20.0) < 1e-4, "{v}");
let mut s = Session::default();
run(&mut s, "solid.box", json!({"length": 10, "width": 20, "height": 30, "body_name": "B"}));
run(&mut s, "solid.fillet", json!({"edges": [[10, 10, 30]], "radius": 2, "type": "variable", "radius2": 5, "start": [10, 0, 30]}));
let v = solvecraft_kernel::measure(&s.world_state().body("B").unwrap().body).unwrap().volume;
let v = solidcraft_kernel::measure(&s.world_state().body("B").unwrap().body).unwrap().volume;
let removed = (1.0 - PI / 4.0) * 20.0 * (4.0 + 10.0 + 25.0) / 3.0;
assert!(rel(v, 6000.0 - removed) < 1e-4, "{v}");
assert!(s.execute("solid.fillet", &json!({"edges": [[10, 10, 30]], "radius": 2, "type": "variable"})).is_err());
@@ -1506,7 +1506,7 @@ fn chord_and_variable_fillets() {
"solid.fillet",
json!({"edges": [[10, 10, 30]], "radius": 2, "type": "variable", "radius2": 2, "start": [10, 0, 30], "mid": [[0.5, "waist"]]}),
);
let vol = |s: &Session| solvecraft_kernel::measure(&s.world_state().body("B").unwrap().body).unwrap().volume;
let vol = |s: &Session| solidcraft_kernel::measure(&s.world_state().body("B").unwrap().body).unwrap().volume;
let removed = 2.0 * (1.0 - PI / 4.0) * 10.0 * (4.0 + 8.0 + 16.0) / 3.0;
assert!(rel(vol(&s), 6000.0 - removed) < 1e-4, "{}", vol(&s));
// The middle radius follows its parameter.
@@ -1530,10 +1530,10 @@ fn components_export_as_step_assembly() {
run(&mut s, "component.create", json!({"name": "Pin"}));
run(&mut s, "solid.cylinder", json!({"radius": 2, "height": 10, "base": [5, 5, 5]}));
run(&mut s, "occurrence.copy", json!({"component": "Pin", "translate": [20, 0, 0]}));
let want: f64 = s.world_state().bodies.iter().map(|b| solvecraft_kernel::measure(&b.body).unwrap().volume).sum();
let want: f64 = s.world_state().bodies.iter().map(|b| solidcraft_kernel::measure(&b.body).unwrap().volume).sum();
let imp = step_round_trip(&mut s, "asm");
assert_eq!(imp.bodies.len(), 3, "{:?}", imp.bodies.iter().map(|b| &b.name).collect::<Vec<_>>());
let got: f64 = imp.bodies.iter().map(|b| solvecraft_kernel::measure(&b.body).unwrap().volume).sum();
let got: f64 = imp.bodies.iter().map(|b| solidcraft_kernel::measure(&b.body).unwrap().volume).sum();
assert!(rel(got, want) < 1e-4, "{got} vs {want}");
let root = &imp.tree[0];
assert_eq!(root.children.len(), 2);
@@ -1543,7 +1543,7 @@ fn components_export_as_step_assembly() {
.bodies
.iter()
.filter(|b| b.path.last().is_some_and(|p| p == "Pin"))
.map(|b| solvecraft_kernel::measure(&b.body).unwrap().centroid.x)
.map(|b| solidcraft_kernel::measure(&b.body).unwrap().centroid.x)
.collect();
assert_eq!(xs.len(), 2);
assert!(((xs[0] - xs[1]).abs() - 20.0).abs() < 1e-6, "{xs:?}");
@@ -1607,7 +1607,7 @@ fn appearances_by_face_body_and_component() {
assert_eq!(look(&mut s, "Shell")["appearance"]["name"], "Brass");
// Saved and opened again.
run(&mut s, "appearance.assign", json!({"faces": [[5, 5, 10]], "color": "#00ff00"}));
let back = solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap();
let back = solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap();
assert_eq!(back.appearances, s.doc.appearances);
assert!(s.execute("appearance.assign", &json!({"bodies": ["Shell"], "appearance": "Unobtainium"})).is_err());
assert!(s.execute("appearance.assign", &json!({"color": "#ffffff"})).is_err(), "no target");
@@ -1627,16 +1627,16 @@ fn cached_evaluation_matches_from_scratch() {
);
run(&mut s, "solid.box", json!({"corner": [-50, 0, 0], "length": 10, "width": 10, "height": 10, "body_name": "Side"}));
let same = |s: &Session, what: &str| {
let mut fresh = solvecraft_doc::Model::new();
let mut fresh = solidcraft_doc::Model::new();
fresh.no_cache = true;
fresh.evaluate(&s.doc);
let errs = |m: &solvecraft_doc::Model| m.results.iter().map(|r| (r.id, r.error.clone(), r.skipped)).collect::<Vec<_>>();
let errs = |m: &solidcraft_doc::Model| m.results.iter().map(|r| (r.id, r.error.clone(), r.skipped)).collect::<Vec<_>>();
assert_eq!(errs(&s.model), errs(&fresh), "{what}: feature results");
let shape = |st: &solvecraft_doc::ModelState| {
let shape = |st: &solidcraft_doc::ModelState| {
st.bodies
.iter()
.map(|b| {
let m = solvecraft_kernel::measure(&b.body).unwrap();
let m = solidcraft_kernel::measure(&b.body).unwrap();
// Volume and area to 1e-9: two evaluations from scratch differ in the last
// bits too (the kernel's tessellation is not bit-for-bit repeatable).
(b.name.clone(), b.feature, (m.volume * 1e6).round(), (m.area * 1e6).round(), m.faces, m.edges, m.vertices, m.merged.faces)
@@ -1647,14 +1647,14 @@ fn cached_evaluation_matches_from_scratch() {
assert_eq!(s.model.state().sketches.len(), fresh.state().sketches.len(), "{what}: sketches");
};
// Two evaluations from scratch agree to the same precision.
let mut a = solvecraft_doc::Model::new();
let mut a = solidcraft_doc::Model::new();
a.no_cache = true;
a.evaluate(&s.doc);
let mut b = solvecraft_doc::Model::new();
let mut b = solidcraft_doc::Model::new();
b.no_cache = true;
b.evaluate(&s.doc);
let vols = |m: &solvecraft_doc::Model| {
m.state().bodies.iter().map(|x| (solvecraft_kernel::measure(&x.body).unwrap().volume * 1e6).round()).collect::<Vec<_>>()
let vols = |m: &solidcraft_doc::Model| {
m.state().bodies.iter().map(|x| (solidcraft_kernel::measure(&x.body).unwrap().volume * 1e6).round()).collect::<Vec<_>>()
};
assert_eq!(vols(&a), vols(&b));
same(&s, "built");
@@ -1859,14 +1859,14 @@ mod naming {
run(&mut s, "edit.undo", json!({}));
assert_eq!(attached(&s), Some(10.));
assert!((top(&mut s) - 15.).abs() < 1e-6);
let reopened = solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap();
let mut model = solvecraft_doc::Model::new();
let reopened = solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap();
let mut model = solidcraft_doc::Model::new();
model.evaluate(&reopened);
assert_eq!(model.state().sketch(reopened.find_feature("Top").unwrap().id).unwrap().plane.origin.z, 10.);
assert!((model.state().bodies[0].mesh().bounds().max.z - 15.).abs() < 1e-6);
// A historical format-10 attachment uses its saved generic face name and no strong anchor.
let mut old = (*s.doc).clone();
let solvecraft_doc::FeatureKind::Sketch { plane: solvecraft_doc::PlaneRef::Face { anchor, name, .. }, sketch } =
let solidcraft_doc::FeatureKind::Sketch { plane: solidcraft_doc::PlaneRef::Face { anchor, name, .. }, sketch } =
&mut old.features.iter_mut().find(|f| f.name == "Top").unwrap().kind
else {
panic!("fixture must contain a face sketch");
@@ -1878,8 +1878,8 @@ mod naming {
link.face_name = name.clone();
}
}
old.format = "solvecraft/10".into();
let mut legacy = Session::new(solvecraft_doc::Document::from_json(&old.to_json()).unwrap());
old.format = "solidcraft/10".into();
let mut legacy = Session::new(solidcraft_doc::Document::from_json(&old.to_json()).unwrap());
assert_eq!(attached(&legacy), Some(10.));
assert!((top(&mut legacy) - 15.).abs() < 1e-6);
// The upstream join consumes the certified body; new attachments cannot follow its substitute roof.
@@ -1893,10 +1893,10 @@ mod naming {
}
assert_eq!(attached(&legacy), Some(10.), "historical weak naming retains its saved geometry behavior");
assert!((top(&mut legacy) - 20.).abs() < 1e-6);
let legacy_reopened = Session::new(solvecraft_doc::Document::from_json(&legacy.doc.to_json()).unwrap());
let legacy_reopened = Session::new(solidcraft_doc::Document::from_json(&legacy.doc.to_json()).unwrap());
assert_eq!(attached(&legacy_reopened), Some(10.));
assert!((legacy_reopened.model.state().bodies[0].mesh().bounds().max.z - 20.).abs() < 1e-6);
let solvecraft_doc::FeatureKind::Sketch { plane: solvecraft_doc::PlaneRef::Face { anchor, .. }, .. } =
let solidcraft_doc::FeatureKind::Sketch { plane: solidcraft_doc::PlaneRef::Face { anchor, .. }, .. } =
&legacy_reopened.doc.find_feature("Top").unwrap().kind
else {
panic!("saved historical face reference must remain present");
@@ -2171,7 +2171,7 @@ fn appearances_survive_later_features() {
// Exported with a body colour and the red face, then opened: the colours come in as the
// body's own; a hole afterwards keeps them.
run(&mut s, "appearance.assign", json!({"bodies": ["B"], "color": "#2050c0"}));
let dir = std::env::temp_dir().join(format!("solvecraft-paint-{}", std::process::id()));
let dir = std::env::temp_dir().join(format!("solidcraft-paint-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let step = dir.join("painted.step");
run(&mut s, "file.export", json!({"path": step.to_string_lossy()}));
@@ -2192,10 +2192,10 @@ fn appearances_survive_later_features() {
/// The original #2 RGB import acceptance, including later topology and placement edits.
#[test]
fn imported_rgb_appearances_survive_edits_reopen_and_step_export() {
let dir = std::env::temp_dir().join(format!("solvecraft-imported-rgb-{}", std::process::id()));
let dir = std::env::temp_dir().join(format!("solidcraft-imported-rgb-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let input = dir.join("rgb.step");
let saved = dir.join("edited.solvecraft");
let saved = dir.join("edited.solidcraft");
let output = dir.join("edited.step");
let mut source = Session::default();
for (name, x, color) in [("Red", 0, "#ff0000"), ("Green", 40, "#00ff00"), ("Blue", 80, "#0000ff")] {
@@ -2380,7 +2380,7 @@ fn combine_keep_tools_and_new_component() {
assert!(st.body("B").is_some(), "the tool is kept");
let a = st.body("A").unwrap();
assert_eq!(s.doc.body_component("A", a.feature), comp);
assert!(rel(solvecraft_kernel::measure(&a.body).unwrap().volume, 4000.0) < 1e-9);
assert!(rel(solidcraft_kernel::measure(&a.body).unwrap().volume, 4000.0) < 1e-9);
}
/// Align with snap points: a box's vertex onto another's face centre, an edge midpoint onto a
@@ -2491,13 +2491,13 @@ fn drawing_on_existing_points_connects_to_them() {
fn computed_import_preserves_file_and_earlier_undo_history() {
let mut s = Session::default();
run(&mut s, "solid.box", json!({"length": 10, "width": 10, "height": 10}));
s.path = Some("existing.solvecraft".into());
s.path = Some("existing.solidcraft".into());
s.mark_saved();
let saved = s.doc.clone();
let mut computed = s.scratch();
run(&mut computed, "solid.box", json!({"length": 5, "width": 5, "height": 5, "corner": [30, 0, 0]}));
s.accept_computed(computed);
assert_eq!(s.path.as_deref(), Some("existing.solvecraft"));
assert_eq!(s.path.as_deref(), Some("existing.solidcraft"));
assert!(s.is_dirty());
assert_eq!(s.model.state().bodies.len(), 2);
run(&mut s, "edit.undo", json!({}));
@@ -2536,7 +2536,7 @@ fn special_files_are_refused_by_every_file_command() {
assert!(r.as_ref().is_err_and(|e| e.to_string().contains("not a regular file")), "{cmd}: {r:?}");
}
// The commands that check the extension first need a special file with the right name.
let dir = std::env::temp_dir().join(format!("solvecraft-special-{}", std::process::id()));
let dir = std::env::temp_dir().join(format!("solidcraft-special-{}", std::process::id()));
let _ = std::fs::create_dir_all(&dir);
for (cmd, name) in [("file.insert_step", "x.step"), ("file.insert_mesh", "x.3mf"), ("sketch.insert_dxf", "x.dxf")] {
let p = dir.join(name);
@@ -2777,12 +2777,12 @@ fn a_design_saved_before_format_14_keeps_generic_names_past_body_name() {
run(&mut s, "sketch.finish", json!({}));
run(&mut s, "solid.extrude", json!({"sketch": "R", "distance": "10", "operation": "new_body", "body_name": "Ribs"}));
let mut legacy: Value = serde_json::from_str(&s.doc.to_json()).unwrap();
legacy["format"] = json!("solvecraft/13");
legacy["format"] = json!("solidcraft/13");
let extrude = legacy["features"].as_array_mut().unwrap().iter_mut().find(|f| f["type"] == "extrude").unwrap();
extrude["body_names"] = json!(["Ribs"]);
let dir = std::env::temp_dir().join(format!("solvecraft-373-{}", std::process::id()));
let dir = std::env::temp_dir().join(format!("solidcraft-373-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let design = dir.join("ribs.solvecraft");
let design = dir.join("ribs.solidcraft");
std::fs::write(&design, legacy.to_string()).unwrap();
let mut old = Session::default();
run(&mut old, "doc.open", json!({"path": design.to_string_lossy()}));