Another project
0

Configure Feed

Select the types of activity you want to include in your feed.

at main 7.8 kB View raw
1use std::path::PathBuf; 2 3use bone_document::{EditOutcome, Sketch, SketchEdit, SketchEntity, SketchRelation}; 4use bone_render::{ 5 Camera2, OffscreenContext, PixelDiff, PixelDiffThreshold, PixelsPerMm, RelationGlyphKind, 6 SketchRenderer, SketchScene, Style, decode_png, encode_png, 7}; 8use bone_types::{Length, Point2, Point3, SketchEntityId, SketchPlaneBasis, Tolerance, UnitVec3}; 9use uom::si::length::millimeter; 10 11mod common; 12 13use common::{extent_square as extent, make_context}; 14 15const GOLDEN: &str = "tests/goldens/relations_256.png"; 16const UPDATE_ENV: &str = "BONE_UPDATE_RELATIONS_GOLDEN"; 17const DIFF_TOLERANCE: f64 = 16.0 / 255.0; 18 19fn plane() -> SketchPlaneBasis { 20 let Ok(basis) = SketchPlaneBasis::new( 21 Point3::origin(), 22 UnitVec3::x_axis(), 23 UnitVec3::y_axis(), 24 Tolerance::new(1e-9), 25 ) else { 26 panic!("xy plane basis is orthogonal"); 27 }; 28 basis 29} 30 31fn add_point(s: Sketch, x: f64, y: f64) -> (Sketch, SketchEntityId) { 32 let Ok((next, EditOutcome::Entity(id))) = s.apply(SketchEdit::AddEntity(SketchEntity::point( 33 Point2::from_mm(x, y), 34 ))) else { 35 panic!("add point"); 36 }; 37 (next, id) 38} 39 40fn add_line(s: Sketch, a: SketchEntityId, b: SketchEntityId) -> (Sketch, SketchEntityId) { 41 let Ok((next, EditOutcome::Entity(id))) = 42 s.apply(SketchEdit::AddEntity(SketchEntity::line(a, b, false))) 43 else { 44 panic!("add line"); 45 }; 46 (next, id) 47} 48 49fn add_circle(s: Sketch, center: SketchEntityId, radius_mm: f64) -> (Sketch, SketchEntityId) { 50 let Ok((next, EditOutcome::Entity(id))) = s.apply(SketchEdit::AddEntity(SketchEntity::circle( 51 center, 52 Length::new::<millimeter>(radius_mm), 53 false, 54 ))) else { 55 panic!("add circle"); 56 }; 57 (next, id) 58} 59 60fn add_relation(s: Sketch, rel: SketchRelation) -> Sketch { 61 let Ok((next, _)) = s.apply(SketchEdit::AddRelation(rel)) else { 62 panic!("add relation {rel:?}"); 63 }; 64 next 65} 66 67fn one_per_kind_scene() -> SketchScene { 68 let s = Sketch::new(plane()); 69 70 let (s, coin_a) = add_point(s, -4.0, 4.0); 71 let (s, coin_b) = add_point(s, -4.0, 4.0); 72 let s = add_relation(s, SketchRelation::Coincident(coin_a, coin_b)); 73 74 let (s, ha) = add_point(s, -1.5, 4.2); 75 let (s, hb) = add_point(s, 1.5, 4.2); 76 let (s, hl) = add_line(s, ha, hb); 77 let s = add_relation(s, SketchRelation::Horizontal(hl)); 78 79 let (s, va) = add_point(s, 4.0, 3.0); 80 let (s, vb) = add_point(s, 4.0, 5.2); 81 let (s, vl) = add_line(s, va, vb); 82 let s = add_relation(s, SketchRelation::Vertical(vl)); 83 84 let (s, pa0) = add_point(s, -5.0, 1.0); 85 let (s, pa1) = add_point(s, -2.5, 1.9); 86 let (s, pla) = add_line(s, pa0, pa1); 87 let (s, pb0) = add_point(s, -5.0, -0.5); 88 let (s, pb1) = add_point(s, -2.5, 0.4); 89 let (s, plb) = add_line(s, pb0, pb1); 90 let s = add_relation(s, SketchRelation::Parallel(pla, plb)); 91 92 let (s, xa0) = add_point(s, -0.5, 1.4); 93 let (s, xa1) = add_point(s, 1.3, 1.4); 94 let (s, xla) = add_line(s, xa0, xa1); 95 let (s, xb0) = add_point(s, 0.5, 0.3); 96 let (s, xb1) = add_point(s, 0.5, 1.9); 97 let (s, xlb) = add_line(s, xb0, xb1); 98 let s = add_relation(s, SketchRelation::Perpendicular(xla, xlb)); 99 100 let (s, tc) = add_point(s, 4.5, 1.2); 101 let (s, tcirc) = add_circle(s, tc, 0.7); 102 let (s, tla) = add_point(s, 3.0, 1.9); 103 let (s, tlb) = add_point(s, 6.0, 1.9); 104 let (s, tline) = add_line(s, tla, tlb); 105 let s = add_relation(s, SketchRelation::Tangent(tcirc, tline)); 106 107 let (s, ea0) = add_point(s, -4.8, -2.5); 108 let (s, ea1) = add_point(s, -3.0, -2.5); 109 let (s, ela) = add_line(s, ea0, ea1); 110 let (s, eb0) = add_point(s, -4.8, -3.8); 111 let (s, eb1) = add_point(s, -3.0, -3.8); 112 let (s, elb) = add_line(s, eb0, eb1); 113 let s = add_relation(s, SketchRelation::Equal(ela, elb)); 114 115 let (s, ccc) = add_point(s, -0.2, -2.8); 116 let (s, cca) = add_circle(s, ccc, 0.8); 117 let (s, ccb) = add_circle(s, ccc, 1.6); 118 let s = add_relation(s, SketchRelation::Concentric(cca, ccb)); 119 120 let (s, fp) = add_point(s, 4.5, -3.0); 121 let s = add_relation(s, SketchRelation::Fix(fp)); 122 123 let (s, mp_a) = add_point(s, 2.0, -3.0); 124 let (s, mp_b) = add_point(s, 5.0, -3.0); 125 let (s, mp_line) = add_line(s, mp_a, mp_b); 126 let (s, mp_point) = add_point(s, 3.5, -3.0); 127 let s = add_relation( 128 s, 129 SketchRelation::Midpoint { 130 point: mp_point, 131 line: mp_line, 132 }, 133 ); 134 135 let (s, sym_a0) = add_point(s, 6.5, -2.5); 136 let (s, sym_a1) = add_point(s, 9.0, -2.5); 137 let (s, sym_axis) = add_line(s, sym_a0, sym_a1); 138 let (s, sym_p) = add_point(s, 7.5, -1.5); 139 let (s, sym_q) = add_point(s, 7.5, -3.5); 140 let s = add_relation( 141 s, 142 SketchRelation::Symmetric { 143 a: sym_p, 144 b: sym_q, 145 axis: sym_axis, 146 }, 147 ); 148 149 let Ok(scene) = SketchScene::extract(&s) else { 150 panic!("scene extract"); 151 }; 152 assert_eq!( 153 scene.relations().len(), 154 RelationGlyphKind::all().len(), 155 "expected one scene glyph per relation kind", 156 ); 157 scene 158} 159 160fn render_scene(ctx: &OffscreenContext, scene: &SketchScene) -> bone_render::SnapshotFrame { 161 let mut renderer = SketchRenderer::new(ctx.gpu(), ctx.color_format()); 162 let camera = Camera2::new(ctx.extent()).with_zoom(PixelsPerMm::new(20.0)); 163 let style = Style::default(); 164 let Ok(frame) = renderer.render(ctx, scene, camera, &style) else { 165 panic!("SketchRenderer::render failed"); 166 }; 167 frame 168} 169 170fn golden_path() -> PathBuf { 171 PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(GOLDEN) 172} 173 174#[test] 175fn relation_kinds_match_golden() { 176 let size = extent(256); 177 let ctx = make_context(size); 178 let scene = one_per_kind_scene(); 179 let frame = render_scene(&ctx, &scene); 180 let path = golden_path(); 181 182 if std::env::var(UPDATE_ENV).is_ok() { 183 let Ok(bytes) = encode_png(&frame) else { 184 panic!("encode_png failed"); 185 }; 186 if let Some(parent) = path.parent() 187 && let Err(e) = std::fs::create_dir_all(parent) 188 { 189 panic!("create goldens dir {}: {e}", parent.display()); 190 } 191 if let Err(e) = std::fs::write(&path, &bytes) { 192 panic!("write golden {}: {e}", path.display()); 193 } 194 return; 195 } 196 197 let Ok(bytes) = std::fs::read(&path) else { 198 panic!( 199 "golden missing at {}: rerun with {UPDATE_ENV}=1 to generate", 200 path.display() 201 ); 202 }; 203 let Ok((golden_extent, golden_rgba)) = decode_png(&bytes) else { 204 panic!("failed to decode golden PNG"); 205 }; 206 assert_eq!(golden_extent, size, "golden extent drift"); 207 let threshold = PixelDiffThreshold::new(DIFF_TOLERANCE); 208 let Ok(report) = PixelDiff::compare(&frame, &golden_rgba, threshold) else { 209 panic!("PixelDiff rejected inputs"); 210 }; 211 assert!( 212 report.is_clean(), 213 "relation render drifted from golden: {} mismatches, worst {:?}, backend {}", 214 report.over_threshold(), 215 report.worst(), 216 frame.backend(), 217 ); 218} 219 220#[test] 221fn relation_glyph_pick_ids_survive_render() { 222 let size = extent(256); 223 let ctx = make_context(size); 224 let scene = one_per_kind_scene(); 225 let frame = render_scene(&ctx, &scene); 226 let Ok(index) = scene.pick_index() else { 227 panic!("pick index build"); 228 }; 229 230 let unresolved: Vec<RelationGlyphKind> = scene 231 .relations() 232 .iter() 233 .filter(|g| g.pick().unpack(&index).is_none()) 234 .map(|g| g.kind()) 235 .collect(); 236 assert!( 237 unresolved.is_empty(), 238 "relation pick ids did not decode: {unresolved:?}", 239 ); 240 241 assert_eq!(frame.extent(), size); 242}