diff --git a/apps/solidcraft-cli/tests/straight_edges_after_cuts.rs b/apps/solidcraft-cli/tests/straight_edges_after_cuts.rs new file mode 100644 index 00000000..a52a24a1 --- /dev/null +++ b/apps/solidcraft-cli/tests/straight_edges_after_cuts.rs @@ -0,0 +1,88 @@ +//! Straight edges stay one edge after many cuts (#1049). The owner's design +//! (`tests/action_matrix/designs/crazy-geom-cuts.solidcraft`, saved by 0.10.0): five extrudes, +//! joins and cuts, over four sketches, three of them on faces of the body with the faces' +//! edges projected. Cuts used to leave the straight edges of the walls they sliced split at +//! vertices nothing else used, so an edge that is one straight line was drawn (and lit on hover) +//! as many pieces, and a sketch on such a face projected every piece. + +use std::collections::HashMap; +use std::path::{Path, PathBuf}; + +use serde_json::json; +use solidcraft_engine::Session; +use solidcraft_engine::doc::kernel::{FaceSurface, face_surface}; +use solidcraft_engine::geom::Vec3; + +fn design() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")).join("../../tests/action_matrix/designs/crazy-geom-cuts.solidcraft") +} + +fn open(path: &Path) -> Session { + let mut s = Session::default(); + s.execute("doc.open", &json!({"path": path.to_string_lossy()})).unwrap_or_else(|e| panic!("{}: {e}", path.display())); + s +} + +/// The design built from scratch: saved without cached geometry and opened again. +fn rebuilt() -> Session { + let s = open(&design()); + let dir = std::env::temp_dir().join(format!("solidcraft-straight-edges-{}", std::process::id())); + std::fs::create_dir_all(&dir).unwrap(); + let path = dir.join("crazy-geom-cuts.json"); + let mut t = s.scratch(); + t.execute("file.save", &json!({"path": path.to_string_lossy(), "format": "json"})).unwrap(); + let out = open(&path); + let _ = std::fs::remove_dir_all(&dir); + out +} + +fn errors(s: &Session) -> Vec<(u64, String)> { + s.model.results.iter().filter_map(|r| r.error.clone().map(|e| (r.id, e))).collect() +} + +fn key(v: Vec3) -> [i64; 3] { + [v.x, v.y, v.z].map(|c| (c * 1e5).round() as i64) +} + +#[test] +fn straight_edges_are_one_edge_after_many_cuts() { + for (what, s) in [("as saved", open(&design())), ("built from scratch", rebuilt())] { + assert_eq!(errors(&s), vec![], "{what}: every feature evaluates"); + let st = s.world_state(); + let bodies: Vec<_> = st.bodies.iter().filter(|b| !b.body.is_mesh()).collect(); + assert_eq!(bodies.len(), 1, "{what}: one body"); + let body = &bodies[0].body; + let mesh = body.tessellate(0.01).unwrap(); + // Every edge is drawn as one polyline; a straight one as one segment. + let mut at: HashMap<[i64; 3], Vec> = HashMap::new(); + for (i, e) in mesh.edges.iter().enumerate() { + let (Some(a), Some(z)) = (e.first(), e.last()) else { panic!("{what}: edge {i} is empty") }; + at.entry(key(*a)).or_default().push(i); + at.entry(key(*z)).or_default().push(i); + } + let straight = |i: usize| mesh.edges.get(i).filter(|e| e.len() == 2).and_then(|e| (e[1] - e[0]).normalized()); + let split: Vec<_> = at + .iter() + .filter(|(_, es)| match es.as_slice() { + [a, b] => straight(*a).zip(straight(*b)).is_some_and(|(u, w)| u.dot(w).abs() > 1. - 1e-9), + _ => false, + }) + .map(|(k, _)| k.map(|c| c as f64 / 1e5)) + .collect(); + assert_eq!(split, Vec::<[f64; 3]>::new(), "{what}: straight edges split where nothing else meets them"); + // No two faces side by side in one plane. + let plane = |f: usize| match face_surface(body, f) { + Some(FaceSurface::Plane { normal, point }) => Some((normal, normal.dot(point))), + _ => None, + }; + for (i, faces) in mesh.edge_faces.iter().enumerate() { + if let [a, b] = faces.as_slice() + && a != b + && let (Some((na, da)), Some((nb, db))) = (plane(*a as usize), plane(*b as usize)) + { + assert!(na.dot(nb) < 1. - 1e-9 || (da - db).abs() > 1e-6, "{what}: faces {a} and {b} meet at edge {i} in one plane"); + } + } + assert_eq!((body.face_count(), body.edge_count(), body.vertex_count()), (35, 107, 71), "{what}: faces, edges and vertices"); + } +} diff --git a/crates/kernel/src/heal.rs b/crates/kernel/src/heal.rs index 20a93e58..ee1b4259 100644 --- a/crates/kernel/src/heal.rs +++ b/crates/kernel/src/heal.rs @@ -318,12 +318,13 @@ fn merge_collinear(shell: &mt::Shell, trace: &mut Trace) -> Option { es.push(e.clone()); continue; }; - // The run's pieces become its joined edge, in this wire's direction. - if done.insert(r) - && let Some((line, pos)) = joined.get(r) - { + // The run's pieces become its joined edge, in this wire's direction: once each + // way, as a seam runs along its wire both ways. + if let Some((line, pos)) = joined.get(r) { let forward = pos.get(&e.front().id()) < pos.get(&e.back().id()); - es.push(if forward { line.clone() } else { line.inverse() }); + if done.insert((r, forward)) { + es.push(if forward { line.clone() } else { line.inverse() }); + } } } wires.push(mt::Wire::from(es)); @@ -547,9 +548,9 @@ pub(crate) fn heal_traced(solid: Solid, size: f64, keep: &[Vec]) -> (Solid healed(solid, size, keep, true, false) } -/// [`heal_traced`] that only merges coplanar neighbouring faces, leaving every edge, curve and -/// surface as it is: for results whose geometry is already exact (a prism boolean's), where -/// joining edges would change more than the faces split in pieces. +/// [`heal_traced`] that only merges coplanar neighbouring faces and joins the straight edges +/// they leave split in line (#1049), leaving every curve and surface as it is: for results +/// whose geometry is already exact (a prism boolean's). pub(crate) fn merge_faces_traced(solid: Solid, size: f64, keep: &[Vec]) -> (Solid, Option) { healed(solid, size, keep, true, true) } @@ -632,7 +633,9 @@ fn heal_shells(solid: &Solid, size: f64, keep: &[Vec], traced: bool, faces from = groups.iter().map(|g| g.iter().filter_map(|i| from.get(*i)).flatten().copied().collect()).collect(); changed = true; } - if !faces_only && let Some(m) = merge_collinear(&cur, &mut trace) { + // Straight edges the merged pieces leave split at vertices nothing else uses join + // into one, after any boolean (#1049): later features meet one edge, not the pieces. + if let Some(m) = merge_collinear(&cur, &mut trace) { cur = m; changed = true; } diff --git a/crates/kernel/src/untouched_faces_tests.rs b/crates/kernel/src/untouched_faces_tests.rs index 39737565..a1e06bd0 100644 --- a/crates/kernel/src/untouched_faces_tests.rs +++ b/crates/kernel/src/untouched_faces_tests.rs @@ -148,3 +148,66 @@ fn random_cuts_and_joins_never_split_other_faces() { } } } + +/// The edges of `b` lying along the line through `a` and `z`. +fn edges_along(b: &Body, a: Vec3, z: Vec3) -> usize { + let on = |p: Vec3| p.dist_to_segment(a, z) < 1e-6; + let edges: std::collections::HashSet<_> = b + .solid + .edge_iter() + .filter(|e| on(crate::body::from_p3(e.front().point())) && on(crate::body::from_p3(e.back().point()))) + .map(|e| e.id()) + .collect(); + edges.len() +} + +/// Vertices of `b` where just two straight edges meet in line (a straight edge split in two). +fn stations(b: &Body) -> usize { + use std::collections::HashMap; + let mut at: HashMap> = HashMap::new(); + let mut seen = std::collections::HashSet::new(); + for e in b.solid.edge_iter() { + if seen.insert(e.id()) { + for v in [e.front(), e.back()] { + at.entry(v.id()).or_default().push(e.clone()); + } + } + } + at.values() + .filter(|es| match es.as_slice() { + [a, b] => { + let dir = |e: &truck_modeling::Edge| (crate::body::from_p3(e.back().point()) - crate::body::from_p3(e.front().point())).normalized(); + matches!((a.curve(), b.curve()), (truck_modeling::Curve::Line(_), truck_modeling::Curve::Line(_))) + && dir(a).zip(dir(b)).is_some_and(|(u, w)| u.dot(w).abs() > 1. - 1e-9) + } + _ => false, + }) + .count() +} + +/// Cuts at several heights through a block's walls leave no vertices along its straight edges +/// (#1049): the edges they never touch stay one edge each, and so does an edge a later join +/// lies beside, instead of being drawn (and met by later features) as many pieces. +#[test] +fn cuts_leave_straight_edges_whole() { + let mut b = box_solid(Vec3::new(0., 0., 0.), Vec3::new(40., 20., 10.)).unwrap().with_box_certificate(7).unwrap(); + // Notches through the front wall at different heights, a pocket in the top and a slot + // through the end: each cut is sliced at its own heights. + b = cut(&b, Vec3::new(5., -1., 2.), Vec3::new(10., 3., 4.)); + b = cut(&b, Vec3::new(15., -1., 5.), Vec3::new(20., 3., 8.)); + b = cut(&b, Vec3::new(25., 8., 6.), Vec3::new(30., 12., 11.)); + b = cut(&b, Vec3::new(37., 5., 3.), Vec3::new(41., 15., 7.)); + assert!(b.history().is_some(), "the cuts report their history"); + assert_eq!(stations(&b), 0, "a straight edge is split at a vertex nothing else uses"); + // The block's four upright edges and the top's far edges are one edge each. + for (x, y) in [(0., 0.), (40., 0.), (0., 20.), (40., 20.)] { + assert_eq!(edges_along(&b, Vec3::new(x, y, 0.), Vec3::new(x, y, 10.)), 1, "upright edge at ({x}, {y})"); + } + assert_eq!(edges_along(&b, Vec3::new(0., 20., 10.), Vec3::new(40., 20., 10.)), 1, "the top's back edge"); + assert_eq!(edges_along(&b, Vec3::new(0., 0., 0.), Vec3::new(0., 20., 0.)), 1, "the bottom's left edge"); + // A boss joined on the top beside its back edge, then another cut: still one edge. + b = boolean(&b, &box_solid(Vec3::new(2., 15., 10.), Vec3::new(8., 20., 14.)).unwrap(), BoolOp::Union).unwrap().unwrap(); + b = cut(&b, Vec3::new(12., 4., 1.), Vec3::new(14., 21., 9.)); + assert_eq!(stations(&b), 0, "after a join and another cut"); + assert_eq!(edges_along(&b, Vec3::new(0., 0., 0.), Vec3::new(0., 0., 10.)), 1, "upright edge after a join and a cut"); +} diff --git a/crates/sketch/src/link.rs b/crates/sketch/src/link.rs index 89bfb1cf..005578d4 100644 --- a/crates/sketch/src/link.rs +++ b/crates/sketch/src/link.rs @@ -356,6 +356,44 @@ fn same_geometry(a: &[(LCurve, Vec)], b: &[(LCurve, Vec)]) -> bool { } /// The point indices a layout curve uses, in order. +/// Is `fresh` the boundary `old` was, with only the stations along its straight lines gone +/// (#1049: straight edges that booleans left split at vertices nothing else used are now one +/// edge)? Every old line lies along exactly one fresh line and the old pieces cover it end to +/// end; every other curve is unchanged. A link saved with the stations keeps its pieces then: +/// the region they bound is the same, and the features that name them by id still find them. +fn only_stations_gone(old: &[(LCurve, Vec)], fresh: &[(LCurve, Vec)]) -> bool { + const TOL: f64 = 1e-6; + let is_line = |c: &&(LCurve, Vec)| matches!(c.0, LCurve::Line(..)); + let ends = |c: &(LCurve, Vec)| match c.1.as_slice() { + [a, b] => Some((*a, *b)), + _ => None, + }; + let (old_lines, old_rest): (Vec<_>, Vec<_>) = old.iter().partition(is_line); + let (fresh_lines, fresh_rest): (Vec<_>, Vec<_>) = fresh.iter().partition(is_line); + if fresh_lines.len() >= old_lines.len() || old_lines.len() > 512 { + return false; + } + let rest = |xs: Vec<&(LCurve, Vec)>| xs.into_iter().cloned().collect::>(); + if !same_geometry(&rest(old_rest), &rest(fresh_rest)) { + return false; + } + let Some(fresh_segs) = fresh_lines.iter().map(|c| ends(c)).collect::>>() else { return false }; + let mut covered = vec![0.0; fresh_segs.len()]; + for c in &old_lines { + let Some((p, q)) = ends(c) else { return false }; + let on = |x: Vec2, (a, b): (Vec2, Vec2)| { + let ab = b - a; + let t = ((x - a).dot(ab) / ab.dot(ab).max(f64::MIN_POSITIVE)).clamp(0.0, 1.0); + x.dist(a + ab * t) <= TOL * (1.0 + a.len().max(b.len())) + }; + let Some(k) = fresh_segs.iter().position(|seg| on(p, *seg) && on(q, *seg)) else { return false }; + if let Some(slot) = covered.get_mut(k) { + *slot += p.dist(q); + } + } + fresh_segs.iter().zip(&covered).all(|((a, b), len)| (a.dist(*b) - len).abs() <= TOL * (1.0 + a.len().max(b.len()))) +} + fn curve_points(c: &LCurve) -> Vec { match *c { LCurve::Line(a, b) => vec![a, b], @@ -677,9 +715,8 @@ impl Sketch { let lone_old: Vec = points.iter().filter_map(|pi| pos(*pi)).collect(); if let (Some(old), Some(fresh)) = (old, fresh) && lay.lone.is_empty() - && points.len() == lay.pts.len() && lone_old.len() == points.len() - && same_geometry(&old, &fresh) + && (points.len() == lay.pts.len() && same_geometry(&old, &fresh) || only_stations_gone(&old, &fresh)) { return Ok(false); } diff --git a/crates/sketch/src/tests.rs b/crates/sketch/src/tests.rs index 0435ec8e..9278b4fd 100644 --- a/crates/sketch/src/tests.rs +++ b/crates/sketch/src/tests.rs @@ -883,3 +883,41 @@ fn exact_profiles_for_high_degree_and_cut_free_form_curves() { let uncut: f64 = find_profiles(&sk).iter().map(|p| p.area).sum(); assert!((whole - uncut).abs() < 1e-6 * uncut, "{whole} vs {uncut}"); } + +/// A face boundary saved with stations along its straight edges (#1049: booleans used to leave +/// vertices nothing else used) keeps its saved pieces when the face comes back with each +/// straight edge whole: the same region, and features that name the pieces still find them. +#[test] +fn projected_boundary_keeps_its_pieces_when_only_stations_go() { + use crate::{LinkGeom, LinkKind, LinkSource}; + let mut sk = Sketch::new(); + let v = Vec2::new; + let split = vec![ + LinkGeom::Line(v(0.0, 0.0), v(5.0, 0.0)), + LinkGeom::Line(v(5.0, 0.0), v(10.0, 0.0)), + LinkGeom::Line(v(10.0, 0.0), v(10.0, 4.0)), + LinkGeom::Line(v(10.0, 4.0), v(10.0, 10.0)), + LinkGeom::Line(v(10.0, 10.0), v(0.0, 10.0)), + LinkGeom::Line(v(0.0, 10.0), v(0.0, 0.0)), + ]; + let j = sk.add_link(LinkKind::Project, LinkSource::Origin, &split).unwrap(); + let ids: Vec = sk.link_curves(&j).iter().map(|c| sk.curves[*c].id.clone()).collect(); + let whole = vec![ + LinkGeom::Line(v(0.0, 0.0), v(10.0, 0.0)), + LinkGeom::Line(v(10.0, 0.0), v(10.0, 10.0)), + LinkGeom::Line(v(10.0, 10.0), v(0.0, 10.0)), + LinkGeom::Line(v(0.0, 10.0), v(0.0, 0.0)), + ]; + assert!(!sk.update_link(&j, &whole).unwrap(), "only the stations went: the pieces stay"); + let kept: Vec = sk.link_curves(&j).iter().map(|c| sk.curves[*c].id.clone()).collect(); + assert_eq!(ids, kept); + // A boundary that really changed is rebuilt. + let smaller = vec![ + LinkGeom::Line(v(0.0, 0.0), v(8.0, 0.0)), + LinkGeom::Line(v(8.0, 0.0), v(8.0, 10.0)), + LinkGeom::Line(v(8.0, 10.0), v(0.0, 10.0)), + LinkGeom::Line(v(0.0, 10.0), v(0.0, 0.0)), + ]; + assert!(sk.update_link(&j, &smaller).unwrap()); + assert_eq!(sk.link_curves(&j).len(), 4); +} diff --git a/tests/action_matrix/designs/crazy-geom-cuts.solidcraft b/tests/action_matrix/designs/crazy-geom-cuts.solidcraft new file mode 100644 index 00000000..f8dd8cda Binary files /dev/null and b/tests/action_matrix/designs/crazy-geom-cuts.solidcraft differ diff --git a/tests/robustness/goldens.json b/tests/robustness/goldens.json index 2e1dce84..80b52fc3 100644 --- a/tests/robustness/goldens.json +++ b/tests/robustness/goldens.json @@ -3,22 +3,22 @@ "seeds": { "0": {"area":4795.764396,"bodies":1,"edges":79,"error":null,"faces":29,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","hole","circular pattern","circular pattern","cut sphere","join cylinder","cut cylinder","fillet"],"unsupported":1,"volume":13466.472108}, "1": {"area":2642.476583,"bodies":1,"edges":37,"error":null,"faces":15,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","cut cylinder","join cylinder"],"unsupported":0,"volume":6991.559261}, - "2": {"area":2493.96451,"bodies":1,"edges":59,"error":null,"faces":22,"outcome":"ok","rejected":0,"shells":1,"steps":["box","cut cylinder","fillet","cut sphere","rectangular pattern","cut sphere"],"unsupported":3,"volume":5020.244288}, + "2": {"area":2522.922626,"bodies":1,"edges":36,"error":"boolean Cut: the operation failed: no result","faces":14,"outcome":"failed","rejected":1,"shells":1,"steps":["box","cut cylinder","fillet","cut sphere","rectangular pattern"],"unsupported":2,"volume":5580.088497}, "3": {"area":6336.456143,"bodies":1,"edges":33,"error":null,"faces":13,"outcome":"ok","rejected":0,"shells":1,"steps":["box","cut box","hole","fillet","hole"],"unsupported":0,"volume":28037.146888}, - "4": {"area":5813.939544,"bodies":1,"edges":27,"error":null,"faces":11,"outcome":"ok","rejected":0,"shells":1,"steps":["box","cut cylinder","fillet"],"unsupported":0,"volume":28462.605133}, + "4": {"area":5810.088602,"bodies":1,"edges":27,"error":null,"faces":11,"outcome":"ok","rejected":0,"shells":1,"steps":["box","cut cylinder","fillet"],"unsupported":0,"volume":28461.628006}, "5": {"area":1696.077144,"bodies":1,"edges":24,"error":"boolean Cut: the operation failed: no result; the operation ","faces":12,"outcome":"failed","rejected":1,"shells":1,"steps":["box","cut cylinder","hole","cut cylinder"],"unsupported":0,"volume":3520.994026}, "6": {"area":11580.298484,"bodies":1,"edges":28,"error":"offset: a corner whose faces don't meet the same way after t","faces":13,"outcome":"failed","rejected":0,"shells":1,"steps":["box","hole","join box","join cylinder","cut cylinder","shell"],"unsupported":1,"volume":69137.260899}, - "7": {"area":7462.861065,"bodies":3,"edges":111,"error":null,"faces":32,"outcome":"ok","rejected":0,"shells":3,"steps":["extruded profile","join box","cut cylinder","join box","circular pattern","hole"],"unsupported":0,"volume":32823.69121}, + "7": {"area":7462.861065,"bodies":3,"edges":72,"error":null,"faces":32,"outcome":"ok","rejected":0,"shells":3,"steps":["extruded profile","join box","cut cylinder","join box","circular pattern","hole"],"unsupported":0,"volume":32823.69121}, "8": {"area":2865.472237,"bodies":1,"edges":24,"error":null,"faces":10,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","cut cylinder"],"unsupported":0,"volume":10024.247951}, "9": {"area":2434.461263,"bodies":1,"edges":38,"error":"lumps: shell boundaries intersect or touch; cavity ownership","faces":17,"outcome":"failed","rejected":0,"shells":2,"steps":["revolved section","chamfer","fillet","join box","cut sphere","rectangular pattern","rectangular pattern","cut box"],"unsupported":4,"volume":6547.123554}, "10": {"area":2786.620097,"bodies":1,"edges":44,"error":null,"faces":18,"outcome":"ok","rejected":1,"shells":1,"steps":["revolved section","cut box","chamfer","hole"],"unsupported":0,"volume":6668.9804}, "11": {"area":7858.300481,"bodies":1,"edges":33,"error":null,"faces":13,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","hole","rectangular pattern","fillet"],"unsupported":0,"volume":34605.048259}, - "12": {"area":4668.664174,"bodies":2,"edges":62,"error":null,"faces":25,"outcome":"ok","rejected":0,"shells":2,"steps":["extruded profile","chamfer","join cylinder","join box","cut cylinder","circular pattern"],"unsupported":3,"volume":11874.471611}, - "13": {"area":1857.434638,"bodies":4,"edges":78,"error":null,"faces":34,"outcome":"ok","rejected":1,"shells":4,"steps":["revolved section","cut cylinder","join box","rectangular pattern","cut cylinder","cut box","fillet","hole"],"unsupported":0,"volume":2300.91061}, - "14": {"area":10553.267698,"bodies":1,"edges":43,"error":null,"faces":20,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","cut cylinder","shell","fillet"],"unsupported":0,"volume":10369.009813}, + "12": {"area":4668.664174,"bodies":2,"edges":59,"error":null,"faces":25,"outcome":"ok","rejected":0,"shells":2,"steps":["extruded profile","chamfer","join cylinder","join box","cut cylinder","circular pattern"],"unsupported":3,"volume":11874.471611}, + "13": {"area":1857.434638,"bodies":4,"edges":75,"error":null,"faces":34,"outcome":"ok","rejected":1,"shells":4,"steps":["revolved section","cut cylinder","join box","rectangular pattern","cut cylinder","cut box","fillet","hole"],"unsupported":0,"volume":2300.91061}, + "14": {"area":10553.267409,"bodies":1,"edges":43,"error":null,"faces":20,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","cut cylinder","shell","fillet"],"unsupported":0,"volume":10369.008764}, "15": {"area":1974.784048,"bodies":1,"edges":40,"error":"boolean Cut: the operation failed: no result; the operation ","faces":18,"outcome":"failed","rejected":1,"shells":2,"steps":["revolved section","fillet","cut box","rectangular pattern","cut sphere","cut cylinder","cut sphere"],"unsupported":0,"volume":4781.224576}, "16": {"area":8872.873884,"bodies":3,"edges":49,"error":null,"faces":23,"outcome":"ok","rejected":0,"shells":3,"steps":["box","join cylinder","circular pattern","hole"],"unsupported":0,"volume":44953.403148}, - "17": {"area":6048.425784,"bodies":1,"edges":56,"error":null,"faces":19,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","cut box","shell","cut cylinder"],"unsupported":0,"volume":2968.531542}, + "17": {"area":6048.425782,"bodies":1,"edges":48,"error":null,"faces":19,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","cut box","shell","cut cylinder"],"unsupported":0,"volume":2968.53154}, "18": {"area":5524.583817,"bodies":4,"edges":98,"error":"boolean Cut: the operation failed: no result","faces":44,"outcome":"failed","rejected":0,"shells":4,"steps":["extruded profile","join cylinder","circular pattern","cut cylinder","hole","cut cylinder","hole","circular pattern"],"unsupported":0,"volume":21568.016672}, "19": {"area":5939.094997,"bodies":1,"edges":68,"error":null,"faces":23,"outcome":"ok","rejected":1,"shells":1,"steps":["revolved section","cut sphere","shell"],"unsupported":0,"volume":2943.015294}, "20": {"area":5727.101189,"bodies":1,"edges":116,"error":null,"faces":45,"outcome":"ok","rejected":1,"shells":1,"steps":["extruded profile","fillet","cut box","cut cylinder","hole","circular pattern","cut box"],"unsupported":0,"volume":17142.727051}, @@ -26,56 +26,56 @@ "22": {"area":4634.476813,"bodies":2,"edges":108,"error":null,"faces":40,"outcome":"ok","rejected":0,"shells":2,"steps":["revolved section","join box","join box","join box","rectangular pattern","cut cylinder"],"unsupported":0,"volume":7855.560098}, "23": {"area":1795.696192,"bodies":1,"edges":16,"error":null,"faces":7,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","cut sphere"],"unsupported":0,"volume":3198.694598}, "24": {"area":13432.401041,"bodies":1,"edges":86,"error":null,"faces":35,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","chamfer","hole","cut box","cut sphere","cut sphere","shell"],"unsupported":0,"volume":9985.17442}, - "25": {"area":6134.243434,"bodies":1,"edges":56,"error":null,"faces":19,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","shell"],"unsupported":0,"volume":5828.585616}, + "25": {"area":6134.243434,"bodies":1,"edges":48,"error":null,"faces":19,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","shell"],"unsupported":0,"volume":5828.585617}, "26": {"area":6805.258418,"bodies":2,"edges":48,"error":null,"faces":20,"outcome":"ok","rejected":0,"shells":2,"steps":["revolved section","join box","cut cylinder","rectangular pattern","cut sphere"],"unsupported":0,"volume":21188.329272}, "27": {"area":11263.08174,"bodies":3,"edges":99,"error":null,"faces":42,"outcome":"ok","rejected":0,"shells":4,"steps":["box","chamfer","cut sphere","cut sphere","join box","circular pattern","hole","join box"],"unsupported":0,"volume":49451.079636}, "28": {"area":2394.691384,"bodies":1,"edges":15,"error":null,"faces":9,"outcome":"ok","rejected":0,"shells":2,"steps":["revolved section","join cylinder"],"unsupported":0,"volume":5902.121375}, "29": {"area":6965.599057,"bodies":1,"edges":74,"error":null,"faces":30,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","shell","join cylinder"],"unsupported":0,"volume":3977.885339}, "30": {"area":853.525389,"bodies":1,"edges":23,"error":null,"faces":10,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","chamfer"],"unsupported":0,"volume":1337.14821}, - "31": {"area":10195.46137,"bodies":3,"edges":69,"error":null,"faces":30,"outcome":"ok","rejected":1,"shells":3,"steps":["box","cut box","fillet","join box","circular pattern","cut box","circular pattern","hole"],"unsupported":1,"volume":56416.127527}, + "31": {"area":10198.42294,"bodies":3,"edges":67,"error":null,"faces":29,"outcome":"ok","rejected":1,"shells":3,"steps":["box","cut box","fillet","join box","fillet","circular pattern","cut box","circular pattern"],"unsupported":0,"volume":56408.3612}, "32": {"area":3220.802769,"bodies":1,"edges":51,"error":null,"faces":19,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","cut sphere","cut box","cut box","join box"],"unsupported":0,"volume":7565.955897}, "33": {"area":5286.736353,"bodies":2,"edges":59,"error":null,"faces":27,"outcome":"ok","rejected":0,"shells":3,"steps":["revolved section","fillet","join box","cut cylinder","chamfer","join box","chamfer","fillet"],"unsupported":2,"volume":13858.467983}, "34": {"area":1086.303472,"bodies":1,"edges":30,"error":null,"faces":14,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","cut sphere","hole"],"unsupported":0,"volume":2134.365275}, "35": {"area":4605.630216,"bodies":1,"edges":26,"error":null,"faces":11,"outcome":"ok","rejected":0,"shells":1,"steps":["box","fillet","join box"],"unsupported":0,"volume":13965.526982}, "36": {"area":10408.825748,"bodies":1,"edges":60,"error":null,"faces":26,"outcome":"ok","rejected":0,"shells":2,"steps":["revolved section","cut cylinder","join box","fillet","join cylinder"],"unsupported":0,"volume":55729.313103}, "37": {"area":11714.822253,"bodies":1,"edges":44,"error":null,"faces":18,"outcome":"ok","rejected":1,"shells":1,"steps":["box","cut cylinder","join cylinder","cut sphere","circular pattern","fillet","circular pattern","rectangular pattern"],"unsupported":2,"volume":75556.602064}, - "38": {"area":11769.375817,"bodies":1,"edges":44,"error":"offset: a corner whose faces don't meet the same way after t","faces":12,"outcome":"failed","rejected":1,"shells":1,"steps":["box","hole","cut cylinder","shell"],"unsupported":0,"volume":78261.020973}, + "38": {"area":11769.375811,"bodies":1,"edges":30,"error":"offset: a corner whose faces don't meet the same way after t","faces":12,"outcome":"failed","rejected":1,"shells":1,"steps":["box","hole","cut cylinder","shell"],"unsupported":0,"volume":78261.020975}, "39": {"area":1892.050815,"bodies":1,"edges":27,"error":null,"faces":11,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","chamfer","cut sphere","rectangular pattern"],"unsupported":1,"volume":4040.62787}, "40": {"area":15548.516294,"bodies":1,"edges":49,"error":"offset: a corner whose faces don't meet the same way after t","faces":22,"outcome":"failed","rejected":0,"shells":1,"steps":["extruded profile","cut cylinder","shell","cut cylinder","join cylinder","shell"],"unsupported":0,"volume":16543.777532}, "41": {"area":7611.393446,"bodies":1,"edges":99,"error":null,"faces":35,"outcome":"ok","rejected":1,"shells":1,"steps":["box","cut sphere","shell","fillet","cut cylinder","rectangular pattern","cut sphere","join cylinder"],"unsupported":1,"volume":5973.93328}, - "42": {"area":5486.877622,"bodies":1,"edges":63,"error":null,"faces":20,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","cut box","cut cylinder","rectangular pattern"],"unsupported":0,"volume":19095.106157}, + "42": {"area":5486.877622,"bodies":1,"edges":51,"error":null,"faces":20,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","cut box","cut cylinder","rectangular pattern"],"unsupported":0,"volume":19095.106157}, "43": {"area":3492.988343,"bodies":1,"edges":72,"error":null,"faces":26,"outcome":"ok","rejected":2,"shells":1,"steps":["extruded profile","cut cylinder","rectangular pattern","fillet","hole","rectangular pattern","join cylinder"],"unsupported":1,"volume":7528.548742}, - "44": {"area":7972.435565,"bodies":1,"edges":36,"error":null,"faces":15,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","shell","cut sphere"],"unsupported":0,"volume":7622.184369}, - "45": {"area":14416.876392,"bodies":2,"edges":62,"error":"boolean Union: implausible result volume ","faces":29,"outcome":"failed","rejected":0,"shells":3,"steps":["revolved section","join cylinder","fillet","shell","cut box","join box"],"unsupported":1,"volume":14286.791174}, + "44": {"area":7972.435566,"bodies":1,"edges":36,"error":null,"faces":15,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","shell","cut sphere"],"unsupported":0,"volume":7622.184366}, + "45": {"area":14416.876392,"bodies":2,"edges":60,"error":"boolean Union: implausible result volume ","faces":29,"outcome":"failed","rejected":0,"shells":3,"steps":["revolved section","join cylinder","fillet","shell","cut box","join box"],"unsupported":1,"volume":14286.791174}, "46": {"area":3445.162934,"bodies":1,"edges":99,"error":null,"faces":31,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","join cylinder","hole","cut cylinder","rectangular pattern","cut box","cut cylinder"],"unsupported":0,"volume":6232.317935}, "47": {"area":3098.575625,"bodies":1,"edges":42,"error":null,"faces":16,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","fillet","cut sphere","fillet"],"unsupported":0,"volume":8834.297438}, "48": {"area":9651.031297,"bodies":1,"edges":66,"error":null,"faces":28,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","hole","circular pattern","join box","shell","cut box"],"unsupported":2,"volume":7044.773463}, "49": {"area":7512.5,"bodies":1,"edges":24,"error":null,"faces":11,"outcome":"ok","rejected":0,"shells":1,"steps":["box","join box"],"unsupported":0,"volume":40546.875}, "50": {"area":9387.513708,"bodies":2,"edges":48,"error":null,"faces":20,"outcome":"ok","rejected":1,"shells":2,"steps":["extruded profile","fillet","join cylinder","join cylinder","fillet","cut box","cut cylinder"],"unsupported":1,"volume":40128.140215}, - "51": {"area":4230.232922,"bodies":1,"edges":66,"error":null,"faces":26,"outcome":"ok","rejected":1,"shells":2,"steps":["box","cut box","hole","rectangular pattern","chamfer","join box"],"unsupported":1,"volume":8742.066663}, - "52": {"area":7851.282527,"bodies":1,"edges":32,"error":null,"faces":14,"outcome":"ok","rejected":0,"shells":1,"steps":["box","shell","cut cylinder","chamfer","fillet"],"unsupported":0,"volume":5741.788461}, + "51": {"area":4221.874863,"bodies":1,"edges":75,"error":null,"faces":31,"outcome":"ok","rejected":1,"shells":2,"steps":["box","cut box","hole","rectangular pattern","chamfer","join box"],"unsupported":1,"volume":8682.025064}, + "52": {"area":8628.608654,"bodies":1,"edges":45,"error":null,"faces":18,"outcome":"ok","rejected":0,"shells":1,"steps":["box","shell","cut cylinder","chamfer","join cylinder"],"unsupported":1,"volume":9001.505742}, "53": {"area":17168.464938,"bodies":1,"edges":110,"error":null,"faces":46,"outcome":"ok","rejected":1,"shells":4,"steps":["extruded profile","cut cylinder","shell","join cylinder","circular pattern","fillet"],"unsupported":0,"volume":16607.030819}, "54": {"area":7080.555724,"bodies":4,"edges":90,"error":null,"faces":38,"outcome":"ok","rejected":0,"shells":4,"steps":["extruded profile","join box","circular pattern","cut cylinder","cut box","join cylinder","cut cylinder"],"unsupported":3,"volume":15737.224446}, "55": {"area":6796.974648,"bodies":1,"edges":60,"error":"Body","faces":23,"outcome":"invalid","rejected":0,"shells":1,"steps":["box","fillet","cut sphere","circular pattern","rectangular pattern","cut sphere"],"unsupported":0,"volume":24741.239331}, - "56": {"area":4468.944174,"bodies":1,"edges":33,"error":null,"faces":13,"outcome":"ok","rejected":0,"shells":1,"steps":["box","cut box","chamfer"],"unsupported":0,"volume":11835.9375}, + "56": {"area":4453.31728,"bodies":1,"edges":33,"error":null,"faces":13,"outcome":"ok","rejected":0,"shells":1,"steps":["box","cut box","chamfer"],"unsupported":0,"volume":11829.375}, "57": {"area":4678.619229,"bodies":1,"edges":63,"error":null,"faces":23,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","cut sphere","chamfer","circular pattern","cut sphere","cut sphere","cut cylinder"],"unsupported":2,"volume":16016.156622}, "58": {"area":4197.049258,"bodies":1,"edges":42,"error":null,"faces":16,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","fillet","cut sphere"],"unsupported":0,"volume":12543.173591}, "59": {"area":7770.5632,"bodies":1,"edges":36,"error":null,"faces":15,"outcome":"ok","rejected":0,"shells":1,"steps":["box","join box","join cylinder"],"unsupported":0,"volume":29735.209746}, - "60": {"area":3443.32863,"bodies":1,"edges":39,"error":null,"faces":15,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","cut sphere"],"unsupported":0,"volume":11779.204752}, + "60": {"area":3443.328607,"bodies":1,"edges":39,"error":null,"faces":15,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","cut sphere"],"unsupported":0,"volume":11779.204543}, "61": {"area":5020.413153,"bodies":1,"edges":36,"error":"boolean Cut: the operation failed: no result","faces":15,"outcome":"failed","rejected":0,"shells":1,"steps":["extruded profile","cut cylinder","shell","rectangular pattern"],"unsupported":0,"volume":4906.628538}, - "62": {"area":8771.003707,"bodies":1,"edges":60,"error":null,"faces":23,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","cut cylinder","circular pattern","cut box","cut box","join cylinder"],"unsupported":2,"volume":40107.001722}, - "63": {"area":3060.377467,"bodies":1,"edges":57,"error":"boolean Union: implausible result volume ","faces":21,"outcome":"failed","rejected":1,"shells":1,"steps":["revolved section","join box","cut cylinder","hole","cut cylinder","join box"],"unsupported":0,"volume":6534.394864}, + "62": {"area":8771.003732,"bodies":1,"edges":60,"error":null,"faces":23,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","cut cylinder","circular pattern","cut box","cut box","join cylinder"],"unsupported":2,"volume":40107.001728}, + "63": {"area":3060.37707,"bodies":1,"edges":57,"error":"boolean Union: implausible result volume ","faces":21,"outcome":"failed","rejected":1,"shells":1,"steps":["revolved section","join box","cut cylinder","hole","cut cylinder","join box"],"unsupported":0,"volume":6534.395036}, "64": {"area":4088.335645,"bodies":1,"edges":36,"error":null,"faces":14,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","fillet","join cylinder"],"unsupported":0,"volume":8495.220295}, - "65": {"area":8469.64766,"bodies":1,"edges":30,"error":null,"faces":12,"outcome":"ok","rejected":0,"shells":1,"steps":["box","cut cylinder","fillet","rectangular pattern","circular pattern","circular pattern","fillet","cut box"],"unsupported":0,"volume":45143.068793}, + "65": {"area":8442.175249,"bodies":1,"edges":33,"error":null,"faces":13,"outcome":"ok","rejected":0,"shells":1,"steps":["box","cut cylinder","fillet","rectangular pattern","circular pattern","circular pattern","fillet","cut box"],"unsupported":0,"volume":45117.305023}, "66": {"area":9222.76638,"bodies":1,"edges":99,"error":null,"faces":37,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","join box","join box","join box","cut sphere","cut box"],"unsupported":1,"volume":35217.203139}, - "67": {"area":7942.099033,"bodies":2,"edges":85,"error":null,"faces":35,"outcome":"ok","rejected":0,"shells":2,"steps":["extruded profile","cut cylinder","hole","join cylinder","join box","hole","chamfer","rectangular pattern"],"unsupported":2,"volume":27607.71011}, + "67": {"area":7943.46222,"bodies":2,"edges":88,"error":null,"faces":37,"outcome":"ok","rejected":0,"shells":2,"steps":["extruded profile","cut cylinder","hole","join cylinder","join box","hole","chamfer","rectangular pattern"],"unsupported":2,"volume":27607.962186}, "68": {"area":7338.675126,"bodies":1,"edges":36,"error":null,"faces":14,"outcome":"ok","rejected":2,"shells":1,"steps":["extruded profile","hole","hole","rectangular pattern","rectangular pattern","fillet","circular pattern"],"unsupported":0,"volume":29780.741772}, - "69": {"area":6451.122768,"bodies":1,"edges":132,"error":"offset: a corner whose faces don't meet the same way after t","faces":27,"outcome":"failed","rejected":0,"shells":3,"steps":["revolved section","join box","shell","shell"],"unsupported":0,"volume":4761.477514}, + "69": {"area":6451.122767,"bodies":1,"edges":60,"error":"offset: a corner whose faces don't meet the same way after t","faces":27,"outcome":"failed","rejected":0,"shells":3,"steps":["revolved section","join box","shell","shell"],"unsupported":0,"volume":4761.477515}, "70": {"area":3682.669157,"bodies":1,"edges":24,"error":"lumps: shell boundaries intersect or touch; cavity ownership","faces":12,"outcome":"failed","rejected":0,"shells":2,"steps":["extruded profile","join box","hole"],"unsupported":1,"volume":9155.027162}, - "71": {"area":3389.53358,"bodies":1,"edges":34,"error":"boolean Union: implausible result volume ","faces":14,"outcome":"failed","rejected":0,"shells":1,"steps":["revolved section","cut box","cut cylinder","hole","join cylinder"],"unsupported":0,"volume":14306.661611}, - "72": {"area":5770.270336,"bodies":2,"edges":46,"error":null,"faces":20,"outcome":"ok","rejected":1,"shells":2,"steps":["box","join cylinder","hole","join box","join box","cut box","cut cylinder","cut cylinder"],"unsupported":0,"volume":16081.801492}, + "71": {"area":3980.574721,"bodies":1,"edges":52,"error":null,"faces":20,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","cut box","cut cylinder","hole","join cylinder"],"unsupported":0,"volume":15749.927796}, + "72": {"area":5770.270336,"bodies":2,"edges":44,"error":null,"faces":20,"outcome":"ok","rejected":1,"shells":2,"steps":["box","join cylinder","hole","join box","join box","cut box","cut cylinder","cut cylinder"],"unsupported":0,"volume":16081.801492}, "73": {"area":6868.162117,"bodies":1,"edges":40,"error":"offset: a corner whose faces don't meet the same way after t","faces":18,"outcome":"failed","rejected":0,"shells":1,"steps":["extruded profile","join box","shell"],"unsupported":1,"volume":36461.939381}, - "74": {"area":9294.57311,"bodies":1,"edges":22,"error":null,"faces":9,"outcome":"ok","rejected":0,"shells":1,"steps":["box","hole"],"unsupported":0,"volume":55866.183074}, + "74": {"area":9294.57311,"bodies":1,"edges":18,"error":null,"faces":9,"outcome":"ok","rejected":0,"shells":1,"steps":["box","hole"],"unsupported":0,"volume":55866.183074}, "75": {"area":7893.117931,"bodies":1,"edges":27,"error":null,"faces":11,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","cut cylinder","chamfer","rectangular pattern","rectangular pattern","cut box"],"unsupported":0,"volume":38757.62083}, "76": {"area":899.010133,"bodies":1,"edges":29,"error":null,"faces":13,"outcome":"ok","rejected":1,"shells":1,"steps":["revolved section","fillet","cut cylinder"],"unsupported":0,"volume":1745.3677}, "77": {"area":12157.059928,"bodies":1,"edges":126,"error":null,"faces":54,"outcome":"ok","rejected":1,"shells":1,"steps":["revolved section","hole","circular pattern","rectangular pattern","circular pattern","join cylinder"],"unsupported":0,"volume":64306.616746}, @@ -83,7 +83,7 @@ "79": {"area":13585.252616,"bodies":1,"edges":27,"error":null,"faces":12,"outcome":"ok","rejected":0,"shells":1,"steps":["box","cut cylinder","join cylinder"],"unsupported":0,"volume":104810.763072}, "80": {"area":8652.533342,"bodies":1,"edges":20,"error":null,"faces":9,"outcome":"ok","rejected":0,"shells":1,"steps":["box","fillet","fillet"],"unsupported":0,"volume":37387.449469}, "81": {"area":4001.847983,"bodies":2,"edges":75,"error":null,"faces":29,"outcome":"ok","rejected":1,"shells":2,"steps":["revolved section","cut sphere","circular pattern","rectangular pattern","hole","join cylinder","join box","cut sphere"],"unsupported":2,"volume":13139.658749}, - "82": {"area":3195.487113,"bodies":1,"edges":36,"error":null,"faces":13,"outcome":"ok","rejected":0,"shells":1,"steps":["box","hole","circular pattern","cut box"],"unsupported":0,"volume":5902.162954}, + "82": {"area":3195.487112,"bodies":1,"edges":30,"error":null,"faces":13,"outcome":"ok","rejected":0,"shells":1,"steps":["box","hole","circular pattern","cut box"],"unsupported":0,"volume":5902.162954}, "83": {"area":5125.0,"bodies":1,"edges":28,"error":"offset: a corner whose faces don't meet the same way after t","faces":12,"outcome":"failed","rejected":0,"shells":1,"steps":["box","join box","rectangular pattern","shell"],"unsupported":0,"volume":19250.0}, "84": {"area":4906.192237,"bodies":1,"edges":67,"error":null,"faces":25,"outcome":"ok","rejected":2,"shells":1,"steps":["extruded profile","fillet","hole","hole","cut cylinder","cut box","fillet"],"unsupported":0,"volume":14219.865875}, "85": {"area":3309.210936,"bodies":1,"edges":57,"error":null,"faces":24,"outcome":"ok","rejected":1,"shells":2,"steps":["revolved section","fillet","cut sphere","circular pattern","join cylinder","hole","join box"],"unsupported":0,"volume":6904.455565}, @@ -94,9 +94,9 @@ "90": {"area":9703.747183,"bodies":1,"edges":30,"error":null,"faces":12,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","hole"],"unsupported":0,"volume":46947.022876}, "91": {"area":3989.788869,"bodies":1,"edges":46,"error":null,"faces":18,"outcome":"ok","rejected":1,"shells":1,"steps":["revolved section","cut cylinder","fillet"],"unsupported":0,"volume":9777.743569}, "92": {"area":2855.139217,"bodies":1,"edges":30,"error":null,"faces":12,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","chamfer","cut sphere"],"unsupported":0,"volume":9814.30292}, - "93": {"area":7266.404017,"bodies":7,"edges":115,"error":null,"faces":51,"outcome":"ok","rejected":0,"shells":7,"steps":["extruded profile","fillet","chamfer","join box","circular pattern","rectangular pattern","cut cylinder"],"unsupported":0,"volume":12734.393171}, - "94": {"area":3504.020666,"bodies":1,"edges":73,"error":null,"faces":27,"outcome":"ok","rejected":1,"shells":1,"steps":["revolved section","cut cylinder","hole","rectangular pattern","cut box","cut box","join box"],"unsupported":1,"volume":5899.986066}, - "95": {"area":2339.366789,"bodies":1,"edges":27,"error":null,"faces":11,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","hole","cut cylinder","cut sphere","chamfer","circular pattern"],"unsupported":1,"volume":6587.694591} + "93": {"area":7266.404017,"bodies":7,"edges":111,"error":null,"faces":51,"outcome":"ok","rejected":0,"shells":7,"steps":["extruded profile","fillet","chamfer","join box","circular pattern","rectangular pattern","cut cylinder"],"unsupported":0,"volume":12734.393171}, + "94": {"area":3505.055926,"bodies":1,"edges":73,"error":null,"faces":27,"outcome":"ok","rejected":1,"shells":1,"steps":["revolved section","cut cylinder","hole","rectangular pattern","cut box","cut box","join box"],"unsupported":1,"volume":5900.505082}, + "95": {"area":2425.131626,"bodies":1,"edges":19,"error":"boolean Cut: the operation failed: no result; the operation ","faces":9,"outcome":"failed","rejected":0,"shells":1,"steps":["revolved section","hole","fillet","cut cylinder","cut sphere"],"unsupported":0,"volume":7106.301922} }, "notes": [ {"reason":"First record: what each seed builds on main at 1a0100cd","seeds":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95]}, @@ -113,6 +113,7 @@ {"reason":"main's fillet changes since the last record (#926, #949) move seeds 33 and 91: a fillet step builds differently or is picked differently","seeds":[33,91]}, {"reason":"hole tangent inside a stepped boss: touching sews split zero-thickness curved walls, so a pattern of holes spaced by their diameter (touching) in a stepped body cuts exactly instead of by a tool nudge: 42 builds more faces (the walls split where the holes touch), 51 picks other edges afterwards","seeds":[42,51]}, {"reason":"corner blends on curved faces (#854): seed 33's last fillet (its second body) is built differently; its display-plan and measured volumes now agree (4442.73 mm³; on main they read 4439.42 and 4443.51)","seeds":[33]}, - {"reason":"#952: a hole placed on a face drills into the material; seed 81's hole moves its volume by 0.1 mm\u00b3 (area by 0.04 mm\u00b2)","seeds":[81]} + {"reason":"#952: a hole placed on a face drills into the material; seed 81's hole moves its volume by 0.1 mm³ (area by 0.04 mm²)","seeds":[81]}, + {"reason":"Cuts join the straight edges they used to leave split along untouched walls (#1049). Replaying each changed seed's new commands on the build before and after gives the same volume, area and faces at every step, and the same refusals (seeds 2 and 95 end on a cut the old build refuses too), except: seed 94's final join was 1.7 mm³ too large before (the box adds 875 − 2.5 × 97.967 = 630.08 mm³ to 5270.48; now 5900.56, before 5902.27), and seed 71's hole differs only in the coarse measuring mesh (identical at 2e-4 mm). Every other volume change is the generator picking other edges by index for fillets and chamfers, some of which are now accepted; seed 71's join now builds.","seeds":[2,4,7,12,13,17,25,31,38,42,45,51,52,56,65,67,69,71,72,74,82,93,94,95]} ] }