Main's new callers of the placing calls that now return their error

The branch's placement change (placing the model returns its error) made
world_state, home_camera, bounds, open_sketch_plane, the scene builders,
to_local and auto_operation return a Result. Main added callers of them
since the branch left it; they now take the error as the branch's own
callers do:

- engine: commands pass it on (`?`); helpers answering yes/no or "found"
  (snap points, a face's normal, the DXF export check) answer no for a
  model that cannot be placed. Frames keep the session for main's lazy body
  frames (#6356) beside the placed model the picks are on; a library apply
  maps its targets on that placed model. Before an extract or a link is
  replaced, the placed model is taken or the command fails.
- ui: panels, picks, dialogs and previews read the placed model through
  open_meshing::session_world (none when it cannot be placed: the viewport
  says why); Home and Fit frame nothing then.
- cli: the usage table is main's, with kernel-cases in it (a resolution of
  the rig's commit had kept the branch's whole file).
- tests read the Results with unwrap, and main's tests of Body::tessellate
  mesh through Body::mesh. Main's test of saved boolean retries goes (the
  branch saves none), as does the thread half of its dialog rendering test
  (the branch's thread dialog has its own); the tapped hole half stays.
- What no caller uses after the merge goes: a mesh body's span, the metric
  thread refusal text, and the revolve's targets-as-shown model (main used
  it for the coplanar run-on the branch removed). Main's SolidWorks Y-up
  test reads the exporting system's name (the branch's STEP header has no
  preprocessor or description fields).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
jyviko
2026-10-26 13:18:38 -04:00
parent d63aa814f4
commit cfc393d68f
73 changed files with 241 additions and 278 deletions
-3
View File
@@ -4538,9 +4538,6 @@ fn feature_tools_with_constructor(
revolve_axis(&ss, axis, &axis_state(doc, st, f.component, axis), &|name| construction_axis_in(Some(doc), st, name, f.component))?;
// To Object is in this feature's component frame: the model as seen from there.
let tst = if to.is_some() { state_in_frame(doc, st, f.component)? } else { std::borrow::Cow::Borrowed(st) };
// Where the tool meets its targets is measured where they are shown (#3383).
let shown = targets_as_shown(st, f, &feature_op(f).1)?;
let st: &ModelState = &shown;
let angles = if to.is_some() {
(0.0, None)
} else {
+2 -2
View File
@@ -342,7 +342,7 @@ fn visibility(s: &mut Session, p: &Value) -> Result<Value> {
// Everything at once, for a single "show all" step: every body, sketch, construction
// item, origin item and joint.
(Some("all"), None) => {
let mut all: Vec<Shown> = s.world_state().bodies.iter().map(|b| Shown::Body(b.name.clone())).collect();
let mut all: Vec<Shown> = s.world_state()?.bodies.iter().map(|b| Shown::Body(b.name.clone())).collect();
all.extend(s.model.state().sketches.iter().map(|x| Shown::Sketch(x.feature)));
all.extend(s.doc.features.iter().filter(|f| is_construction(&f.kind)).map(|f| Shown::Origin(f.name.clone())));
all.extend(ORIGIN.iter().map(|o| Shown::Origin(o.to_string())));
@@ -1664,7 +1664,7 @@ mod tests {
run(&mut s, "solid.box", json!({"length": 10, "width": 10, "height": 10, "body_name": "Box"}));
s.mark_saved();
let cam = solidcraft_render::Camera::default();
let drawn = |s: &Session| crate::view::scene(s, &cam).meshes.len();
let drawn = |s: &Session| crate::view::scene(s, &cam).unwrap().meshes.len();
assert!(drawn(&s) > 0, "the box is drawn");
assert!(!s.is_dirty());
let steps = s.undo.len();
@@ -193,7 +193,7 @@ fn a_drag_and_a_world_move_leave_the_same_design() {
run(&mut dragged, "occurrence.drag", json!({"occurrence": free, "at": [25, 0, 0], "point": [25, 15, 0]}));
run(&mut moved, "occurrence.move", json!({"occurrence": free, "space": "world", "translate": [0, 15, 0]}));
assert_eq!(dragged.doc.to_json(), moved.doc.to_json());
let shown = |s: &Session| format!("{:?}", s.world_state().sketches.iter().map(|k| &k.sketch.points).collect::<Vec<_>>());
let shown = |s: &Session| format!("{:?}", s.world_state().unwrap().sketches.iter().map(|k| &k.sketch.points).collect::<Vec<_>>());
assert_eq!(shown(&dragged), shown(&moved));
}
+15 -15
View File
@@ -453,14 +453,14 @@ struct Before {
}
impl Before {
fn take(s: &Session) -> Before {
fn take(s: &Session) -> Result<Before> {
let sol = solidcraft_doc::joints::solve(&s.doc, &s.model.state());
Before {
world: s.world_state(),
Ok(Before {
world: s.world_state()?,
placements: s.doc.occurrences.iter().map(|o| (o.id, o.transform)).collect(),
errors: s.model.results.iter().map(|r| (r.id, r.error.is_some())).collect(),
joint_errors: sol.errors.iter().chain(&sol.conflicts).map(|(_, n, _)| n.clone()).collect(),
}
})
}
/// Refuse a swap that made a feature fail or changed a body; warn about joints it broke and
@@ -482,7 +482,7 @@ impl Before {
o.transform = *t;
}
}
let world = if moved.is_empty() { s.world_state() } else { std::sync::Arc::new(solidcraft_doc::world_state(&placed, &s.model.state())) };
let world = if moved.is_empty() { s.world_state()? } else { std::sync::Arc::new(solidcraft_doc::world_state(&placed, &s.model.state())?) };
let (was, now): (Vec<&ModelBody>, Vec<&ModelBody>) = (self.world.bodies.iter().collect(), world.bodies.iter().collect());
if let Some(what) = unmatched(&was, &now)? {
return Err(bad(cmd, format!("{what} would look different without the history of {name}")));
@@ -529,7 +529,7 @@ fn copies_of(s: &Session, linked: u64) -> Result<Vec<u64>> {
}
let Ok(sc) = scope(s, c.id, CMD) else { continue };
let Ok((part, _)) = part_design(s, &sc, &c.name, CMD) else { continue };
let world = part.world_state();
let world = part.world_state()?;
let built: Vec<&ModelBody> = world.bodies.iter().collect();
if same_solids(&reference, &built)? {
out.push(c.id);
@@ -569,12 +569,12 @@ pub(super) fn extract(s: &mut Session, p: &Value) -> Result<Value> {
let (_, sf, faces, _) = session_bodies(&part, &shown)?;
// The link has a body per piece: a body in pieces keeps its name on the first
let mut solids: Vec<String> = Vec::new();
for b in part.world_state().bodies.iter().filter(|b| !b.body.is_mesh()) {
for b in part.world_state()?.bodies.iter().filter(|b| !b.body.is_mesh()) {
let pieces = b.body.lumps()?.len().max(1);
solids.extend((1..=pieces).map(|n| if n == 1 { b.name.clone() } else { format!("{} ({n})", b.name) }));
}
let state = s.model.state();
let before = Before::take(s);
let before = Before::take(s)?;
let last = s.doc.features.iter().rposition(|f| sc.inside.contains(&f.id)).unwrap_or(s.doc.features.len());
let at = s.doc.features.iter().take(last).filter(|f| !sc.inside.contains(&f.id)).count();
clear_history(s, &sc);
@@ -661,12 +661,12 @@ pub(super) fn replace_with_link(s: &mut Session, p: &Value) -> Result<Value> {
let (part, _) = part_design(s, &sc, &sc.name, cmd)?;
let state = s.model.state();
let reference: Vec<&ModelBody> = state.bodies.iter().filter(|b| b.feature == link.feature).collect();
let world = part.world_state();
let world = part.world_state()?;
let built: Vec<&ModelBody> = world.bodies.iter().collect();
if !same_solids(&reference, &built)? {
return Err(bad(cmd, format!("{} is not the same as {lname}", sc.name)));
}
let before = Before::take(s);
let before = Before::take(s)?;
clear_history(s, &sc);
let d = s.doc_mut();
// Named as the linked component's next placements, as add_occurrence names them
@@ -718,7 +718,7 @@ mod tests {
}
fn volumes(s: &Session) -> BTreeMap<String, i64> {
s.world_state()
s.world_state().unwrap()
.bodies
.iter()
.map(|b| (b.name.clone(), (solidcraft_kernel::measure(&b.body).unwrap().volume * 1000.0).round() as i64))
@@ -726,7 +726,7 @@ mod tests {
}
fn centroids(s: &Session) -> BTreeMap<String, [i64; 3]> {
s.world_state()
s.world_state().unwrap()
.bodies
.iter()
.map(|b| {
@@ -740,7 +740,7 @@ mod tests {
/// coarser than what a link's STEP round trip changes in a round face.
fn shapes(s: &Session) -> Vec<[i64; 4]> {
let mut out: Vec<[i64; 4]> = s
.world_state()
.world_state().unwrap()
.bodies
.iter()
.map(|b| {
@@ -885,12 +885,12 @@ mod tests {
let plate_occ = s.doc.occurrence_of(plate).map(|o| o.id).unwrap();
let other_occ = s.doc.occurrence_of(other).map(|o| o.id).unwrap();
let top = {
let w = s.world_state();
let w = s.world_state().unwrap();
let m = solidcraft_kernel::measure(&w.body("PlateBody").unwrap().body).unwrap();
[(m.bbox.min.x + m.bbox.max.x) / 2.0, (m.bbox.min.y + m.bbox.max.y) / 2.0, m.bbox.max.z]
};
let bottom = {
let w = s.world_state();
let w = s.world_state().unwrap();
let m = solidcraft_kernel::measure(&w.body("OtherBody").unwrap().body).unwrap();
[(m.bbox.min.x + m.bbox.max.x) / 2.0, (m.bbox.min.y + m.bbox.max.y) / 2.0, m.bbox.min.z]
};
+2 -2
View File
@@ -109,7 +109,7 @@ fn inside_hull(sorted: &[(f64, f64)], q: (f64, f64)) -> bool {
/// The outward normal of the planar face at the world point `p`: on the model as the view
/// places it (moved components where they are shown).
pub(super) fn face_normal_world(s: &Session, p: Vec3) -> Option<Vec3> {
let (m, f, d, _) = face_at_state(&s.world_state(), p, None).ok().flatten()?;
let (m, f, d, _) = face_at_state(&*s.world_state().ok()?, p, None).ok().flatten()?;
let tol = (m.bounds().diagonal() * 1e-3).max(1e-3);
if d > tol * 10.0 {
return None;
@@ -149,7 +149,7 @@ pub(super) fn face_ref(s: &Session, v: &Value, body: Option<&str>, cmd: &str) ->
match face_ref_form(v).map_err(|why| bad(cmd, why))? {
FaceRefForm::Point(p) => Ok(p),
FaceRefForm::Name(name) => {
let st = if crate::frames::mapped() { s.model.state() } else { s.world_state() };
let st = if crate::frames::mapped() { s.model.state() } else { s.world_state()? };
solidcraft_doc::naming::named_face_point(&st, body, name).map_err(|why| bad(cmd, why))
}
}
+1 -8
View File
@@ -1450,7 +1450,7 @@ fn edge_points(s: &Session, p: &Value, cmd: &str) -> Result<Vec<Vec3>> {
let tol = (size * 2e-3).max(1e-3);
// The display mesh flags seams (a smooth surface meeting itself, as down the side
// of a cylinder), which nobody means by a pick where they end.
match b.body.display_mesh(tol) {
match b.body.mesh(&solidcraft_kernel::MeshTolerance::chord(tol)) {
Ok(m) => Some((size, m.edges, m.seams)),
Err(_) => b.body.edges(tol).ok().map(|edges| (size, edges.into_iter().map(|e| e.points).collect(), Vec::new())),
}
@@ -2329,13 +2329,6 @@ fn clearance_diameter(size: &str, fit: &str) -> Option<f64> {
}
}
/// The refusal for a thread designation `parse_metric_thread` does not know. A plain `M<d>` with
/// a sane diameter but no ISO coarse pitch (M28, M7) gets two pitches to try: the largest common
/// pitch up to about an eighth of the diameter, and the next finer one.
fn unknown_metric_thread(t: &str, examples: &str) -> String {
fine_only_hint(t).unwrap_or_else(|| format!("unknown thread `{t}` (ISO metric, like {examples})"))
}
/// The pitches to try for a plain `M<d>` that has no ISO coarse pitch, `None` for anything else.
fn fine_only_hint(t: &str) -> Option<String> {
const PITCHES: [f64; 9] = [2.0, 1.5, 1.25, 1.0, 0.75, 0.5, 0.35, 0.25, 0.2];
+8 -8
View File
@@ -95,7 +95,7 @@ fn sketch_on_a_part_in_another_component_joins_and_cuts_it() {
// Through All with no distance cut nothing and made a separate body.
(json!({"through_all":true,"direction":"negative"}), "cut", 1000. - 10. / 3. * added),
] {
assert_eq!(crate::auto_operation(&s, &params), Some(expected), "{params}");
assert_eq!(crate::auto_operation(&s, &params).unwrap(), Some(expected), "{params}");
for operation in ["auto", expected] {
let mut t = s.scratch();
let mut p = params.clone();
@@ -156,7 +156,7 @@ fn every_inward_extrude_into_a_part_in_another_component_cuts_it() {
if from_sketch {
p["profiles"] = json!("all");
}
let auto = crate::auto_operation(&t, &p);
let auto = crate::auto_operation(&t, &p).unwrap();
if operation == "auto" && auto != Some(expected) {
wrong.push(format!("{params}: auto reads {auto:?}, expected {expected}"));
}
@@ -1400,7 +1400,7 @@ fn a_tapered_cut_that_widens_cuts_from_small_corners() {
let pocket = w * h * l + k * (w + h) * l * l + 4.0 / 3.0 * k * k * l.powi(3) - c * (r * r * l + r * k * l * l + k * k * l.powi(3) / 3.0);
let v = volume(&mut s);
assert!((v - (318.0 * 212.0 * 20.0 - pocket)).abs() < 1e-3 * pocket, "{deg}°: {v}, a {pocket} mm³ pocket");
assert_eq!(s.world_state().bodies.len(), 1);
assert_eq!(s.world_state().unwrap().bodies.len(), 1);
}
}
@@ -2094,7 +2094,7 @@ fn duplicate_and_excess_profile_picks_are_refused_quickly() {
let e = s.execute("solid.extrude", &json!({"sketch": "Bottom", "distance": 5, "profiles": points})).unwrap_err().to_string();
assert!(e.contains("2 profile points are given, but Bottom has 1"), "{e}");
assert!(started.elapsed().as_secs_f64() < 5.0, "refusing took {:?}", started.elapsed());
assert!(s.world_state().bodies.is_empty(), "nothing was built");
assert!(s.world_state().unwrap().bodies.is_empty(), "nothing was built");
// One pick of the profile still builds.
run(&mut s, "solid.loft", json!({"sections": [{"sketch": "Bottom", "profiles": [0]}, {"sketch": "Top"}]}));
}
@@ -2276,7 +2276,7 @@ fn a_sketch_coil_never_panics_on_hostile_parameters() {
}
fn placed_bounds(s: &Session, name: &str) -> solidcraft_geom::Aabb3 {
s.world_state().body(name).unwrap_or_else(|| panic!("no {name}")).mesh().bounds()
s.world_state().unwrap().body(name).unwrap_or_else(|| panic!("no {name}")).mesh().bounds()
}
fn about(a: f64, b: f64) -> bool {
@@ -2300,7 +2300,7 @@ fn twice_placed_blade(s: &mut Session) -> (String, String) {
let copy = run(s, "occurrence.copy", json!({"occurrence": made["occurrence"]}));
run(s, "occurrence.move", json!({"occurrence": copy["occurrence"], "axis": [1, 0, 0], "angle": "90 deg"}));
run(s, "occurrence.move", json!({"occurrence": copy["occurrence"], "translate": [100, 0, 0], "space": "world"}));
let names: Vec<String> = s.world_state().bodies.iter().map(|b| b.name.clone()).collect();
let names: Vec<String> = s.world_state().unwrap().bodies.iter().map(|b| b.name.clone()).collect();
assert_eq!(names.len(), 2, "{names:?}");
unground_all(s);
(names[0].clone(), names[1].clone())
@@ -2357,7 +2357,7 @@ fn an_imported_assembly_with_a_repeated_part_moves_as_one() {
let mut s = Session::default();
run(&mut s, "file.insert_step", json!({"path": path}));
unground_all(&mut s);
let names: Vec<String> = s.world_state().bodies.iter().map(|b| b.name.clone()).collect();
let names: Vec<String> = s.world_state().unwrap().bodies.iter().map(|b| b.name.clone()).collect();
assert_eq!(names.len(), 2, "{names:?}");
let before: Vec<_> = names.iter().map(|n| placed_bounds(&s, n)).collect();
let r = run(&mut s, "solid.move", json!({"bodies": names, "translate": [240, -100, 0]}));
@@ -2386,7 +2386,7 @@ fn placed_plate(s: &mut Session, name: &str, x0: f64, angle: f64) {
}
fn world_bounds(s: &Session, name: &str) -> solidcraft_geom::Aabb3 {
s.world_state().body(name).unwrap_or_else(|| panic!("no {name}")).mesh().bounds()
s.world_state().unwrap().body(name).unwrap_or_else(|| panic!("no {name}")).mesh().bounds()
}
fn near(a: f64, b: f64) -> bool {
+1 -22
View File
@@ -130,7 +130,7 @@ fn exporter_up(import: &solidcraft_kernel::StepImport) -> Option<(&'static str,
if import.system.trim().starts_with("SolidCraft") {
return Some(("z", "SolidCraft"));
}
let named = [&import.system, &import.preprocessor, &import.description].iter().any(|h| h.to_ascii_lowercase().contains("solidworks"));
let named = import.system.to_ascii_lowercase().contains("solidworks");
named.then_some(("y", "SolidWorks"))
}
@@ -1129,27 +1129,6 @@ mod tests {
/// #5286: a saved model whose feature records the boolean retries it took (as the kernel
/// writes them, with no `rejected` list when there were none) is taken on opening, not
/// refused and evaluated again.
#[test]
fn a_saved_model_with_boolean_retries_is_adopted() {
let mut session = Session::new(Document::new("Retried"));
session.execute("solid.box", &json!({"length":10,"width":20,"height":30})).unwrap();
let mut saved = saved_geometry(&session, &session.doc).unwrap();
// What a coplanar-face retry leaves in the file (a user's design, #5286).
let retry = json!({"events": [{"face_shift_mm": 0.6477, "rotation_rad": 0.0, "strategy": "coplanar_separation", "tolerance_mm": 0.0, "translation_mm": 0.0}], "truncated": false});
saved.records["features"][0]["diagnostics"] = retry;
let bytes =
solidcraft_io::write_design_container_with_cache(&session.doc, solidcraft_io::DesignCompression::Fast, None, Some(&saved)).unwrap();
let path = std::env::temp_dir().join(format!("solidcraft-saved-retries-{}.solidcraft", std::process::id()));
std::fs::write(&path, &bytes).unwrap();
let mut reopened = Session::default();
reopened.execute("doc.open", &json!({"path": path})).unwrap();
std::fs::remove_file(&path).unwrap();
assert!(reopened.model.results.iter().all(|r| r.ms == session.model.results[0].ms), "taken from the file, not evaluated");
let d = &reopened.model.results[0].boolean_diagnostics;
assert_eq!(d.events.len(), 1, "{d:?}");
assert!(d.rejected.is_empty());
}
#[test]
fn saved_model_needs_no_meshes_and_is_adopted_only_for_its_document() {
let mut session = Session::new(Document::new("Saved model"));
+1 -1
View File
@@ -90,7 +90,7 @@ fn catalog_world_instance_targets_map_once_and_invalid_targets_are_atomic() {
assert!(face.point.dist(solidcraft_geom::Vec3::new(1., 1., 2.)) < 1e-9);
assert_eq!(face.look, look.to_look());
assert_eq!(s.undo.len(), history.0 + 1);
let world = s.world_state();
let world = s.world_state().unwrap();
let placed = world.body(&instance).unwrap();
assert!(s.doc.face_colors(placed).iter().any(|(_, assigned)| assigned == &look.to_look()));
apply_entry(&mut s, &file, &c.entries[1], json!({"bodies":[instance]})).unwrap();
+2 -1
View File
@@ -372,7 +372,8 @@ fn describe(s: &Session, st: &solidcraft_doc::ModelState, sel: &Sel) -> Option<I
/// A picked item's exact shape with its points (an edge's polyline) and triangles (a face's),
/// as Measure sees it: what [`crate::snap`] takes an item's centres, middle and ends from.
pub(crate) fn shape_of(s: &Session, sel: &Sel) -> Option<(Shape, Vec<Vec3>, Vec<[Vec3; 3]>)> {
let item = describe(s, sel)?;
let world = s.world_state().ok()?;
let item = describe(s, &world, sel)?;
Some(match item.geo {
Geo::Points(p) => (item.shape, p, Vec::new()),
Geo::Tris(t) => (item.shape, Vec::new(), t),
+3 -3
View File
@@ -107,7 +107,7 @@ fn assert_joint_only_moves(s: &mut Session, joint: &str) {
use std::sync::Arc;
let definitions = s.model.state();
let meshes: Vec<_> = definitions.bodies.iter().map(|b| b.mesh()).collect();
for body in &s.world_state().bodies {
for body in &s.world_state().unwrap().bodies {
std::hint::black_box(body.mesh());
std::hint::black_box(body.display_plan());
}
@@ -127,7 +127,7 @@ fn assert_joint_only_moves(s: &mut Session, joint: &str) {
for (b, mesh) in current.bodies.iter().zip(&meshes) {
assert!(Arc::ptr_eq(&b.mesh(), mesh), "{}: canonical mesh replaced", b.name);
}
let world = s.world_state();
let world = s.world_state().unwrap();
let (_, work) = crate::kernel::profile::capture(|| {
for b in &world.bodies {
let local = definitions.body(b.definition_name()).unwrap().mesh();
@@ -200,7 +200,7 @@ fn joint_pose_second_occurrence_shares_the_definition() {
let mut s = joined_motion_part(PartAuthoring::InComponent, JoinSelection::Named);
let second = run(&mut s, "occurrence.copy", json!({"component":"Rotor","translate":[40,0,0]}))["occurrence"].as_u64().unwrap();
run(&mut s, "joint.as_built", json!({"type":"revolute","name":"Copy turn","a":0,"b":second,"at":{"occurrence":0,"point":[40,0,0],"z":[0,0,1]}}));
assert_eq!(s.world_state().bodies.len(), 2);
assert_eq!(s.world_state().unwrap().bodies.len(), 2);
assert_joint_only_moves(&mut s, "Copy turn");
assert_motion_matches_full_rebuild(&s);
}
+2 -2
View File
@@ -881,8 +881,8 @@ fn a_stood_up_nested_step_part_is_cut_and_joined_from_its_face() {
.unwrap();
run(&mut s, "sketch.circle.center", json!({"center": [centre.x, centre.y], "radius": 3}));
run(&mut s, "sketch.finish", json!({}));
assert_eq!(crate::auto_operation(&s, &json!({"distance": -5})), Some("cut"), "top level {top_level}");
assert_eq!(crate::auto_operation(&s, &json!({"distance": 5})), Some("join"), "top level {top_level}");
assert_eq!(crate::auto_operation(&s, &json!({"distance": -5})).unwrap(), Some("cut"), "top level {top_level}");
assert_eq!(crate::auto_operation(&s, &json!({"distance": 5})).unwrap(), Some("join"), "top level {top_level}");
let volume = |s: &mut Session| run(s, "inspect.measure", json!({"bodies": ["NestedBlock"]}))["total"]["volume_mm3"].as_f64().unwrap();
let block = 40.0 * 24.0 * 10.0;
let hole = std::f64::consts::PI * 3.0 * 3.0;
+3 -3
View File
@@ -93,7 +93,7 @@ fn rectangular_cut_pattern_counts_only_target_contacts_and_keeps_landing_copies(
run(&mut s, "solid.cylinder", json!({"radius":2,"height":20,"base":[10,15,-5],"operation":"cut","targets":["Plate"],"name":"Cut"}));
let reply = run(&mut s, "solid.pattern.rectangular", json!({"features":["Cut"],"dir1":[1,0,0],"count1":4,"spacing1":20}));
assert_eq!(reply["warning"], "2 of 3 cut copies remove no material");
let state = s.world_state();
let state = s.world_state().unwrap();
let plate = &state.body("Plate").unwrap().body;
assert!(!plate.contains(solidcraft_geom::Vec3::new(30., 15., 5.)));
assert!(state.body("Neighbor").unwrap().body.contains(solidcraft_geom::Vec3::new(50., 15., 5.)));
@@ -756,7 +756,7 @@ fn patterns_and_mirrors_of_a_rib() {
run(&mut s, "solid.pattern.circular", json!({"features": ["Rib"], "axis": {"origin": [0, 0, -100], "dir": [0, 0, 1]}, "count": 4, "angle": 360}));
let ribs = [Vec3::new(12.0, 0.0, 8.0), Vec3::new(0.0, 12.0, 8.0), Vec3::new(-12.0, 0.0, 8.0), Vec3::new(0.0, -12.0, 8.0)];
let check = |s: &Session, inside: &[Vec3]| {
let st = s.world_state();
let st = s.world_state().unwrap();
assert!(s.model.results.iter().all(|r| r.error.is_none()), "{:?}", s.model.results.iter().map(|r| &r.error).collect::<Vec<_>>());
assert_eq!(st.bodies.len(), 1);
let body = &st.bodies[0].body;
@@ -853,7 +853,7 @@ fn patterns_and_mirrors_of_patterns_and_mirrors() {
for r in &s.model.results {
assert!(r.error.is_none(), "{what}: {}: {:?}", r.name, r.error);
}
let st = s.world_state();
let st = s.world_state().unwrap();
assert_eq!(st.bodies.len(), 1, "{what}");
let body = &st.bodies[0].body;
for [x, y] in &holes {
@@ -141,7 +141,7 @@ fn portable_job_save_is_host_independent_atomic_and_undoable_without_geometry_ch
assert!(s.render_job_host().is_none());
let original = s.doc.clone();
let model = s.model.state();
let world = s.world_state();
let world = s.world_state().unwrap();
let undo = s.undo.len();
s.evaluations = 0;
let setup: RenderSetup = serde_json::from_value(json!({
@@ -187,7 +187,7 @@ fn portable_job_save_is_host_independent_atomic_and_undoable_without_geometry_ch
);
assert_eq!(s.doc.render.setups["Studio"].exposure_ev, 2.5);
assert!(Arc::ptr_eq(&model, &s.model.state()));
assert!(Arc::ptr_eq(&world, &s.world_state()));
assert!(Arc::ptr_eq(&world, &s.world_state().unwrap()));
assert_eq!(s.evaluations, 0);
}
@@ -79,7 +79,7 @@ pub(super) fn resolve(s: &Session, scene: &ProductScene, bindings: &SurfaceBindi
return Ok(Vec::new());
}
let identities = targets(s, command)?;
let world = s.world_state();
let world = s.world_state()?;
let world_by_name: BTreeMap<_, _> = world.bodies.iter().map(|body| (body.name.as_str(), body)).collect();
let visible: BTreeMap<_, _> = scene.meshes.iter().enumerate().map(|(i, body)| (body.name.as_str(), i)).collect();
let mut result = Vec::new();
@@ -147,7 +147,7 @@ pub(super) fn bind(s: &mut Session, params: &Value) -> Result<Value> {
if matches.next().is_some() {
return Err(bad(command, "surface target is ambiguous"));
}
let world = s.world_state();
let world = s.world_state()?;
let body = world.body(name).ok_or_else(|| bad(command, "surface target disappeared"))?;
let inverse = solidcraft_doc::mat_inverse(&body.source_transform).ok_or_else(|| bad(command, "surface mapping transform is singular"))?;
let frame: PlanarFrame = serde_json::from_value(prepared["texture"]["frame"].clone()).map_err(|e| bad(command, e.to_string()))?;
@@ -698,7 +698,7 @@ fn rectangular_pattern_expressions_follow_parameters_and_survive_reopening() {
s.refresh();
run(&mut s, "parameters.change", json!({"name":"pitch","expression":"40"}));
run(&mut s, "parameters.change", json!({"name":"copies","expression":"4"}));
let state = s.world_state();
let state = s.world_state().unwrap();
let sk = &state.sketch(1).unwrap().sketch;
assert_eq!(sk.patterns[0].instances.len(), 7);
assert_eq!(sk.patterns[0].instances[0].curves[0], first);
@@ -706,7 +706,7 @@ fn rectangular_pattern_expressions_follow_parameters_and_survive_reopening() {
let last = &sk.patterns[0].instances.last().unwrap().curves[0];
assert!(point(sk, &format!("{last}.center")).dist(Vec2::new(125., 25.)) < 1e-6);
run(&mut s, "parameters.change", json!({"name":"copies","expression":"2"}));
assert_eq!(s.world_state().sketch(1).unwrap().sketch.patterns[0].instances.len(), 3);
assert_eq!(s.world_state().unwrap().sketch(1).unwrap().sketch.patterns[0].instances.len(), 3);
run(&mut s, "parameters.change", json!({"name":"pitch","new_name":"step"}));
assert_eq!(serde_json::to_value(&s.doc.sketch(1).unwrap().patterns[0]).unwrap()["expressions"]["spacing"], "step");
assert!(s.execute("parameters.change", &json!({"delete":"step"})).is_err());
@@ -749,7 +749,7 @@ fn circular_pattern_expressions_validate_inputs_and_keep_numeric_compatibility()
let before = sketch(&s);
let mut s = Session { doc: reopened.into(), ..Default::default() };
s.refresh();
let state = s.world_state();
let state = s.world_state().unwrap();
let after = &state.sketch(1).unwrap().sketch;
for (a, b) in before.points.iter().zip(&after.points) {
assert!(a.id == b.id && a.pos.dist(b.pos) < 1e-9, "{} moved", a.id);
+2 -2
View File
@@ -338,7 +338,7 @@ fn the_walls_of_an_open_thin_extrude_take_sketches_that_follow_their_thickness()
/// A point inside each flat, vertical face of the first body: (what it is, point).
fn flat_side_faces(s: &Session) -> Vec<(String, [f64; 3])> {
let world = s.world_state();
let world = s.world_state().unwrap();
let mut out = Vec::new();
let Some(body) = world.bodies.first() else { return out };
let mesh = body.mesh();
@@ -369,7 +369,7 @@ fn the_round_sides_of_a_thin_ring_are_named_by_the_circle_they_run_beside() {
run(&mut s, "sketch.circle.center", json!({"center": [0, 0], "radius": 10}));
run(&mut s, "sketch.finish", json!({}));
run(&mut s, "solid.extrude", json!({"sketch": "Outline", "thin": true, "thickness": 2, "distance": 5, "name": "Ring"}));
let world = s.world_state();
let world = s.world_state().unwrap();
let names = solidcraft_doc::naming::face_names(world.bodies.first().unwrap());
// Each round side is two half cylinders, told apart by their numbers.
let walls: Vec<_> = names.iter().filter(|name| name.contains(":wall:")).collect();
+2 -2
View File
@@ -31,7 +31,7 @@ fn has_profile(sk: &Sketch) -> bool {
/// Does the current view have anything `file.export_view_dxf` draws: a visible body or a shown
/// sketch with a profile curve? The export dialog asks this before choosing a file.
pub fn exportable(s: &Session) -> bool {
let world = s.world_state();
let Ok(world) = s.world_state() else { return false };
world.bodies.iter().any(|b| s.body_visible(&b.name)) || world.sketches.iter().any(|ss| sketch_shown(s, ss.feature) && has_profile(&ss.sketch))
}
@@ -51,7 +51,7 @@ fn curve_parts(sk: &Sketch, i: usize, plane: &Plane, tolerance: f64) -> Vec<Vec<
/// The shown sketches' profile curves, sampled at the export's chord `tolerance`.
pub(super) fn parts(s: &Session, tolerance: f64) -> Result<Vec<Part>, &'static str> {
let world = s.world_state();
let world = s.world_state().map_err(|_| "the model cannot be placed")?;
let shown: Vec<_> = world.sketches.iter().filter(|ss| sketch_shown(s, ss.feature)).collect();
let progress = Stage::new("dxf_projection", shown.len());
let mut result = Vec::new();
+12 -6
View File
@@ -93,7 +93,10 @@ fn turn_to(s: &mut Session, p: &Value, cmd: &str) -> Result<Value> {
let view = standard_view(cmd).ok_or_else(|| bad(cmd, "not a standard view"))?;
let mut cam: Camera = match p.get("camera") {
Some(value) => serde_json::from_value(value.clone()).map_err(|_| bad(cmd, "invalid camera"))?,
None => s.camera.unwrap_or_else(|| crate::view::home_camera(s)),
None => match s.camera {
Some(c) => c,
None => crate::view::home_camera(s)?,
},
};
if !cam.is_valid() || !cam.fov.is_finite() || !(0.0..std::f64::consts::PI).contains(&cam.fov) {
return Err(bad(cmd, "invalid camera"));
@@ -104,7 +107,7 @@ fn turn_to(s: &mut Session, p: &Value, cmd: &str) -> Result<Value> {
}
fn normal_target(s: &Session) -> Option<(solidcraft_geom::Vec3, solidcraft_geom::Vec3)> {
let state = s.world_state();
let state = s.world_state().ok()?;
s.selection.iter().find_map(|selection| match selection {
Sel::Face { body, index, point } if point.is_finite() => {
let plane = state.body(body)?.body.planar_face(*index)?;
@@ -120,7 +123,10 @@ fn normal_to(s: &mut Session, p: &Value) -> Result<Value> {
let (point, normal) = normal_target(s).ok_or_else(|| bad(cmd, "select a planar face or plane"))?;
let cam: Camera = match p.get("camera") {
Some(value) => serde_json::from_value(value.clone()).map_err(|_| bad(cmd, "invalid camera"))?,
None => s.camera.unwrap_or_else(|| crate::view::home_camera(s)),
None => match s.camera {
Some(c) => c,
None => crate::view::home_camera(s)?,
},
};
if !cam.is_valid()
|| !cam.fov.is_finite()
@@ -435,7 +441,7 @@ mod tests {
fn normal_to_selection_faces_each_planar_surface_without_editing_the_design() {
let mut s = Session::default();
s.execute("solid.box", &json!({"length":40,"width":30,"height":20})).unwrap();
let state = s.world_state();
let state = s.world_state().unwrap();
let body = state.bodies.first().unwrap();
let faces = body.body.faces(0.01).unwrap();
let document = s.doc.to_json();
@@ -490,7 +496,7 @@ mod tests {
fn normal_to_selection_rejects_curved_missing_and_invalid_inputs() {
let mut s = Session::default();
s.execute("solid.cylinder", &json!({"diameter":20,"height":30})).unwrap();
let state = s.world_state();
let state = s.world_state().unwrap();
let body = state.bodies.first().unwrap();
let faces = body.body.faces(0.1).unwrap();
let flat = faces.iter().find(|f| f.plane_normal.is_some()).unwrap();
@@ -521,7 +527,7 @@ mod tests {
s.execute("component.create", &json!({"name":"Part","grounded":false})).unwrap();
s.execute("solid.box", &json!({"length":40,"width":30,"height":20})).unwrap();
s.execute("occurrence.move", &json!({"occurrence":"Part:1","translate":[50,20,10],"axis":[1,2,3],"angle":"37 deg"})).unwrap();
let state = s.world_state();
let state = s.world_state().unwrap();
let body = state.bodies.first().unwrap();
let faces = body.body.faces(0.01).unwrap();
let cam = Camera { roll: 1.4, ..Default::default() };
+10 -10
View File
@@ -201,9 +201,9 @@ mod tests {
fn modeled_threads_do_not_receive_cosmetic_strokes() {
let mut s = Session::default();
run(&mut s, "solid.cylinder", json!({"radius":3,"height":4}));
let before = s.world_state().bodies[0].mesh().triangles.len();
let before = s.world_state().unwrap().bodies[0].mesh().triangles.len();
run(&mut s, "solid.thread", json!({"face":[3,0,3],"designation":"M6","length":2,"modeled":true}));
let st = s.world_state();
let st = s.world_state().unwrap();
assert!(st.threads.is_empty());
assert_ne!(st.bodies[0].mesh().triangles.len(), before);
assert!(segments(&st.bodies[0], &st.bodies[0].mesh(), &st.threads).is_empty());
@@ -215,7 +215,7 @@ mod tests {
run(&mut s, "solid.box", json!({"length":20,"width":20,"height":10}));
run(&mut s, "solid.hole", json!({"position":[10,10,10],"direction":[0,0,-1],"thread":"M6"}));
run(&mut s, "solid.hole", json!({"position":[10,10,10],"direction":[0,0,-1],"diameter":8,"depth":3}));
let st = s.world_state();
let st = s.world_state().unwrap();
let body = &st.bodies[0];
let lines = segments(body, &body.mesh(), &st.threads);
assert!(!lines.is_empty());
@@ -233,7 +233,7 @@ mod tests {
for i in 1..96 {
run(&mut s, "occurrence.copy", json!({"component":c["component"],"translate":[i*8,0,0]}));
}
let st = s.world_state();
let st = s.world_state().unwrap();
assert_eq!(st.bodies.len(), 96);
assert_eq!(st.threads.len(), 96);
let started = std::time::Instant::now();
@@ -277,7 +277,7 @@ mod tests {
let saved = s.doc.to_json();
let reopened = solidcraft_doc::Document::from_json(&saved).unwrap();
let mut s = Session::new(reopened);
let st = s.world_state();
let st = s.world_state().unwrap();
assert_eq!(st.threads.len(), 1);
assert!(st.threads[0].axis.z.abs() < 1e-6);
let body = &st.bodies[0];
@@ -305,10 +305,10 @@ mod tests {
} else {
run(&mut s, "solid.cylinder", json!({"radius":3,"height":10,"body_name":"Part"}));
}
let before = s.world_state().bodies[0].mesh();
let before = s.world_state().unwrap().bodies[0].mesh();
let face = if internal { json!([12.5, 10, 9]) } else { json!([3, 0, 9]) };
run(&mut s, "solid.thread", json!({"face":face,"designation":"M6","length":4,"modeled":false}));
let st = s.world_state();
let st = s.world_state().unwrap();
let body = &st.bodies[0];
assert_eq!(before.positions, body.mesh().positions);
assert_eq!(before.triangles, body.mesh().triangles);
@@ -336,7 +336,7 @@ mod tests {
}
}
assert_eq!(s.doc.to_json(), saved);
let scene = crate::view::scene(&s, &crate::render::Camera::default());
let scene = crate::view::scene(&s, &crate::render::Camera::default()).unwrap();
assert!(!scene.lines.iter().any(|l| l.width == 0.85), "ordinary snapshots preserve the default-off behavior");
for distance in [25.0, 60.0] {
let cam = crate::render::Camera {
@@ -353,7 +353,7 @@ mod tests {
&crate::view::Palette::DEFAULT,
Default::default(),
true,
);
).unwrap();
assert!(scene.lines.iter().any(|l| l.width == 0.85 && !l.on_top));
let marked = crate::render::render(&scene, &cam, 320, 240).rgba;
scene.lines.retain(|l| l.width != 0.85);
@@ -363,7 +363,7 @@ mod tests {
}
}
s.execute("edit.undo", &json!({})).unwrap();
assert!(s.world_state().threads.is_empty());
assert!(s.world_state().unwrap().threads.is_empty());
}
}
}
+6 -4
View File
@@ -143,7 +143,7 @@ fn pt(p: Vec3) -> Value {
}
/// The frame of the body a world point lies on (the nearest), or of the named body.
fn frame_at(world: &solidcraft_doc::ModelState, f: &Frames, p: Vec3, body: Option<&str>) -> Option<Mat> {
fn frame_at(s: &Session, world: &solidcraft_doc::ModelState, f: &Frames, p: Vec3, body: Option<&str>) -> Option<Mat> {
if let Some(b) = body
&& let Some((_, _, m)) = f.bodies(s).iter().find(|(w, _, _)| w == b)
{
@@ -182,6 +182,8 @@ fn tri_dist(p: Vec3, a: Vec3, b: Vec3, c: Vec3) -> f64 {
}
struct Mapper<'a> {
/// The session the body frames are read from (only when a pick needs them).
s: &'a Session,
/// The placed model the picks are on.
world: &'a solidcraft_doc::ModelState,
f: Frames,
@@ -195,7 +197,7 @@ struct Mapper<'a> {
impl Mapper<'_> {
fn pick(&mut self, v: &Value, body: Option<&str>) -> Option<Value> {
let p = vec3(v)?;
let m = if self.here { self.f.sketch } else { frame_at(self.world, &self.f, p, body).unwrap_or(self.f.active) };
let m = if self.here { self.f.sketch } else { frame_at(self.s, self.world, &self.f, p, body).unwrap_or(self.f.active) };
self.first.get_or_insert(m);
self.local_point(&m, p)
}
@@ -382,7 +384,7 @@ fn map_local(s: &Session, world: &solidcraft_doc::ModelState, id: &str, params:
// Library targets are the same world selections as appearance.assign,
// nested one level down. Convert at the outer boundary exactly once;
// run_nested subsequently receives the component-local picks and names.
let targets = to_local(s, "appearance.assign", params.get("targets")?)?;
let targets = map_local(s, world, "appearance.assign", params.get("targets")?)?;
let mut out = params.clone();
out["targets"] = targets;
return Some(out);
@@ -473,7 +475,7 @@ fn map_local(s: &Session, world: &solidcraft_doc::ModelState, id: &str, params:
obj.insert("origin".into(), serde_json::json!([0.0, 0.0, 0.0]));
}
let obj = &obj;
let mut m = Mapper { world, f: command_frames, first, changed: false, here: false };
let mut m = Mapper { s, world, f: command_frames, first, changed: false, here: false };
let body = obj.get("body").or_else(|| obj.get("face_body")).and_then(Value::as_str).map(str::to_string);
let mut out: Map<String, Value> = obj.clone();
// Picks first: their frame turns the directions.
+1 -1
View File
@@ -224,7 +224,7 @@ impl Ahead {
/// front end can say how far a batch has got.
pub fn evaluate_counting(self, commands: &[(String, Value)], step: &std::sync::atomic::AtomicUsize) -> Result<Evaluated> {
let (after, _) = self.0.preview_in_place(commands, Some(step))?;
after.world_state().warm_display();
after.world_state()?.warm_display();
Ok(Evaluated(after.model))
}
+1
View File
@@ -7,6 +7,7 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use super::types::{
AngleExpr, ConventionParam, DirectionParam, ExtentKeyword, ExtrudeOperation, FacesParam, FromPoint, IdOrName, LengthExpr, OperationParam, Point3,
+2 -2
View File
@@ -95,7 +95,7 @@ fn edge_points(s: &Session, sel: &Sel) -> Vec<(Vec3, SnapKind)> {
/// same circle, in order: the whole arc when the kernel split it.
fn whole_arc(s: &Session, sel: &Sel, center: Vec3, axis: UnitVec3, radius: f64) -> Option<Vec<Vec3>> {
let Sel::Edge { body, index, .. } = sel else { return None };
let st = s.world_state();
let st = s.world_state().ok()?;
let m = st.body(body)?.mesh();
let tol = (radius * 1e-6).max(1e-9);
let on = |e: &Vec<Vec3>| e.iter().all(|p| (p.dist(center) - radius).abs() <= tol * 10.0 && (*p - center).dot(axis).abs() <= tol * 10.0);
@@ -157,7 +157,7 @@ fn along(pts: &[Vec3], f: f64) -> Option<Vec3> {
/// ends on the axis; a sphere or torus its centre.
fn face_points(s: &Session, sel: &Sel) -> Vec<(Vec3, SnapKind)> {
let Sel::Face { body, index, .. } = sel else { return Vec::new() };
let st = s.world_state();
let Ok(st) = s.world_state() else { return Vec::new() };
let Some(b) = st.body(body) else { return Vec::new() };
let Some((shape, _, tris)) = crate::cmd::shape_of(s, sel) else { return Vec::new() };
let verts: Vec<Vec3> = tris.iter().flat_map(|t| t.iter().copied()).collect();
+8 -8
View File
@@ -1434,17 +1434,17 @@ fn extrude_auto_operation_counts_from_the_start() {
run(&mut s, "sketch.rectangle.two_point", json!({"p0": [5, 5], "p1": [15, 15]}));
run(&mut s, "sketch.finish", json!({}));
let down = json!({"profiles": [{"point": [10, 10]}], "distance": -5});
assert_eq!(auto_operation(&s, &down), Some("cut"));
assert_eq!(auto_operation(&s, &down).unwrap(), Some("cut"));
let mut from = down.clone();
from["from"] = json!([20, 15, 0]);
assert_eq!(auto_operation(&s, &from), Some("join"));
assert_eq!(auto_operation(&s, &from).unwrap(), Some("join"));
let mut offset = down.clone();
offset["start_offset"] = json!("-15 mm");
assert_eq!(auto_operation(&s, &offset), Some("join"));
assert_eq!(auto_operation(&s, &offset).unwrap(), Some("join"));
// Up from the bottom face into the plate it cuts again.
let mut up = from.clone();
up["distance"] = json!(5);
assert_eq!(auto_operation(&s, &up), Some("cut"));
assert_eq!(auto_operation(&s, &up).unwrap(), Some("cut"));
}
/// #1666: a loft between two 18-point polylines has twisted (bilinear) side faces. Each was
@@ -3565,7 +3565,7 @@ fn default_extrude_profiles_keep_even_depth_islands_and_separate_outlines() {
/// The face of `body` whose normal points along +x, as a plane pick.
fn side_face_pick(s: &Session, body: &str) -> Value {
let world = s.world_state();
let world = s.world_state().unwrap();
let b = world.body(body).unwrap();
let face = b
.body
@@ -3592,7 +3592,7 @@ fn extruding_a_sketch_on_a_side_face_takes_the_whole_drawn_profile() {
run(&mut s2, "solid.extrude", json!({"sketch": id, "distance": 5, "operation": "new", "body_name": "Rib", "profiles": selection.clone()}));
let state = s2.model.state();
let rib = state.bodies.iter().find(|b| b.name == "Rib").unwrap();
let v = rib.body.tessellate(1e-2).unwrap().measure().volume;
let v = rib.body.mesh(&solidcraft_kernel::MeshTolerance::chord(1e-2)).unwrap().measure().volume;
assert!(rel(v, 500.0) < 1e-6, "{selection}: {v}");
}
}
@@ -3614,7 +3614,7 @@ fn a_pick_bounded_by_a_projected_rim_takes_only_that_region() {
run(&mut s2, "solid.extrude", json!({"sketch": id, "distance": 5, "operation": "new", "body_name": "Pad", "profiles": [{"point": [12, 7]}]}));
let state = s2.model.state();
let pad = state.bodies.iter().find(|b| b.name == "Pad").unwrap();
let v = pad.body.tessellate(1e-2).unwrap().measure().volume;
let v = pad.body.mesh(&solidcraft_kernel::MeshTolerance::chord(1e-2)).unwrap().measure().volume;
// The square with the hole's rim cut out of it, not the whole square. The mesh measures the
// round rim as a polygon, so the volume is a little under the analytic one.
let want = (400.0 - PI * 25.0) * 5.0;
@@ -4472,7 +4472,7 @@ fn parallel_pipe_large_nominals_support_shallow_and_thin_cylindrical_holes() {
assert!(result.is_ok(), "{designation}, height={height}, depth={depth:?}: {result:?}");
let reopened = crate::Session::new(crate::doc::Document::from_json(&s.doc.to_json()).unwrap());
assert!(reopened.model.results.iter().all(|r| r.error.is_none()));
let state = reopened.world_state();
let state = reopened.world_state().unwrap();
assert_eq!(state.threads.len(), 1);
assert_eq!(state.threads[0].designation, designation);
assert!((state.threads[0].end - state.threads[0].start - depth.unwrap_or(height)).abs() < 1e-6);
+1 -1
View File
@@ -230,5 +230,5 @@ fn a_placement_leading_to_another_component_is_reported_lost() {
s.refresh();
let e = s.model.result(mirror).and_then(|r| r.error.clone()).unwrap_or_else(|| panic!("mirrored at the wrong placement"));
assert!(e.contains("no longer exists"), "{e}");
assert!(s.world_state().body(&copy).is_none());
assert!(s.world_state().unwrap().body(&copy).is_none());
}
+3 -3
View File
@@ -564,7 +564,7 @@ pub fn scene_with_thread_markings(
p: &Palette,
guides: Guides,
cosmetic_threads: bool,
) -> Scene {
) -> crate::Result<Scene> {
build_scene(s, cam, section, guides, sampler, p, cosmetic_threads)
}
@@ -1087,11 +1087,11 @@ mod tests {
let mut s = Session::default();
s.execute("solid.box", &json!({"length": 40, "width": 30, "height": 20})).unwrap();
s.execute("appearance.assign", &json!({"bodies": ["Body1"], "color": [40, 160, 90]})).unwrap();
let cam = home_camera(&s);
let cam = home_camera(&s).unwrap();
let mut picture = |hatch: bool| {
let plane = json!({"origin": [0, 15, 0], "normal": [0, -1, 0]});
s.execute("inspect.section", &json!({"plane": plane, "hatch": hatch})).unwrap();
solidcraft_render::render(&scene(&s, &cam), &cam, 300, 220).rgba
solidcraft_render::render(&scene(&s, &cam).unwrap(), &cam, 300, 220).rgba
};
// Pixels in the body's exact colour: only a plain cut has them (lit faces are shaded).
let solid = |img: &[u8]| img.chunks(4).filter(|p| p[..3] == [40, 160, 90]).count();
+5 -5
View File
@@ -222,19 +222,19 @@ fn colored_components_override_appearances_and_restore_them() {
}
s.selection.clear();
let cam = Camera::default();
let original = scene(&s, &cam);
let original = scene(&s, &cam).unwrap();
s.execute("inspect.colored_components", &json!({"enabled": true})).unwrap();
s.colored_components = Some(());
let colored = scene(&s, &cam);
let colored = scene(&s, &cam).unwrap();
assert_eq!(colored.meshes.len(), 2);
assert_ne!(colored.meshes[0].color, colored.meshes[1].color);
for (body, mesh) in s.world_state().bodies.iter().zip(&colored.meshes) {
for (body, mesh) in s.world_state().unwrap().bodies.iter().zip(&colored.meshes) {
let [r, g, b] = component_color(&s, body.component).unwrap();
assert_eq!(mesh.color, Rgb(r, g, b));
assert!(mesh.face_colors.is_empty());
}
s.execute("inspect.colored_components", &json!({"enabled": false})).unwrap();
let restored = scene(&s, &cam);
let restored = scene(&s, &cam).unwrap();
for (a, b) in original.meshes.iter().zip(restored.meshes) {
assert_eq!(a.color, b.color);
assert_eq!(a.face_colors, b.face_colors);
@@ -260,7 +260,7 @@ fn cycling_component_color_is_saved_undoable_and_shared_by_instances() {
assert_eq!(s.doc.appearances, appearances);
let loaded = Session::new(solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap());
assert_eq!(loaded.doc.component_identification_color(component), custom);
let copies: Vec<_> = s.world_state().bodies.iter().map(|b| b.component).collect();
let copies: Vec<_> = s.world_state().unwrap().bodies.iter().map(|b| b.component).collect();
assert_eq!(copies, [component, component]);
s.execute("edit.undo", &json!({})).unwrap();
assert_eq!(component_color(&s, component), Some(automatic));
+2 -2
View File
@@ -31,11 +31,11 @@ fn the_grid_fades_out_before_its_edge_and_before_the_far_plane() {
#[test]
fn the_headless_grid_fades_out_with_the_distance() {
let s = Session::default();
let mut cam = home_camera(&s);
let mut cam = home_camera(&s).unwrap();
// A top view: the ground is the XY plane and the grid is centred on the target.
cam.pitch = std::f64::consts::FRAC_PI_2;
cam.target = Vec3::ZERO;
let scene = scene(&s, &cam);
let scene = scene(&s, &cam).unwrap();
assert!(!scene.grid.is_empty(), "the grid is drawn");
// How far each piece of the grid is from the middle of the view, and the alpha it is drawn
// with.
+6 -6
View File
@@ -102,8 +102,8 @@ fn pictures_show_appearances() {
fn the_renderer_counts_what_it_draws() {
let Some(c) = capturer() else { return };
let drawn = |s: &Session| {
let cam = view::home_camera(s);
let scene = view::scene_with_guides(s, &cam, None, Guides { grid: false, axes: false });
let cam = view::home_camera(s).unwrap();
let scene = view::scene_with_guides(s, &cam, None, Guides { grid: false, axes: false }).unwrap();
let triangles: usize = scene.meshes.iter().map(|m| m.mesh.triangles.len()).sum();
c.rgba(&scene, &cam, 160, 100).unwrap();
(c.last_stats(), triangles as u64)
@@ -187,10 +187,10 @@ fn pictures_show_a_plain_cut_without_the_hatch() {
("solid.box", json!({"length": 40, "width": 30, "height": 20})),
("appearance.assign", json!({"bodies": ["Body1"], "color": [40, 160, 90]})),
]);
let cam = view::home_camera(&s);
let cam = view::home_camera(&s).unwrap();
let mut picture = |hatch: bool| {
s.execute("inspect.section", &json!({"plane": {"origin": [0, 15, 0], "normal": [0, -1, 0]}, "hatch": hatch})).unwrap();
c.rgba(&view::scene(&s, &cam), &cam, 300, 220).unwrap()
c.rgba(&view::scene(&s, &cam).unwrap(), &cam, 300, 220).unwrap()
};
let solid = |img: &[u8]| pixels(img).filter(|p| p[..3].iter().zip([40u8, 160, 90]).all(|(a, b)| a.abs_diff(b) <= 3)).count();
let (hatched, plain) = (picture(true), picture(false));
@@ -222,9 +222,9 @@ fn a_body_behind_the_cut_does_not_colour_the_cap() {
s.execute("inspect.section", &json!({"plane": {"origin": [0, 6, 0], "normal": [0, -1, 0]}, "hatch": hatch})).unwrap();
}
for fov in [0.0, 0.6] {
let mut cam = view::home_camera(&alone);
let mut cam = view::home_camera(&alone).unwrap();
cam.fov = fov;
let picture = |s: &Session| c.rgba(&view::scene(s, &cam), &cam, 300, 220).unwrap();
let picture = |s: &Session| c.rgba(&view::scene(s, &cam).unwrap(), &cam, 300, 220).unwrap();
let (a, b) = (picture(&alone), picture(&with));
let differ = pixels(&a).zip(pixels(&b)).filter(|(p, q)| p.iter().zip(q).any(|(x, y)| x.abs_diff(*y) > 8)).count();
assert_eq!(differ, 0, "hatch {hatch}, fov {fov}: the inner body shows through the cap in {differ} pixels");
+3 -3
View File
@@ -291,7 +291,7 @@ fn an_edge_on_the_cut_face_draws_over_the_cap() {
/// the cap: the cap's pixels are where the picture without lines shows the hatch (warm, with
/// warm pixels 3 px around, so the cut's outline doesn't count).
fn edges_through_the_cap(c: &Capturer, s: &Session, cam: &Camera, (origin, normal): (Vec3, Vec3), (w, h): (u32, u32)) -> usize {
let mut sc = view::scene(s, cam);
let mut sc = view::scene(s, cam).unwrap();
let behind = |p: &Vec3| (*p - origin).dot(normal) < -0.5;
let segments = sc
.lines
@@ -335,7 +335,7 @@ fn edges_of_a_pocket_behind_the_cut_stay_under_the_cap() {
s.execute("inspect.section", &json!({"plane": {"origin": [0, -16, 0], "normal": [0, -1, 0]}})).unwrap();
for (mode, c) in capturers() {
for fov in [0.0, 0.6] {
let mut cam = view::home_camera(&s);
let mut cam = view::home_camera(&s).unwrap();
cam.fov = fov;
let n = edges_through_the_cap(c, &s, &cam, plane, (360, 300));
assert!(n <= 2, "{mode} depth, fov {fov}: the pocket's edges show through the cap in {n} px");
@@ -361,7 +361,7 @@ fn edges_of_a_cut_hub_stay_under_the_cap_as_the_view_turns() {
for fov in [0.0, 0.6] {
let mut shown = Vec::new();
for i in 0..12 {
let mut cam = view::home_camera(&s);
let mut cam = view::home_camera(&s).unwrap();
(cam.fov, cam.yaw, cam.pitch) = (fov, -1.2 + 0.075 * f64::from(i), 0.35);
shown.push(edges_through_the_cap(c, &s, &cam, plane, (900, 760)));
}
-11
View File
@@ -40,17 +40,6 @@ impl TriMesh {
}
/// Distinct undirected triangle sides.
/// The diagonal of the box around the vertices: a mesh body's size ([`Body::size`]),
/// measured once when the body is made. 0 when it has none.
pub(crate) fn span(&self) -> f64 {
let mut b = solidcraft_geom::Aabb3::EMPTY;
for p in &self.positions {
b.add(*p);
}
let d = b.diagonal();
if d.is_finite() && d > 0.0 { d } else { 0.0 }
}
pub(crate) fn edge_count(&self) -> usize {
let mut set = std::collections::HashSet::new();
for t in &self.triangles {
+1 -1
View File
@@ -2300,7 +2300,7 @@ fn a_rim_clicked_once_is_one_item_not_its_pieces() {
let (mut app, script, ctx) = app_with_script();
app.run("solid.cylinder", json!({"radius": 10, "height": 20})).expect("a cylinder");
let (name, top, bottom, mid_top, mid_bottom) = {
let st = app.session.world_state();
let st = crate::open_meshing::session_world(&app.session);
let body = &st.bodies[0];
let mesh = body.mesh();
let rim_at = |z: f64| mesh.edges.iter().position(|e| e.len() > 3 && e.iter().all(|p| (p.z - z).abs() < 1e-6)).expect("a rim");
+1 -1
View File
@@ -628,7 +628,7 @@ mod tests {
assert!(h.app.dialog.is_none(), "programmatic commands do not open interactive tools");
h.frames(120);
assert_eq!(rx.try_recv().unwrap()["ok"], true);
assert_eq!(h.app.session.world_state().bodies.len(), 1);
assert_eq!(crate::open_meshing::session_world(&h.app.session).bodies.len(), 1);
assert!(!h.app.agent.busy());
assert!(h.widget("Box").is_none(), "the command label ends with the animation");
}
+1 -1
View File
@@ -36,7 +36,7 @@ pub(crate) fn reveal_body(app: &mut SolveApp, body: &str) {
app.tree.reveal_feature = None;
app.tree.reveal = Some(body.to_string());
let state = app.session.model.state();
let Some(source) = app.session.world_state().body(body).map(|selected| selected.definition_name().to_string()) else { return };
let Some(source) = crate::open_meshing::session_world(&app.session).body(body).map(|selected| selected.definition_name().to_string()) else { return };
let Some(component) = state.body(&source).map(|definition| definition.component) else { return };
// Every component's first placement keeps the definition's name, so the owner must match too.
let Some((_, path, _)) = app
+1 -1
View File
@@ -585,7 +585,7 @@ fn draw(app: &SolveApp, ui: &mut egui::Ui, painter: &egui::Painter, proj: &Proj,
// An offset plane shows where it will be: at the distance, or through the picked point.
let through = crate::dialogs::offset_through_point(d).and_then(|p| match p {
Sel::Vertex { point, .. } => Some(*point),
Sel::SketchPoint { id } => sketch_point_at(app, &app.session.world_state(), id),
Sel::SketchPoint { id } => sketch_point_at(app, &crate::open_meshing::session_world(&app.session), id),
_ => None,
});
if let (Kind::OffsetPlane { offset }, Some(n)) = (&d.kind, dir)
+5 -5
View File
@@ -2232,7 +2232,7 @@ mod tests {
.to_owned();
let horiz = h.app.run("sketch.constrainer", json!({"entities":[l]})).unwrap()["constraint"].as_str().unwrap().to_owned();
let is_driven = |h: &crate::scenario::Harness| {
h.app.session.world_state().sketch(sid).unwrap().sketch.constraints.iter().any(|c| c.id == dim && c.driven)
crate::open_meshing::session_world(&h.app.session).sketch(sid).unwrap().sketch.constraints.iter().any(|c| c.id == dim && c.driven)
};
// A selected dimension offers the toggle, aimed at that constraint.
@@ -2318,7 +2318,7 @@ mod tests {
h.app.run("sketch.offset", json!({"curves":[source],"distance":2,"chain":false})).unwrap()["curves"][0].as_str().unwrap().to_owned();
h.app.run("sketch.line", json!({"points":[[10,-5],[10,5]],"connect":false,"infer":false})).unwrap();
let sid = h.app.session.active_sketch.unwrap();
let state = h.app.session.world_state();
let state = crate::open_meshing::session_world(&h.app.session);
let sk = &state.sketch(sid).unwrap().sketch;
let Shape::Line { a, b } = sk.shape(sk.curve_index(&output).unwrap()).unwrap() else { panic!("offset must be a line") };
let cut = Vec2::new(15.0, a.y);
@@ -2336,9 +2336,9 @@ mod tests {
assert!(item.enabled);
assert_eq!(item.params, Some(json!({"curve":output})));
h.step(&json!({"click":{"handle":"menu:sketch.offset.break"}})).unwrap();
assert!(h.app.session.world_state().sketch(sid).unwrap().sketch.offset_distance(&output).is_none());
assert!(crate::open_meshing::session_world(&h.app.session).sketch(sid).unwrap().sketch.offset_distance(&output).is_none());
h.app.run("sketch.trim", json!({"curve":output,"at":[cut.x,cut.y]})).unwrap();
let state = h.app.session.world_state();
let state = crate::open_meshing::session_world(&h.app.session);
let sk = &state.sketch(sid).unwrap().sketch;
let remaining = sk.shape(sk.curve_index(&output).unwrap()).unwrap();
assert!(matches!(remaining, Shape::Line { a, b } if (a.dist(b) - 10.0).abs() < 1e-6), "{remaining:?}");
@@ -2350,7 +2350,7 @@ mod tests {
let mut h = crate::scenario::Harness::new();
h.app.home.open = false;
h.app.run("solid.cylinder", json!({"diameter":30,"height":20})).unwrap();
let state = h.app.session.world_state();
let state = crate::open_meshing::session_world(&h.app.session);
let body = state.bodies.first().unwrap();
let faces = body.body.faces(0.1).unwrap();
let flat = faces.iter().find(|f| f.plane_normal.is_some()).unwrap();
+2 -2
View File
@@ -1438,7 +1438,7 @@ mod tests {
app.preferences.cosmetic_threads = cosmetic_threads;
for hatch in [false, true] {
app.session.section_hatch = hatch;
assert_eq!(view_scene(&app, Default::default()).plain_cap, !hatch);
assert_eq!(view_scene(&app, Default::default()).unwrap().plain_cap, !hatch);
app.session.selection = vec![Sel::Plane { name: "XZ".into() }];
app.start("inspect.section");
let Some(crate::dialogs::Kind::Section { hatch: preview_hatch, .. }) = app.dialog.as_mut().map(|d| &mut d.kind) else {
@@ -1446,7 +1446,7 @@ mod tests {
};
*preview_hatch = !hatch;
assert_eq!(
view_scene(&app, Default::default()).plain_cap,
view_scene(&app, Default::default()).unwrap().plain_cap,
hatch,
"the preview's hatch overrides the session with cosmetic_threads={cosmetic_threads}"
);
+9 -11
View File
@@ -1112,9 +1112,7 @@ impl Dialog {
return;
}
*of = picks.to_vec();
*suggested = crate::dialogs_thread::suggested(s, picks.map(|value| {
solidcraft_engine::doc::parse_metric_thread(&value).map(|(d, p)| crate::dialogs_thread::designation(d, p)).unwrap_or(value)
}), options);
*suggested = crate::dialogs_thread::suggested(s, picks, options);
*designation = suggested.clone().unwrap_or_default();
}
@@ -1304,7 +1302,7 @@ impl Dialog {
{
return Some(chain.faces.clone());
}
let state = s.world_state();
let state = crate::open_meshing::session_world(s);
let mut by_body = std::collections::BTreeMap::<&str, Vec<Vec3>>::new();
for pick in picks {
if let Sel::Face { body, point, .. } = pick {
@@ -1646,7 +1644,7 @@ fn default_profiles(s: &Session) -> Vec<Sel> {
}
fn single_profile(s: &Session, sketch: u64) -> Vec<Sel> {
match s.world_state().sketch(sketch).map(|ss| ss.profiles.len()) {
match crate::open_meshing::session_world(s).sketch(sketch).map(|ss| ss.profiles.len()) {
Some(1) => vec![Sel::Profile { sketch, index: 0 }],
_ => Vec::new(),
}
@@ -3249,7 +3247,7 @@ pub(crate) fn set_object_accepting(inputs: &mut Vec<SelInput>, active: &mut usiz
/// The plane an extrude's picked profiles lie in: the first pick's sketch plane, or its planar face.
fn profile_plane(s: &Session, first: &Sel) -> Option<solidcraft_engine::geom::Plane> {
let st = s.world_state();
let st = crate::open_meshing::session_world(s);
match first {
Sel::Profile { sketch, .. } => st.sketch(*sketch).map(|ss| ss.plane),
Sel::Face { body, index, .. } => st.body(body)?.body.planar_face(*index),
@@ -3263,7 +3261,7 @@ fn object_target(s: &Session, reference: &solidcraft_engine::geom::Plane, sel: &
const TOLERANCE: f64 = 1e-6;
match sel {
Sel::Face { body, index, .. } => {
let st = s.world_state();
let st = crate::open_meshing::session_world(s);
let Some(b) = st.body(body) else { return false };
b.body
.planar_face(*index)
@@ -4904,7 +4902,7 @@ fn named_refill(s: &Session, body: Option<&str>, name: &str, point: Vec3, edge:
.find(|(_, definition, _, _, _)| definition == source.definition_name())
.map(|(world, _, _, _, frame)| (world, solidcraft_engine::doc::apply_point(&frame, point)))
.unwrap_or_else(|| (source.name.clone(), point));
s.world_state().body(&body)?;
crate::open_meshing::session_world(s).body(&body)?;
Some(if edge { Sel::Edge { body, index, point } } else { Sel::Face { body, index, point } })
}
@@ -6298,7 +6296,7 @@ mod extrude_mixed_pick_tests {
// The new sketch's profile is taken at once.
h.step(&json!({"start":"solid.extrude"})).unwrap();
h.frames(2);
let st = h.app.session.world_state();
let st = crate::open_meshing::session_world(&h.app.session);
let box_body = &st.bodies[0];
let top = (0..64)
.find(|i| box_body.body.planar_face(*i).is_some_and(|p| p.normal().z > 0.99 && (p.origin.z - 10.0).abs() < 1e-9))
@@ -6315,7 +6313,7 @@ mod extrude_mixed_pick_tests {
h.frames(2);
h.step(&json!({"key":"Enter"})).unwrap();
assert!(h.app.dialog.is_none(), "OK: {:?}", h.app.dialog.as_ref().and_then(|d| d.error.clone()));
let st = h.app.session.world_state();
let st = crate::open_meshing::session_world(&h.app.session);
let volume: f64 = st.bodies.iter().map(|b| solidcraft_engine::doc::kernel::measure(&b.body).unwrap().volume).sum();
assert!((volume - 8000.0).abs() < 1e-6, "the box grown to 20 mm: {volume}");
}
@@ -6335,7 +6333,7 @@ mod shell_body_tests {
let mut h = Harness::new();
h.app.run("solid.box", json!({"length":20,"width":20,"height":20,"corner":[0,0,0]})).unwrap();
h.app.run("solid.box", json!({"length":20,"width":20,"height":10,"corner":[0,0,20],"operation":"new"})).unwrap();
let st = h.app.session.world_state();
let st = crate::open_meshing::session_world(&h.app.session);
let (lower, upper) = (st.bodies[0].name.to_string(), st.bodies[1].name.to_string());
h.app.run("browser.visibility", json!({"visible":false,"items":[lower]})).unwrap();
h.step(&json!({"call":"ui.view","params":{"view":"bottom"}})).unwrap();
+8 -8
View File
@@ -91,7 +91,7 @@ fn representative(h: &Harness, command: &str) -> Dialog {
} else if input.accept & PLANES != 0 {
vec![Sel::Plane { name: if index == 1 { "XZ" } else { "XY" }.into() }]
} else if input.accept & crate::selection::SKETCH_LINES != 0 {
vec![Sel::ExternalSketchCurve { sketch: s.world_state().sketches.last().unwrap().feature, id: path.clone() }]
vec![Sel::ExternalSketchCurve { sketch: crate::open_meshing::session_world(s).sketches.last().unwrap().feature, id: path.clone() }]
} else if input.accept & CURVES != 0 || command == "sheet.fold" && index == 0 {
vec![Sel::SketchCurve { id: path.clone(), sketch: None }]
} else if input.accept & AXES != 0 {
@@ -286,7 +286,7 @@ fn mirror_object_type_switch_back_restores_preselected_feature() {
for (command, params) in commands {
h.app.run(&command, params).unwrap();
}
assert_eq!(h.app.session.world_state().bodies.len(), 2);
assert_eq!(crate::open_meshing::session_world(&h.app.session).bodies.len(), 2);
}
#[test]
@@ -573,12 +573,12 @@ fn view_corner_ok_and_cancel_answer_the_dialog_like_its_own_buttons() {
h.settle();
assert_eq!(scenario::count_of("view:confirm:ok:enabled"), scenario::count_of("dialog:ok:enabled"));
assert_eq!(scenario::count_of("view:confirm:ok:enabled"), Some(1.0));
let (design, bodies) = (h.app.session.doc.to_json(), h.app.session.world_state().bodies.len());
let (design, bodies) = (h.app.session.doc.to_json(), crate::open_meshing::session_world(&h.app.session).bodies.len());
h.step(&json!({"click":{"handle":format!("view:confirm:{how}")}})).unwrap();
h.settle();
assert!(h.app.dialog.is_none(), "{how}: the dialog closes");
if how == "ok" {
assert_eq!(h.app.session.world_state().bodies.len(), bodies + 1, "the check made the revolve");
assert_eq!(crate::open_meshing::session_world(&h.app.session).bodies.len(), bodies + 1, "the check made the revolve");
} else {
assert_eq!(h.app.session.doc.to_json(), design, "the cross leaves the design as it was");
}
@@ -714,7 +714,7 @@ fn face_editor_refills_named_faces_after_sketch_resize() {
fn draft_uses_the_picked_solid_beside_a_hidden_overlapping_mesh() {
use solidcraft_engine::doc::{Document, MeshData, naming};
let mesh = solidcraft_engine::kernel::box_solid(Vec3::ZERO, Vec3::new(20.0, 20.0, 10.0)).unwrap().tessellate(0.01).unwrap();
let mesh = solidcraft_engine::kernel::box_solid(Vec3::ZERO, Vec3::new(20.0, 20.0, 10.0)).unwrap().mesh(&solidcraft_engine::kernel::MeshTolerance::chord(0.01)).unwrap();
let mut doc = Document::default();
doc.add_feature(
FeatureKind::MeshImport {
@@ -733,7 +733,7 @@ fn draft_uses_the_picked_solid_beside_a_hidden_overlapping_mesh() {
h.app.session = Session::new(doc);
h.app.run("solid.box", json!({"length":20,"width":20,"height":10})).unwrap();
h.app.run("browser.visibility", json!({"items":["Hidden mesh"],"visible":false})).unwrap();
let state = h.app.session.world_state();
let state = crate::open_meshing::session_world(&h.app.session);
let hidden_mesh = state.body("Hidden mesh").unwrap().mesh();
let body = state.bodies.iter().find(|body| !body.body.is_mesh()).unwrap();
let target = body.name.clone();
@@ -821,7 +821,7 @@ fn solid_pattern_quantity_arrows_step_the_count_the_command_receives() {
fn coil_from_a_profile_hides_the_primitive_section() {
let h = fixture();
let mut d = Dialog::for_command(&h.app, "solid.coil").unwrap();
let sketch = h.app.session.world_state().sketches[0].feature;
let sketch = crate::open_meshing::session_world(&h.app.session).sketches[0].feature;
d.inputs[0].items = vec![Sel::Profile { sketch, index: 0 }];
let commands = apply_commands(&h.app, &d).unwrap();
let (id, p) = &commands[0];
@@ -859,7 +859,7 @@ fn viewport_profile_click_returns_extrude_value_focus() {
h.step(&json!({"key":"Enter"})).unwrap();
h.settle();
assert!(h.app.dialog.is_none());
let bounds = h.app.session.world_state().bodies.first().unwrap().mesh().bounds();
let bounds = crate::open_meshing::session_world(&h.app.session).bodies.first().unwrap().mesh().bounds();
assert!((bounds.max.z - bounds.min.z - 30.0).abs() < 1e-7);
}
+1 -1
View File
@@ -374,7 +374,7 @@ fn straight_edge_snap_follows_a_rotated_occurrence() {
let transform = app.session.doc.occurrence_of(b).unwrap().transform;
let mid = solidcraft_engine::doc::apply_point(&transform, Vec3::new(30.0, 10.0, 10.0));
let axis = solidcraft_engine::doc::apply_vector(&transform, Vec3::Y);
let st = app.session.world_state();
let st = crate::open_meshing::session_world(&app.session);
let mesh = st.body("Body2").unwrap().mesh();
let index = mesh.edges.iter().position(|e| polyline_mid(e).dist(mid) < 1e-6).unwrap();
let snap = snap_of(&app.session, &Sel::Edge { body: "Body2".into(), index, point: mid }).unwrap();
+2 -2
View File
@@ -37,7 +37,7 @@ fn frame_lines(app: &SolveApp) -> Vec<String> {
}
fn body_names(app: &SolveApp) -> Vec<String> {
app.session.world_state().bodies.iter().map(|b| b.name.clone()).collect()
crate::open_meshing::session_world(&app.session).bodies.iter().map(|b| b.name.clone()).collect()
}
/// The dialog started with the given lines picked.
@@ -98,7 +98,7 @@ fn a_picked_section_and_the_placement() {
let ring = sketch_id(&app, "Ring");
let mut d = dialog_on(&mut app, &lines[..1]);
// The ring between the circles: the region whose interior point lies between radii 17 and 20.
let ss = app.session.world_state().sketch(ring).unwrap().clone();
let ss = crate::open_meshing::session_world(&app.session).sketch(ring).unwrap().clone();
let index = ss.profiles.iter().position(|p| (p.region.interior_point().len() - 18.5).abs() < 1.5).unwrap();
d.inputs[1].items = vec![Sel::Profile { sketch: ring, index }];
let m = member(&mut d);
+3 -3
View File
@@ -539,7 +539,7 @@ fn rule_value(app: &SolveApp, rule: usize) -> Option<String> {
fn path_chain(app: &SolveApp, inputs: &mut [SelInput]) {
let edge = inputs.first().and_then(|i| i.items.edge_points().first().copied());
let Some(input) = inputs.get_mut(1) else { return };
let st = app.session.world_state();
let st = crate::open_meshing::session_world(&app.session);
// The path is in the sketch its picks name (#3726).
let Some(ss) = crate::dialogs::picks_sketch(app, &input.items, None).and_then(|k| st.sketch(k)) else { return };
let mut ids: Vec<String> = Vec::new();
@@ -577,7 +577,7 @@ fn path_chain(app: &SolveApp, inputs: &mut [SelInput]) {
/// The lengths of the path's lines, in the order picked (mm).
fn path_lengths(app: &SolveApp, inputs: &[SelInput]) -> Vec<f64> {
let st = app.session.world_state();
let st = crate::open_meshing::session_world(&app.session);
let picks = items(inputs, 1);
let Some(ss) = crate::dialogs::picks_sketch(app, picks, None).and_then(|k| st.sketch(k)) else { return Vec::new() };
picks
@@ -1175,7 +1175,7 @@ impl Default for FlatView {
impl FlatView {
fn cache_perforations(&mut self, session: &solidcraft_engine::Session, body: &str) {
let state = session.world_state();
let state = crate::open_meshing::session_world(session);
let state_key = std::sync::Arc::downgrade(&state);
if self.perforation_key.as_ref().is_some_and(|(cached, name)| cached.ptr_eq(&state_key) && name == body) {
return;
+4 -4
View File
@@ -858,7 +858,7 @@ fn path_flange_dialog_follows_the_path() {
app.run("sketch.create", json!({"plane": {"origin": [0, 0, 15], "x_dir": [1, 0, 0], "y_dir": [0, 1, 0]}, "name": "Path"})).unwrap();
app.run("sketch.line", json!({"points": [[0, 0], [a, -a], [a + 30.0, -a]], "ids": ["p1", "p2"]})).unwrap();
app.run("sketch.finish", json!({})).unwrap();
let path = app.session.world_state().sketches.last().map(|ss| ss.feature);
let path = crate::open_meshing::session_world(&app.session).sketches.last().map(|ss| ss.feature);
// A newer sketch with the same line ids: the picks name the path's sketch (#3726).
app.run("sketch.create", json!({"plane": {"origin": [0, 0, 60], "x_dir": [1, 0, 0], "y_dir": [0, 1, 0]}, "name": "Other"})).unwrap();
app.run("sketch.line", json!({"points": [[50, 50], [60, 50], [60, 60]], "ids": ["p1", "p2"]})).unwrap();
@@ -1134,7 +1134,7 @@ fn flat_circles(app: &mut SolveApp, dialog: &Dialog, rect: Rect) -> Vec<egui::ep
#[test]
fn flat_perforations_match_export_centres_and_cutout_style() {
let (mut app, dialog) = perforated_flat();
let state = app.session.world_state();
let state = crate::open_meshing::session_world(&app.session);
let sheet = state.sheets.last().unwrap();
let holes = sheet.perforation_holes(&sheet.perforations[0]).unwrap();
let rect = Rect::from_min_size(Pos2::ZERO, vec2(1400.0, 900.0));
@@ -1156,7 +1156,7 @@ fn flat_perforations_match_export_centres_and_cutout_style() {
assert_eq!(flat_circles(&mut app, &dialog, rect).len(), 155);
assert_eq!(app.flat_view.perforations.as_ptr(), cached, "navigation reuses the centres");
app.run("sheet.perforate", json!({"diameter": 2, "pitch": 10})).unwrap();
let state = app.session.world_state();
let state = crate::open_meshing::session_world(&app.session);
let sheet = state.sheets.last().unwrap();
let expected: usize = sheet.perforations.iter().map(|p| sheet.perforation_holes(p).unwrap().len()).sum();
assert!(expected > 155);
@@ -1201,7 +1201,7 @@ fn hem_handle_runs_along_the_hem_to_its_tip() {
let commands = apply_commands(&app, &d).unwrap();
run_all(&mut app, commands);
// The 5 mm hem layer: its tip is the far end of the added layer, along the sheet.
let m = app.session.world_state().body(&body).unwrap().mesh();
let m = crate::open_meshing::session_world(&app.session).body(&body).unwrap().mesh();
// The straight part of the layer, past the fold.
let layer_z = |z: f64| if flipped { z > 2.505 } else { z < -0.01 };
let tip_y = m.positions.iter().filter(|v| layer_z(v.z)).map(|v| v.y).fold(f64::NEG_INFINITY, f64::max);
+13 -17
View File
@@ -6,7 +6,10 @@
use serde_json::{Value, json};
use solidcraft_engine::doc::thread_forms::{SteepFlank, ThreadForm, ThreadHand, ThreadTip};
use solidcraft_engine::doc::{Expr, FeatureKind, ThreadHandedness, ThreadLimit, ThreadRunout};
use solidcraft_engine::doc::{
Document, Expr, FeatureKind, TappedThreadFamily, ThreadHandedness, ThreadLimit, ThreadRunout, metric_thread_sizes, parse_metric_thread,
parse_tapped_thread, tapped_thread_sizes,
};
use solidcraft_engine::{Sel, Session};
use crate::dialogs::{Dialog, Kind, combo, edge_sel, face_sel, form_row, object_point, plane_sel, put, set_object_accepting};
@@ -707,7 +710,7 @@ mod contribution_tests {
);
let saved = h.app.session.doc.to_json();
let reopened = Session::new(Document::from_json(&saved).unwrap());
let world = reopened.world_state();
let world = crate::open_meshing::session_world(&reopened);
assert_eq!(world.threads.len(), 1);
assert_eq!(world.threads[0].designation, "M8.5x1");
assert!((world.threads[0].pitch - 1.).abs() < 1e-9);
@@ -879,7 +882,7 @@ mod contribution_tests {
let id = out["feature"].as_u64().unwrap();
let saved = app.session.doc.to_json();
let reopened = Session::new(Document::from_json(&saved).unwrap());
let world = reopened.world_state();
let world = crate::open_meshing::session_world(&reopened);
let t = &world.threads[0];
assert_eq!(t.designation, name);
assert!((t.major_diameter - major).abs() < 1e-9 && (t.pitch - pitch).abs() < 1e-9);
@@ -887,7 +890,7 @@ mod contribution_tests {
assert!(body.validity().is_empty());
let face = solidcraft_engine::kernel::cylinder_face_at(body, Vec3::new(10.0 + drill / 2.0, 10.0, 5.0)).unwrap();
assert!(face.internal && (2.0 * face.radius - drill).abs() < 1e-8);
let actual = body.tessellate(0.001).unwrap().measure().volume;
let actual = body.mesh(&solidcraft_engine::kernel::MeshTolerance::chord(0.001)).unwrap().measure().volume;
let expected = 4000.0 - std::f64::consts::PI * (drill / 2.0).powi(2) * 10.0;
assert!((actual - expected).abs() < 0.5, "{name}: {actual} versus {expected}");
// A saved explicit drill remains authoritative, including a legacy metric bore.
@@ -895,7 +898,7 @@ mod contribution_tests {
let solidcraft_engine::doc::FeatureKind::Hole { diameter, .. } = &mut doc.feature_mut(id).unwrap().kind else { panic!("not Hole") };
*diameter = format!("{} mm", drill + 0.05).as_str().into();
let reopened = Session::new(Document::from_json(&doc.to_json()).unwrap());
let world = reopened.world_state();
let world = crate::open_meshing::session_world(&reopened);
let face = solidcraft_engine::kernel::cylinder_face_at(&world.bodies[0].body, Vec3::new(10.0 + (drill + 0.05) / 2.0, 10.0, 5.0)).unwrap();
assert!((2.0 * face.radius - drill - 0.05).abs() < 1e-8);
}
@@ -964,26 +967,19 @@ mod contribution_tests {
#[test]
fn contribution_metric_designation_rendering_preserves_authored_legacy_and_custom_text() {
// The tapped hole's rows; the thread dialog's own tests cover its designations.
let context = egui::Context::default();
crate::fonts::setup_bundled_fonts(&context);
for original in ["M8x1.25", "M8x1", "M33x1", "M5×0.5", " m8x1.25 ", "invalid"] {
let mut hole = original.to_string();
let mut thread = original.to_string();
let mut suggested = None;
let mut modeled = false;
let mut full = true;
let mut custom = None;
let doc = Document::default();
let _ = context.run_ui(Default::default(), |ui| {
egui::Grid::new("authored_hole").show(ui, |ui| {
hole_rows(ui, &doc, &mut hole, &mut custom);
});
egui::Grid::new("authored_thread").show(ui, |ui| {
rows(ui, &mut thread, &mut suggested, &mut modeled, &mut full);
});
});
assert_eq!(hole, original);
assert_eq!(thread, original);
}
}
@@ -1004,7 +1000,7 @@ mod contribution_tests {
assert!((app.session.doc.eval(diameter, solidcraft_engine::doc::expr::Kind::Length).unwrap() - drill).abs() < 1e-9);
let saved = app.session.doc.to_json();
let reopened = Session::new(solidcraft_engine::doc::Document::from_json(&saved).unwrap());
let state = reopened.world_state();
let state = crate::open_meshing::session_world(&reopened);
assert_eq!(state.threads.len(), 1);
assert!((state.threads[0].pitch - pitch).abs() < 1e-9);
}
@@ -1040,7 +1036,7 @@ mod contribution_tests {
let result = app.run("solid.hole", json!({"position":[40,40,15],"thread":size.designation,"depth":10})).unwrap();
let saved = app.session.doc.to_json();
let reopened = Session::new(Document::from_json(&saved).unwrap());
let state = reopened.world_state();
let state = crate::open_meshing::session_world(&reopened);
assert_eq!(state.threads.len(), 1);
let t = &state.threads[0];
assert_eq!(t.designation, size.designation);
@@ -1052,7 +1048,7 @@ mod contribution_tests {
let c = solidcraft_engine::kernel::cylinder_face_at(body, Vec3::new(40.0 + size.tap_drill / 2.0, 40.0, z)).unwrap();
assert!(c.internal && (2.0 * c.radius - size.tap_drill).abs() < 1e-8);
}
let actual = body.tessellate(0.001).unwrap().measure().volume;
let actual = body.mesh(&solidcraft_engine::kernel::MeshTolerance::chord(0.001)).unwrap().measure().volume;
let removed = std::f64::consts::PI * (size.tap_drill / 2.0).powi(2) * 10.0;
assert!((actual - (96000.0 - removed)).abs() / removed < 0.002, "{}: {actual}", size.designation);
assert_eq!(reopened.doc.to_json(), saved);
@@ -1065,7 +1061,7 @@ mod contribution_tests {
};
*diameter = format!("{} mm", size.tap_drill + 0.03).as_str().into();
let reopened = Session::new(Document::from_json(&doc.to_json()).unwrap());
let state = reopened.world_state();
let state = crate::open_meshing::session_world(&reopened);
let c = solidcraft_engine::kernel::cylinder_face_at(
&state.bodies[0].body,
Vec3::new(40.0 + (size.tap_drill + 0.03) / 2.0, 40.0, 10.0),
@@ -881,7 +881,7 @@ fn midpoint_then_edge_creates_a_distance_in_one_undo_step() {
h.step(&json!({"key":"Enter"})).unwrap();
assert!(!crate::dim_view::editing());
assert_eq!(h.app.session.undo.len(), undo + 1);
let state = h.app.session.world_state();
let state = crate::open_meshing::session_world(&h.app.session);
let solved = state.sketch(h.app.session.active_sketch.unwrap()).unwrap();
assert!(solved.report.ok());
let sk = &solved.sketch;
@@ -907,7 +907,7 @@ fn midpoint_face_sketch() -> Harness {
h.app.finish_sketch();
h.app.run("solid.extrude", json!({"distance":10})).unwrap();
h.app.run("sketch.create", json!({"plane":{"face":[0,5,10]}})).unwrap();
let state = h.app.session.world_state();
let state = crate::open_meshing::session_world(&h.app.session);
let ss = state.sketch(h.app.session.active_sketch.unwrap()).unwrap();
let plane = ss.plane;
assert!(ss.sketch.curves.iter().any(|c| c.link.is_some()));
@@ -985,7 +985,7 @@ fn midpoint_pairs_dimension_sketch_and_projected_model_edges() {
if crate::dim_view::editing() {
h.step(&json!({"key":"Enter"})).unwrap();
}
let state = h.app.session.world_state();
let state = crate::open_meshing::session_world(&h.app.session);
let sk = &state.sketch(h.app.session.active_sketch.unwrap()).unwrap().sketch;
let c = sk.constraints.iter().rev().find(|c| c.kind.is_dimension()).expect("two MID picks commit a distance");
let ConstraintKind::Distance { p, q, .. } = c.kind else { panic!("expected midpoint distance for {projected:?}: {:?}", c.kind) };
@@ -1040,7 +1040,7 @@ fn midpoint_dimension_orientation_follows_the_pointer_before_placement() {
}
let mut h = midpoint_face_sketch();
if held_y {
let state = h.app.session.world_state();
let state = crate::open_meshing::session_world(&h.app.session);
let ss = state.sketch(h.app.session.active_sketch.unwrap()).unwrap();
let short = ss.sketch.curves.iter().find(|c| c.link.is_none()).unwrap().id.clone();
let bottom = ss
@@ -1062,7 +1062,7 @@ fn midpoint_dimension_orientation_follows_the_pointer_before_placement() {
h.settle();
}
if reused {
let state = h.app.session.world_state();
let state = crate::open_meshing::session_world(&h.app.session);
let ss = state.sketch(h.app.session.active_sketch.unwrap()).unwrap();
let mid_ref = |point: [f64; 2]| {
ss.sketch
@@ -1113,7 +1113,7 @@ fn midpoint_dimension_orientation_follows_the_pointer_before_placement() {
if crate::dim_view::editing() {
h.step(&json!({"key":"Enter"})).unwrap();
}
let state = h.app.session.world_state();
let state = crate::open_meshing::session_world(&h.app.session);
let sk = &state.sketch(h.app.session.active_sketch.unwrap()).unwrap().sketch;
let c = sk.constraints.iter().rev().find(|c| c.kind.is_dimension()).unwrap();
assert!(if horizontal { matches!(c.kind, ConstraintKind::DistanceX { .. }) } else { matches!(c.kind, ConstraintKind::DistanceY { .. }) });
@@ -84,7 +84,7 @@ fn face_off_the_profile_plane_turns_the_extent_to_that_face() {
// OK builds the extrude up to the face.
h.step(&json!({"widget": "OK"})).unwrap();
h.frames(4);
let state = h.app.session.world_state();
let state = crate::open_meshing::session_world(&h.app.session);
let volume: f64 = state.bodies.iter().filter_map(|b| solidcraft_engine::doc::kernel::measure(&b.body).ok()).map(|m| m.volume).sum();
// The box, the cylinder and the 10 x 10 x 20 extrude.
let expected = 8000.0 + std::f64::consts::PI * 25.0 * 30.0 + 2000.0;
@@ -117,7 +117,7 @@ fn vertex_and_curved_face_are_targets() {
/// The cylinder's side, at the middle of one of its triangles.
fn curved_hit(h: &Harness) -> Hit {
let st = h.app.session.world_state();
let st = crate::open_meshing::session_world(&h.app.session);
for b in &st.bodies {
let m = b.mesh();
for (t, f) in m.triangles.iter().zip(&m.tri_face) {
@@ -131,7 +131,7 @@ fn curved_hit(h: &Harness) -> Hit {
}
fn dialog_body(h: &Harness) -> String {
h.app.session.world_state().bodies.first().unwrap().name.clone()
crate::open_meshing::session_world(&h.app.session).bodies.first().unwrap().name.clone()
}
#[test]
+1 -1
View File
@@ -448,7 +448,7 @@ fn a_control_import_passes_its_matrix_and_name_on() {
assert_eq!(reply["ok"], true, "{reply}");
let mut sync = Session::default();
assert_eq!(reply["result"], sync.execute("file.insert_step", &params).unwrap(), "the window's reply is the session's");
let b = solidcraft_engine::view::bounds(&w.app.session);
let b = solidcraft_engine::view::bounds(&w.app.session).unwrap();
assert!((b.min.x - 100.0).abs() < 1e-6 && (b.max.x - 160.0).abs() < 1e-6, "moved by the matrix: {b:?}");
assert!(w.app.session.doc.components.iter().any(|c| c.name == "Bracket"), "named as asked");
let _ = std::fs::remove_dir_all(&dir);
+2 -2
View File
@@ -158,7 +158,7 @@ pub(crate) fn snap_hits(app: &SolveApp, proj: &Proj, pos: Pos2, hits: &mut Vec<H
if !snapping(app) || crate::drag_snap::suppressed() {
return;
}
let st = app.session.world_state();
let st = crate::open_meshing::session_world(&app.session);
let face = hits.iter().position(|h| matches!(h, Hit::Face { .. }));
let at_face = face.and_then(|i| match hits.get(i) {
Some(Hit::Face { body, index, .. }) => {
@@ -192,7 +192,7 @@ pub(crate) fn snap_hits(app: &SolveApp, proj: &Proj, pos: Pos2, hits: &mut Vec<H
/// The snap a face place sits on, if any.
fn snap_under(app: &SolveApp, body: &str, index: usize, point: Vec3) -> (Arc<Vec<Snap>>, Option<Snap>) {
let st = app.session.world_state();
let st = crate::open_meshing::session_world(&app.session);
let Some(b) = st.body(body) else { return (Arc::new(Vec::new()), None) };
let snaps = face_snaps(&b.mesh(), index);
let on = snaps.iter().find(|s| s.at.dist(point) <= 1e-9 * (1.0 + s.at.len())).cloned();
+5 -5
View File
@@ -92,7 +92,7 @@ fn vec3(v: &Value) -> Option<Vec3> {
/// The way the hole drills at `at`: as it was made (editing), else into the face there.
fn direction(s: &Session, d: &Dialog, at: Vec3) -> Option<Vec3> {
d.extra.get("direction").and_then(vec3).or_else(|| solidcraft_engine::doc::hole_direction_at(&s.world_state(), at))
d.extra.get("direction").and_then(vec3).or_else(|| solidcraft_engine::doc::hole_direction_at(&crate::open_meshing::session_world(s), at))
}
/// The face's frame at the place for the picked edges (and which reference inputs they are), found
@@ -114,7 +114,7 @@ fn frame(s: &Session, d: &mut Dialog) -> Option<(ReferenceFrame, Vec<usize>)> {
let picked: Vec<(usize, Vec3)> = edges.iter().enumerate().filter_map(|(k, e)| e.map(|p| (k, p))).collect();
let found = direction(s, d, at).and_then(|dir| {
let pts: Vec<(Vec3, Option<&str>)> = picked.iter().map(|(_, p)| (*p, None)).collect();
reference_frame(&s.world_state(), at, dir, &pts, true).ok().map(|f| (f, picked.iter().map(|(k, _)| *k).collect()))
reference_frame(&crate::open_meshing::session_world(s), at, dir, &pts, true).ok().map(|f| (f, picked.iter().map(|(k, _)| *k).collect()))
});
if let Kind::Hole { opts, .. } = &mut d.kind {
opts.refs.frame = Some(Framed { revision: s.revision, place: at, edges, frame: found.clone() });
@@ -147,7 +147,7 @@ pub(crate) fn sync(s: &Session, d: &mut Dialog) {
Some(sn) if sn.distance != refs.distance => {
let values: Option<Vec<f64>> =
picked.iter().map(|k| refs.distance.get(*k).and_then(|e| s.doc.eval(e, ValueKind::Length).ok())).collect();
if let Some(c) = values.and_then(|v| f.place(at, &v).ok()).filter(|c| on_reference_face(&s.world_state(), f, *c)) {
if let Some(c) = values.and_then(|v| f.place(at, &v).ok()).filter(|c| on_reference_face(&crate::open_meshing::session_world(s), f, *c)) {
to = c;
}
}
@@ -216,7 +216,7 @@ pub(crate) fn restore(s: &Session, d: &mut Dialog, references: &[HoleReference],
return;
}
let Some((i, at)) = place(d) else { return };
let st = s.world_state();
let st = crate::open_meshing::session_world(s);
let edges: Vec<(Vec3, Option<&str>)> = references.iter().enumerate().map(|(k, r)| (r.edge.0, names.get(k).map(String::as_str))).collect();
let found = direction(s, d, at).and_then(|dir| reference_frame(&st, at, dir, &edges, false).ok());
let mesh = found.as_ref().and_then(|f| st.body(&f.body)).map(|b| b.mesh());
@@ -262,7 +262,7 @@ pub(crate) fn restore(s: &Session, d: &mut Dialog, references: &[HoleReference],
/// found as it was made: OK then reports why).
fn nearest_edge(s: &Session, at: Vec3, p: Vec3) -> Option<Sel> {
let Some(Sel::Face { body, .. }) = crate::dialogs::face_sel(s, at) else { return None };
let st = s.world_state();
let st = crate::open_meshing::session_world(s);
let m = st.body(&body)?.mesh();
let to = |e: &Vec<Vec3>| e.windows(2).map(|w| p.dist_to_segment(w[0], w[1])).fold(f64::INFINITY, f64::min);
let index = m.edges.iter().enumerate().map(|(i, e)| (to(e), i)).filter(|(d, _)| d.is_finite()).min_by(|a, b| a.0.total_cmp(&b.0))?.1;
+1 -1
View File
@@ -1417,7 +1417,7 @@ impl SolveApp {
self.last_command = Some((spec.id.to_string(), spec.label.to_string()));
}
if let Some(mut t) = tools::Tool::for_command(id) {
let state = self.session.world_state();
let state = crate::open_meshing::session_world(&self.session);
let sketch = self.session.active_sketch.and_then(|s| state.sketch(s)).map(|ss| &ss.sketch);
if let Some(params) = tools::constraint_preselection(id, &self.session.selection, sketch) {
if self.run(id, params).is_ok() {
+1 -1
View File
@@ -1121,7 +1121,7 @@ fn create_sketch_from_empty_space_sketches_on_the_plane_clicked_next() {
let p = h.at(&json!({"plane": "XY"})).unwrap();
h.call("ui.click", json!({"x": p[0], "y": p[1]}));
h.frames(3);
let normal = h.app.session.active_sketch.and_then(|s| h.app.session.world_state().sketch(s).map(|s| s.plane.normal()));
let normal = h.app.session.active_sketch.and_then(|s| crate::open_meshing::session_world(&h.app.session).sketch(s).map(|s| s.plane.normal()));
assert!(normal.is_some_and(|n| n.dot(Vec3::Z).abs() > 0.999), "sketching on XY: {normal:?}");
}
+2 -2
View File
@@ -79,7 +79,7 @@ impl MovePreview {
.as_ref()
.is_none_or(|c| c.revision != app.session.revision || !Arc::ptr_eq(&c.document, &app.session.doc) || c.bodies != bodies)
{
let state = app.session.world_state();
let state = crate::open_meshing::session_world(&app.session);
let meshes: Vec<Arc<Mesh>> = bodies.iter().map(|n| state.body(n).map(|b| b.mesh())).collect::<Option<_>>()?;
if meshes.iter().map(|m| m.triangles.len()).sum::<usize>() > MAX_TRIANGLES {
return None;
@@ -180,7 +180,7 @@ mod tests {
// The Move feature on the same body: the preview's extent is the moved body's.
let mut s = app.session.scratch();
s.execute("solid.move", &cmds[0].1).unwrap();
let moved = s.world_state().body("Plate").unwrap().mesh().bounds();
let moved = crate::open_meshing::session_world(&s).body("Plate").unwrap().mesh().bounds();
assert!(near(built.bounds.min, moved.min) && near(built.bounds.max, moved.max), "{:?} vs {moved:?}", built.bounds);
// A copy keeps the original shown.
let copy = [("solid.move".to_string(), json!({"bodies": ["Plate"], "translate": [0, 0, 10], "copy": true}))];
+1 -1
View File
@@ -659,7 +659,7 @@ pub(crate) fn device(app: &mut SolveApp, rect: Rect, caps: NavCaps) {
if app.viewport.device_motion.is_none() && app.viewport.bridge_motion.is_none() {
return;
}
let sketch_view_is_initial = solidcraft_engine::view::open_sketch_plane(&app.session).is_some_and(|plane| {
let sketch_view_is_initial = solidcraft_engine::view::open_sketch_plane(&app.session).ok().flatten().is_some_and(|plane| {
let normal = plane.normal();
let mut initial = app.cam.looking_from(normal);
if normal.z.abs() > 0.999 {
+1 -1
View File
@@ -110,7 +110,7 @@ fn pick_timing_on_curved_models() {
let mut app = SolveApp::new(solidcraft_engine::Session::default(), crate::Services::default());
let component = app.run("file.insert_mesh", json!({"path": path.to_string_lossy()})).unwrap()["component"].clone();
app.run("component.activate", json!({"component":"root"})).unwrap();
let size = app.session.world_state().bodies[0].mesh_bounds().size();
let size = crate::open_meshing::session_world(&app.session).bodies[0].mesh_bounds().size();
let step = size.x.max(size.y) * 1.3;
for k in 1..25 {
app.run("occurrence.copy", json!({"component":component,"translate":[f64::from(k % 5) * step, f64::from(k / 5) * step, 0.0]})).unwrap();
+3 -3
View File
@@ -2654,7 +2654,7 @@ fn face_then_revolve_turns_the_face() {
for (cmd, p) in cmds {
app.run(&cmd, p).unwrap();
}
let st = app.session.world_state();
let st = crate::open_meshing::session_world(&app.session);
let made = st.bodies.iter().find(|b| b.name != "Body1").expect("the revolve makes a new body");
let v = solidcraft_engine::doc::kernel::measure(&made.body).unwrap().volume;
let want = std::f64::consts::PI * 40.0 * 40.0 * 20.0;
@@ -2685,7 +2685,7 @@ fn a_loft_from_a_profile_to_a_sketch_point_is_a_pyramid() {
for (cmd, p) in cmds {
app.run(&cmd, p).unwrap();
}
let st = app.session.world_state();
let st = crate::open_meshing::session_world(&app.session);
let v = solidcraft_engine::doc::kernel::measure(&st.bodies[0].body).unwrap().volume;
assert!((v - 4000.0).abs() < 4.0, "{v}");
}
@@ -2707,7 +2707,7 @@ fn a_pipe_cuts_from_its_window_and_keeps_the_cut_when_edited() {
let cmds = apply_commands(&app, &d).unwrap();
assert_eq!(cmds[0].1["operation"], "cut", "{:?}", cmds[0].1);
let volume = |app: &SolveApp| {
let st = app.session.world_state();
let st = crate::open_meshing::session_world(&app.session);
assert_eq!(st.bodies.len(), 1, "one body: {:?}", st.bodies.iter().map(|b| &b.name).collect::<Vec<_>>());
solidcraft_engine::doc::kernel::measure(&st.bodies[0].body).unwrap().volume
};
+2 -2
View File
@@ -186,7 +186,7 @@ fn target(app: &SolveApp, name: &str) -> Option<Target> {
if !valid_name(name) || app.session.model.is_stale() {
return None;
}
let world = app.session.world_state();
let world = crate::open_meshing::session_world(&app.session);
let mut matches = world.bodies.iter().filter(|body| app.session.body_visible(&body.name)).enumerate().filter(|(_, body)| body.name == name);
let (ordinal, body) = matches.next()?;
if matches.next().is_some() {
@@ -377,7 +377,7 @@ pub(super) fn reflection_controls(app: &mut SolveApp, ui: &mut egui::Ui) {
ui_kit::bounded_cell_editor(ui, ui.id().with("cpu_body_filter"), &mut app.render_workspace.view.cpu.body_filter, &hint, false, 128);
let selected = app.render_workspace.view.cpu.selected_body.clone();
let label = selected.clone().unwrap_or_else(|| app.tr("render.cpu_body_choose").to_string());
let world = app.session.world_state();
let world = crate::open_meshing::session_world(&app.session);
let filter = app.render_workspace.view.cpu.body_filter.to_lowercase();
let candidates: Vec<_> = world
.bodies
+1 -1
View File
@@ -1196,7 +1196,7 @@ fn native_cpu_controls_check_and_start_the_same_capture_and_keep_it_exportable()
let numeric_check = h.app.tr("cmd.render_preflight").to_string();
h.step(&json!({"widget":numeric_check})).unwrap();
assert_eq!(h.app.render_workspace.view.cpu.checked.as_ref().unwrap().request, numeric);
let reflected = h.app.session.world_state().bodies.first().unwrap().name.clone();
let reflected = crate::open_meshing::session_world(&h.app.session).bodies.first().unwrap().name.clone();
let reflection_section = format!("dialog:section:{}", h.app.tr("render.cpu_reflections"));
h.step(&json!({"click":{"handle":reflection_section}})).unwrap();
h.step(&json!({"click":{"handle":"render:cpu-reflection-body"}})).unwrap();
+1 -1
View File
@@ -416,7 +416,7 @@ fn whole_body_highlight_cost_from_the_models_own_triangles() {
h.app.run("solid.torus", json!({"major": 100, "minor": 10, "center": [k as f64 * 220.0, 0.0, 0.0], "body_name": name.clone()})).unwrap();
names.push(name);
}
let st = h.app.session.world_state();
let st = crate::open_meshing::session_world(&h.app.session);
let triangles: usize = st.bodies.iter().map(|b| b.mesh().triangles.len()).sum();
st.warm_meshes(|| true);
for b in &st.bodies {
+3 -3
View File
@@ -141,10 +141,10 @@ fn clicking_a_parameter_suggestion_fills_the_length_box() {
h.frames(1);
assert_focus(&h, 0);
assert_eq!(h.app.tool.as_ref().unwrap().dims[0].value, "width");
let sketch = |h: &Harness| h.app.session.world_state().sketch(h.app.session.active_sketch.unwrap()).unwrap().sketch.curves.len();
let sketch = |h: &Harness| crate::open_meshing::session_world(&h.app.session).sketch(h.app.session.active_sketch.unwrap()).unwrap().sketch.curves.len();
assert_eq!(sketch(&h), 0, "the click on the list placed no point");
h.step(&json!({"key": "Enter"})).unwrap();
let state = h.app.session.world_state();
let state = crate::open_meshing::session_world(&h.app.session);
let sk = &state.sketch(h.app.session.active_sketch.unwrap()).unwrap().sketch;
assert_eq!(sk.curves.len(), 1);
let length = sk.shape(0).map(|s| match s {
@@ -162,7 +162,7 @@ fn two_point_circle_takes_a_typed_diameter() {
assert_eq!(h.app.tool.as_ref().unwrap().dims.iter().map(|b| b.label).collect::<Vec<_>>(), ["Diameter"]);
type_value(&mut h, "30");
h.step(&json!({"key": "Enter"})).unwrap();
let state = h.app.session.world_state();
let state = crate::open_meshing::session_world(&h.app.session);
let sk = &state.sketch(h.app.session.active_sketch.unwrap()).unwrap().sketch;
assert_eq!(sk.curves.len(), 1, "{:?}", h.app.status);
assert!(sk.radius(0).is_some_and(|r| (r - 15.0).abs() < 1e-6), "{:?}", sk.radius(0));
@@ -309,7 +309,7 @@ fn a_large_sketch_drawn_first_leaves_a_later_sketch_its_curves() {
h.app.run("sketch.line", json!({"points":[[-30,-30],[-10,-20]],"ids":["new"]})).unwrap();
h.settle();
let proj = projection(&h.app, h.app.viewport.rect.unwrap());
let state = h.app.session.world_state();
let state = crate::open_meshing::session_world(&h.app.session);
let drawing = state.sketches.iter().find(|s| s.name == "Drawing").unwrap().clone();
let after = state.sketches.iter().find(|s| s.name == "After").unwrap().clone();
drop(state);
+1 -1
View File
@@ -896,5 +896,5 @@ fn circular_quantity_keeps_its_expression_when_reopened_and_follows_parameter_ch
assert!(crate::sketch_modify_ui::edit_pattern(&mut h.app, out["pattern"].as_str().unwrap()));
assert!(matches!(&h.app.dialog.as_ref().unwrap().kind, Kind::SketchModify(Mod::PatternCirc { count, .. }) if count=="HoleAmount"));
let sid = h.app.session.active_sketch.unwrap();
assert_eq!(h.app.session.world_state().sketch(sid).unwrap().sketch.curves.len(), 7);
assert_eq!(crate::open_meshing::session_world(&h.app.session).sketch(sid).unwrap().sketch.curves.len(), 7);
}
+2 -2
View File
@@ -529,7 +529,7 @@ pub fn glyph_deletion(sk: &solidcraft_engine::sketch::Sketch, id: &str) -> Vec<s
/// [`glyph_deletion`] in the active sketch.
pub fn active_glyph_deletion(app: &SolveApp, id: &str) -> Vec<solidcraft_engine::Sel> {
let st = app.session.world_state();
let st = crate::open_meshing::session_world(&app.session);
match active_sketch(app).and_then(|s| st.sketch(s)) {
Some(ss) => glyph_deletion(&ss.sketch, id),
None => vec![solidcraft_engine::Sel::SketchConstraint { id: id.to_string() }],
@@ -1161,7 +1161,7 @@ fn extend_preview_piece(app: &SolveApp, sketch: u64, id: &str) -> Option<Vec<Vec
if active_sketch(app) != Some(sketch) {
return None;
}
let st = app.session.world_state();
let st = crate::open_meshing::session_world(&app.session);
let sk = st.sketch(sketch)?;
let hover_pos = hover.0;
let ci = sk.sketch.curve_index(id)?;
+1 -1
View File
@@ -132,7 +132,7 @@ fn a_new_start_object_decides_the_operation_again() {
drag_to(&mut h, 5.0);
assert_eq!(state(&h), (0, CUT, true), "into the box: Cut");
// Start: From Object, the box's bottom face (as the Start choice and a pick there make it).
let st = h.app.session.world_state();
let st = crate::open_meshing::session_world(&h.app.session);
let b = st.bodies.first().expect("the box");
let bottom =
(0..64).find(|i| b.body.planar_face(*i).is_some_and(|p| p.normal().z < -0.99 && p.origin.z.abs() < 1e-9)).expect("the box's bottom face");
+1 -1
View File
@@ -86,7 +86,7 @@ fn the_construction_line_is_the_symmetry_line_whatever_the_pick_order() {
fn three_selected_lines_take_the_construction_line_as_the_symmetry_line() {
use solidcraft_engine::Sel::SketchCurve;
let h = between_two_lines();
let state = h.app.session.world_state();
let state = crate::open_meshing::session_world(&h.app.session);
let sk = &state.sketch(h.app.session.active_sketch.unwrap()).unwrap().sketch;
let sel = |ids: [&str; 3]| ids.map(|id| SketchCurve { id: id.into(), sketch: None });
for ids in [["mid", "left", "right"], ["right", "mid", "left"]] {
+3 -3
View File
@@ -315,7 +315,7 @@ pub fn on_click(app: &mut SolveApp, proj: &Proj, pos: Pos2, shift: bool) {
if id.starts_with("mid:") { return Some(id); }
// An earlier dimension may already have materialized this midpoint.
// Picking that point retains the same midpoint intent on subsequent dimensions.
let state = app.session.world_state();
let state = crate::open_meshing::session_world(&app.session);
let ss = app.session.active_sketch.and_then(|sid| state.sketch(sid))?;
let point = ss.sketch.resolve_point(&id)?;
ss.sketch.constraints.iter().find_map(|c| match c.kind {
@@ -326,7 +326,7 @@ pub fn on_click(app: &mut SolveApp, proj: &Proj, pos: Pos2, shift: bool) {
})
}).filter(|id| {
let Some(line) = id.strip_prefix("mid:") else { return false };
let state = app.session.world_state();
let state = crate::open_meshing::session_world(&app.session);
app.session
.active_sketch
.and_then(|sid| state.sketch(sid))
@@ -731,7 +731,7 @@ fn run_pick(app: &mut SolveApp, t: &Tool) {
"sketch.constraint.midpoint" => json!({"a": k[0], "b": k[1]}),
"sketch.constraint.symmetry" => {
let params = {
let state = app.session.world_state();
let state = crate::open_meshing::session_world(&app.session);
symmetry_args(&k, app.session.active_sketch.and_then(|id| state.sketch(id)).map(|ss| &ss.sketch))
};
let Some(params) = params else {
+1 -1
View File
@@ -81,7 +81,7 @@ fn world_rays_pick_without_a_pixel() {
#[test]
fn a_hidden_body_is_not_picked() {
let mut h = top_view();
let name = h.app.session.world_state().bodies[0].name.clone();
let name = crate::open_meshing::session_world(&h.app.session).bodies[0].name.clone();
h.app.session.visibility.hidden_bodies.push(name);
let r = pick(&mut h, json!({"pixels": [[480, 300]]}));
assert_eq!(r["hits"][0], serde_json::Value::Null);
+24 -23
View File
@@ -375,10 +375,11 @@ pub(crate) fn visible_sketches(app: &SolveApp) -> Vec<u64> {
/// now (see [`solidcraft_engine::view::bounds`]).
pub fn view_bounds(app: &SolveApp) -> solidcraft_engine::geom::Aabb3 {
// Bodies still being meshed after an open frame the view by their outlines.
// A model that cannot be placed shows no placed bodies (see `open_meshing::session_world`).
if crate::open_meshing::framing(app) {
return solidcraft_engine::view::unmeshed_bounds(&app.session);
return solidcraft_engine::view::unmeshed_bounds(&app.session).unwrap_or(solidcraft_engine::geom::Aabb3::EMPTY);
}
solidcraft_engine::view::bounds(&app.session)
solidcraft_engine::view::bounds(&app.session).unwrap_or(solidcraft_engine::geom::Aabb3::EMPTY)
}
/// The middle of the shown bodies the view shows part of: what a cursor orbit begun over empty
@@ -390,7 +391,7 @@ pub(crate) fn in_view_centre(app: &SolveApp, proj: &Proj) -> Option<Vec3> {
let b = body_bounds(app, &st)
.iter()
.filter(|(name, b)| !b.is_empty() && app.session.body_visible(name) && in_view(proj, b))
.fold(solidcraft_engine::geom::Aabb3::EMPTY, |a, (_, b)| a.union(b)).unwrap_or(solidcraft_engine::geom::Aabb3::EMPTY);
.fold(solidcraft_engine::geom::Aabb3::EMPTY, |a, (_, b)| a.union(b));
(!b.is_empty()).then(|| b.center())
}
@@ -5687,7 +5688,7 @@ mod tests {
session.execute("solid.box", &json!({"length": 30, "width": 30, "height": 10}))?;
let app = SolveApp::new(session, crate::Services::default());
let mut scene = GpuScene::default();
ground_shadow(&mut scene, &app, &app.session.world_state());
ground_shadow(&mut scene, &app, &crate::open_meshing::session_world(&app.session));
assert!(scene.trans.is_empty(), "the ground shadow must not trigger order-independent transparency");
assert_eq!(scene.shadow.len(), 48 * 5 * 3 * crate::gpu::TRI_SIZE, "all shadow rings are retained");
let shadow_alpha = |v: &[u8]| {
@@ -5712,7 +5713,7 @@ mod tests {
s.execute("sketch.finish", &json!({})).unwrap();
let mut app = SolveApp::new(s, crate::Services::default());
app.fit_view();
let st = app.session.world_state();
let st = crate::open_meshing::session_world(&app.session);
let wire = &st.sketches[0].sketch.wires[0];
let at = (wire.pts[0] + wire.pts[1]) * 0.5;
let proj = projection(&app, Rect::from_min_size(Pos2::ZERO, vec2(800., 600.)));
@@ -6760,7 +6761,7 @@ mod tests {
fn placed_three_times_is_drawn_from_one_copy(part: (&str, &str), flat: bool) {
let app = three_placed_parts(part);
let st = app.session.world_state();
let st = crate::open_meshing::session_world(&app.session);
assert_eq!(st.bodies.len(), 3);
eprintln!("DBG before {}", st.bodies.iter().filter(|b| b.cached_mesh().is_some()).count());
let scene = build_scene(&app);
@@ -6829,7 +6830,7 @@ mod tests {
assert_eq!(scene.tris.is_empty(), style == 2, "wireframe draws only the edges");
}
let proj = projection(&app, Rect::from_min_size(Pos2::ZERO, vec2(800., 600.)));
for body in &app.session.world_state().bodies {
for body in &crate::open_meshing::session_world(&app.session).bodies {
let mesh = body.mesh();
assert_eq!(mesh.edges.len(), 12, "the placed mesh keeps its outlines");
for (i, edge) in mesh.edges.iter().enumerate() {
@@ -6870,7 +6871,7 @@ mod tests {
app.dialog = None;
let hits = pick_cached(&mut app, &proj, pos);
assert!(!hits.iter().any(|h| matches!(h, Hit::Edge { .. } | Hit::Vertex { .. })));
assert!(app.session.world_state().bodies[0].mesh().display_only_edges);
assert!(crate::open_meshing::session_world(&app.session).bodies[0].mesh().display_only_edges);
}
/// Manual GPU benchmark: 25 placements of the mesh `SOLIDCRAFT_BENCH_MESH` (an STL, OBJ or
@@ -6888,7 +6889,7 @@ mod tests {
app.set_origin_shown(false);
let component = app.run("file.insert_mesh", json!({"path": path.to_string_lossy()})).unwrap()["component"].clone();
app.run("component.activate", json!({"component":"root"})).unwrap();
let size = app.session.world_state().bodies[0].mesh_bounds().size();
let size = crate::open_meshing::session_world(&app.session).bodies[0].mesh_bounds().size();
let step = size.x.max(size.y) * 1.3;
for k in 1..25 {
app.run("occurrence.copy", json!({"component":component,"translate":[f64::from(k % 5) * step, f64::from(k / 5) * step, 0.0]})).unwrap();
@@ -6929,7 +6930,7 @@ mod tests {
drop(build_scene(&app));
eprintln!("scene built in {:.0} ms, again in {:.0} ms", (t1 - t0).as_secs_f64() * 1e3, t1.elapsed().as_secs_f64() * 1e3);
let proj = projection(&app, rect);
let st = app.session.world_state();
let st = crate::open_meshing::session_world(&app.session);
let seen = st.bodies.iter().filter(|b| proj.to_screen(b.mesh_bounds().center()).is_some_and(|p| rect.contains(p))).count();
eprintln!("{w} x {h} px: {seen} of {} placements in view", st.bodies.len());
// `SOLIDCRAFT_BENCH_CLOSE=1`: close up on the middle placement, most of the others out of view.
@@ -7085,7 +7086,7 @@ mod tests {
doc.add_feature(FeatureKind::MeshImport { file: "grid.stl".into(), meshes }, Some("Grid")).unwrap();
let mut app = SolveApp::new(solidcraft_engine::Session::new(doc), crate::Services::default());
app.viewport.gpu = Some(GpuTarget { format: egui_wgpu::wgpu::TextureFormat::Rgba8Unorm, depth: None, samples: 1, tess: false, oit: false });
let (mesh, _) = app.session.world_state().bodies[0].mesh_in_place();
let (mesh, _) = crate::open_meshing::session_world(&app.session).bodies[0].mesh_in_place();
assert!(mesh.triangles.len() >= INSTANCE_ALONE);
warm_shared_tris(&mesh);
let warmed = WARMED.lock().unwrap().iter().find(|(m, _)| std::sync::Arc::ptr_eq(m, &mesh)).map(|(_, t)| t.clone()).unwrap();
@@ -7132,7 +7133,7 @@ mod tests {
#[test]
fn a_large_mesh_seen_small_is_drawn_from_its_levels_of_detail() {
let mut app = large_scan();
let (mesh, _) = app.session.world_state().bodies[0].mesh_in_place();
let (mesh, _) = crate::open_meshing::session_world(&app.session).bodies[0].mesh_in_place();
assert!(mesh.triangles.len() >= INSTANCE_ALONE);
let key = scene_key(&app);
let first = build_scene(&app);
@@ -7257,7 +7258,7 @@ mod tests {
app.fit_view();
let rect = Rect::from_min_size(Pos2::ZERO, vec2(800.0, 600.0));
let proj = projection(&app, rect);
let st = app.session.world_state();
let st = crate::open_meshing::session_world(&app.session);
// The top face's centre of each placement, from its moved box corners.
let tops: Vec<(String, Vec3)> = st
.bodies
@@ -7337,7 +7338,7 @@ mod tests {
let differ = instanced.chunks(4).zip(one_by_one.chunks(4)).filter(|(a, b)| a.iter().zip(b.iter()).any(|(x, y)| x.abs_diff(*y) > 24)).count();
assert!(differ * 200 < drawn, "{part:?}: {differ} of {drawn} pixels differ");
// Close up on the turned placement, the others out of view: what is in view is all drawn.
let turned = app.session.world_state().bodies[2].mesh_bounds();
let turned = crate::open_meshing::session_world(&app.session).bodies[2].mesh_bounds();
app.cam.target = turned.center();
app.cam.distance = turned.diagonal() * 0.75;
let proj = projection(&app, rect);
@@ -8222,7 +8223,7 @@ mod tests {
fn whole_body_highlights_reuse_the_models_own_triangles() {
let mut app = cylinder_app();
app.viewport.gpu = Some(GpuTarget { format: egui_wgpu::wgpu::TextureFormat::Rgba8Unorm, depth: None, samples: 1, tess: true, oit: false });
let body = app.session.world_state().bodies[0].clone();
let body = crate::open_meshing::session_world(&app.session).bodies[0].clone();
let plan = body.display_plan().expect("the test body is drawn from a plan");
app.viewport.hover_bodies = vec![body.name.clone()];
let hovered = build_highlight(&app);
@@ -8257,7 +8258,7 @@ mod tests {
s.execute("occurrence.copy", &json!({"component": part, "translate": [40, 0, 0]})).unwrap();
let mut assembly = SolveApp::new(s, crate::Services::default());
assembly.set_origin_shown(false);
let names: Vec<String> = assembly.session.world_state().bodies.iter().map(|b| b.name.clone()).collect();
let names: Vec<String> = crate::open_meshing::session_world(&assembly.session).bodies.iter().map(|b| b.name.clone()).collect();
assert_eq!(names.len(), 2, "two placements of the part");
build_scene(&assembly);
assembly.viewport.hover_bodies = names;
@@ -9305,10 +9306,10 @@ mod tests {
app.preferences.cosmetic_threads = true;
assert_ne!(scene_key(&app), off_key, "toggling invalidates the scene without a camera move");
assert!(count(build_scene(&app)) > 20);
assert!(crate::control::view_scene(&app, Default::default()).lines.iter().any(|l| l.width == 0.85));
assert!(crate::control::view_scene(&app, Default::default()).unwrap().lines.iter().any(|l| l.width == 0.85));
app.preferences.cosmetic_threads = false;
assert_eq!(count(build_scene(&app)), 0);
assert!(!crate::control::view_scene(&app, Default::default()).lines.iter().any(|l| l.width == 0.85));
assert!(!crate::control::view_scene(&app, Default::default()).unwrap().lines.iter().any(|l| l.width == 0.85));
app.preferences.cosmetic_threads = true;
app.session.selection = vec![Sel::Face { body: "Body1".into(), index: 0, point: Vec3::new(3., 0., 5.) }];
let selection = app.session.selection.clone();
@@ -9359,7 +9360,7 @@ mod tests {
crate::prefs::set(&mut app, &json!({"cosmetic_threads":true})).unwrap();
assert_ne!(scene_key(&app), key);
assert!(count(&app) > 20, "internal={internal}");
assert!(crate::control::view_scene(&app, Default::default()).lines.iter().any(|l| l.width == 0.85));
assert!(crate::control::view_scene(&app, Default::default()).unwrap().lines.iter().any(|l| l.width == 0.85));
crate::prefs::set(&mut app, &json!({"cosmetic_threads":false})).unwrap();
assert_eq!(scene_key(&app), key);
assert_eq!(count(&app), 0);
@@ -10133,11 +10134,11 @@ mod grid_plane_tests {
app.cam.target = Vec3::ZERO;
app.cam.distance = 100.0;
// The grid's centre, as both draw it: the sketch plane's, round the view's target.
let plane = solidcraft_engine::view::open_sketch_plane(&app.session).unwrap();
let plane = solidcraft_engine::view::open_sketch_plane(&app.session).unwrap().unwrap();
let (_, major) = grid_step(app.cam.half_height());
let (centre, _, _) = solidcraft_engine::view::sketch_grid_frame(&plane, app.cam.target, major);
// How far the picture's grid reaches from that centre.
let scene = solidcraft_engine::view::scene(&app.session, &app.cam);
let scene = solidcraft_engine::view::scene(&app.session, &app.cam).unwrap();
let picture = scene.grid.iter().flat_map(|l| l.points.iter()).map(|p| (*p - centre).len()).fold(0.0_f64, f64::max);
// How far the window's grid reaches: the same points, as bytes.
let under = build_sketch_scene(&app, &projection(&app, rect)).under;
@@ -10287,7 +10288,7 @@ mod grid_plane_tests {
app.fit_view();
app.cancel_view_animation();
app.cam.distance = 10_002.0;
assert_eq!(app.session.world_state().bodies.len(), 2);
assert_eq!(crate::open_meshing::session_world(&app.session).bodies.len(), 2);
let camera = app.cam;
let rect = Rect::from_min_size(Pos2::ZERO, vec2(1280.0, 720.0));
let original = projection(&app, rect).vp;
@@ -10296,7 +10297,7 @@ mod grid_plane_tests {
for perspective in [false, true, false] {
app.ui.perspective = perspective;
let proj = projection(&app, rect);
for body in &app.session.world_state().bodies {
for body in &crate::open_meshing::session_world(&app.session).bodies {
let (mesh, _) = body.mesh_in_place();
assert_eq!(mesh.positions.len(), 3);
for p in &mesh.positions {
@@ -278,11 +278,11 @@ fn theme_switches_refresh_cached_viewport_colours_without_remeshing() {
h.app.run("solid.box", json!({"length": 20, "width": 15, "height": 10})).unwrap();
h.app.viewport.gpu =
Some(super::GpuTarget { format: egui_wgpu::wgpu::TextureFormat::Rgba8Unorm, depth: None, samples: 1, tess: false, oit: false });
let body = h.app.session.world_state().bodies[0].name.clone();
let body = crate::open_meshing::session_world(&h.app.session).bodies[0].name.clone();
h.app.session.selection = vec![solidcraft_engine::Sel::Body { name: body.clone() }];
h.frame();
let revision = h.app.session.revision;
let mesh = h.app.session.world_state().bodies[0].cached_mesh().unwrap();
let mesh = crate::open_meshing::session_world(&h.app.session).bodies[0].cached_mesh().unwrap();
let mut previous: Option<(u64, u64, u64)> = None;
for theme in ["dark", "gruvbox-dark", "blueprint", "dark", "light", "solidcraft-95", "cupertino", "light"] {
let result = h.call("ui.prefs", json!({"theme": theme}));
@@ -301,7 +301,7 @@ fn theme_switches_refresh_cached_viewport_colours_without_remeshing() {
let colour = super::c4(crate::theme::Tokens::get().body);
assert!(vertices.iter().all(|v| vertex_color(&scene, v) == colour), "{theme}: stale body colour");
assert_eq!(h.app.session.revision, revision, "theme changes must not edit the design");
assert!(std::sync::Arc::ptr_eq(&mesh, &h.app.session.world_state().bodies[0].cached_mesh().unwrap()));
assert!(std::sync::Arc::ptr_eq(&mesh, &crate::open_meshing::session_world(&h.app.session).bodies[0].cached_mesh().unwrap()));
h.frame();
assert_eq!((h.app.viewport.key, h.app.viewport.sketch_key, h.app.viewport.hl_key), keys);
assert!(h.app.viewport.slot.lock().unwrap().is_none(), "an unchanged theme reuses its scene");
+1 -1
View File
@@ -14,7 +14,7 @@ fn the_coincident_tool_lays_one_circle_on_another() {
h.step(&json!({"start":"sketch.constraint.coincident"})).unwrap();
h.step(&json!({"click":{"world":[0,10,0]}})).unwrap();
h.step(&json!({"click":{"world":[30,6,0]}})).unwrap();
let st = h.app.session.world_state();
let st = h.app.session.world_state().unwrap();
let sk = &st.sketch(h.app.session.active_sketch.unwrap()).unwrap().sketch;
use solidcraft_engine::sketch::ConstraintKind;
let laid = sk.constraints.iter().filter(|k| matches!(k.kind, ConstraintKind::Concentric { .. } | ConstraintKind::Equal { .. })).count();
@@ -99,7 +99,7 @@ fn two_lines() -> Harness {
}
fn line_ends(h: &Harness, id: &str) -> (Vec2, Vec2) {
let state = h.app.session.world_state();
let state = h.app.session.world_state().unwrap();
let sk = &state.sketch(h.app.session.active_sketch.unwrap()).unwrap().sketch;
let p = |end: &str| sk.point(sk.resolve_point(&format!("{id}.{end}")).unwrap()).unwrap();
(p("start"), p("end"))
@@ -138,13 +138,13 @@ fn midpoint_tool_takes_two_preselected_lines() {
}
fn midpoint_ids(h: &Harness) -> Vec<String> {
let state = h.app.session.world_state();
let state = h.app.session.world_state().unwrap();
let sk = &state.sketch(h.app.session.active_sketch.unwrap()).unwrap().sketch;
sk.constraints.iter().filter(|c| matches!(c.kind, ConstraintKind::Midpoint { .. })).map(|c| c.id.clone()).collect()
}
fn point_count(h: &Harness) -> usize {
let state = h.app.session.world_state();
let state = h.app.session.world_state().unwrap();
state.sketch(h.app.session.active_sketch.unwrap()).unwrap().sketch.points.len()
}
+3 -3
View File
@@ -163,15 +163,15 @@ fn sketched_and_extruded(start: impl FnOnce(&mut Harness)) -> (V3, V3, solidcraf
}
assert!((height(&h) - 40.0).abs() < 1e-6, "stood up: {}", height(&h));
assert_ne!(h.app.session.active_component, 0, "the import's component is the one worked in");
let before: Vec<String> = h.app.session.world_state().bodies.iter().map(|b| b.name.clone()).collect();
let before: Vec<String> = h.app.session.world_state().unwrap().bodies.iter().map(|b| b.name.clone()).collect();
start(&mut h);
h.frames(2);
let sketch = h.app.session.active_sketch.expect("a sketch is open");
let normal = h.app.session.world_state().sketch(sketch).unwrap().plane.normal();
let normal = h.app.session.world_state().unwrap().sketch(sketch).unwrap().plane.normal();
let back = h.app.cam.back();
h.app.run("sketch.rectangle.two_point", json!({"p0": [200, 0], "p1": [220, 10]})).unwrap();
h.app.run("solid.extrude", json!({"distance": 15})).unwrap();
let world = h.app.session.world_state();
let world = h.app.session.world_state().unwrap();
let made: Vec<_> = world.bodies.iter().filter(|b| !before.contains(&b.name)).collect();
assert_eq!(made.len(), 1);
(normal, back, made[0].mesh().bounds())
+1 -1
View File
@@ -14,7 +14,7 @@ fn sketch() -> Harness {
/// A line's first point: the circle's centre, or a point of its own.
fn starts_at_centre(h: &Harness) -> bool {
let st = h.app.session.world_state();
let st = h.app.session.world_state().unwrap();
let sk = &st.sketch(h.app.session.active_sketch.unwrap()).unwrap().sketch;
let centre = sk.resolve_point("c1.center").unwrap();
sk.curves.iter().any(|c| matches!(c.kind, solidcraft_engine::sketch::CurveKind::Line { a, b } if a == centre || b == centre))