Another project
0

Configure Feed

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

at main 8.1 kB View raw
1use bone_document::{ 2 DimensionKind, Document, DocumentFolder, DocumentHeader, ExtrudeFile, Sketch, SketchDimension, 3 SketchEdit, SketchEntity, SketchFile, SketchRelation, save, to_string, 4}; 5use bone_kernel::{ 6 ExtrudeDirection, ExtrudeEndCondition, ExtrudeFeature, ExtrudeSense, MergeResult, 7}; 8use bone_types::{ 9 Angle, DocumentId, Length, Point2, Point3, PositiveLength, SketchEntityId, SketchId, 10 SketchPlaneBasis, Tolerance, UnitVec3, degree, millimeter, 11}; 12use slotmap::KeyData; 13use tempfile::{TempDir, tempdir}; 14 15fn plane() -> SketchPlaneBasis { 16 let Ok(basis) = SketchPlaneBasis::new( 17 Point3::origin(), 18 UnitVec3::x_axis(), 19 UnitVec3::y_axis(), 20 Tolerance::new(1e-9), 21 ) else { 22 panic!("xy plane"); 23 }; 24 basis 25} 26 27fn mm(v: f64) -> Length { 28 Length::new::<millimeter>(v) 29} 30 31fn deg(v: f64) -> Angle { 32 Angle::new::<degree>(v) 33} 34 35fn ok_dir() -> TempDir { 36 let Ok(dir) = tempdir() else { 37 panic!("tempdir"); 38 }; 39 dir 40} 41 42fn sketch_id(idx: u32) -> SketchId { 43 SketchId::from(KeyData::from_ffi((1u64 << 32) | u64::from(idx))) 44} 45 46fn document_id(idx: u32) -> DocumentId { 47 DocumentId::from(KeyData::from_ffi((1u64 << 32) | u64::from(idx))) 48} 49 50fn point_of(s: &Sketch, i: usize) -> SketchEntityId { 51 s.entity_order()[i] 52} 53 54fn add_entity(s: Sketch, entity: SketchEntity) -> Sketch { 55 let Ok((next, _)) = s.apply(SketchEdit::AddEntity(entity)) else { 56 panic!("add entity"); 57 }; 58 next 59} 60 61fn add_relation(s: Sketch, relation: SketchRelation) -> Sketch { 62 let Ok((next, _)) = s.apply(SketchEdit::AddRelation(relation)) else { 63 panic!("add relation"); 64 }; 65 next 66} 67 68fn add_dimension(s: Sketch, dim: SketchDimension) -> Sketch { 69 let Ok((next, _)) = s.apply(SketchEdit::AddDimension(dim)) else { 70 panic!("add dim"); 71 }; 72 next 73} 74 75fn first_of_kind(s: &Sketch, kind: bone_document::SketchEntityKind) -> SketchEntityId { 76 let Some(&id) = s.entity_order().iter().find(|id| { 77 matches!( 78 s.entities().get(**id).map(bone_document::SketchEntity::kind), 79 Some(k) if k == kind 80 ) 81 }) else { 82 panic!("no entity of kind {kind:?}"); 83 }; 84 id 85} 86 87fn build_full_sketch() -> Sketch { 88 let coords = [ 89 Point2::from_mm(0.0, 0.0), 90 Point2::from_mm(10.0, 0.0), 91 Point2::from_mm(10.0, 5.0), 92 Point2::from_mm(0.0, 5.0), 93 ]; 94 let with_points = coords.into_iter().fold(Sketch::new(plane()), |s, p| { 95 add_entity(s, SketchEntity::point(p)) 96 }); 97 let p0 = point_of(&with_points, 0); 98 let p1 = point_of(&with_points, 1); 99 let p2 = point_of(&with_points, 2); 100 let p3 = point_of(&with_points, 3); 101 102 let with_lines = [ 103 (p0, p1, false), 104 (p1, p2, false), 105 (p2, p3, false), 106 (p3, p0, true), 107 ] 108 .into_iter() 109 .fold(with_points, |s, (a, b, cons)| { 110 add_entity(s, SketchEntity::line(a, b, cons)) 111 }); 112 let with_arc = add_entity(with_lines, SketchEntity::arc(p0, p1, p2, false)); 113 let with_circle = add_entity(with_arc, SketchEntity::circle(p0, mm(2.5), false)); 114 115 let line_ids: Vec<_> = with_circle 116 .entity_order() 117 .iter() 118 .copied() 119 .filter(|id| { 120 matches!( 121 with_circle 122 .entities() 123 .get(*id) 124 .map(bone_document::SketchEntity::kind), 125 Some(bone_document::SketchEntityKind::Line) 126 ) 127 }) 128 .collect(); 129 let arc_id = first_of_kind(&with_circle, bone_document::SketchEntityKind::Arc); 130 let circle_id = first_of_kind(&with_circle, bone_document::SketchEntityKind::Circle); 131 132 let relations = [ 133 SketchRelation::Coincident(p0, line_ids[0]), 134 SketchRelation::Horizontal(line_ids[0]), 135 SketchRelation::Vertical(line_ids[1]), 136 SketchRelation::Parallel(line_ids[0], line_ids[2]), 137 SketchRelation::Perpendicular(line_ids[0], line_ids[1]), 138 SketchRelation::Tangent(line_ids[0], circle_id), 139 SketchRelation::Equal(line_ids[0], line_ids[2]), 140 SketchRelation::Concentric(arc_id, circle_id), 141 SketchRelation::Midpoint { 142 point: p2, 143 line: line_ids[0], 144 }, 145 SketchRelation::Fix(p0), 146 ]; 147 let with_relations = relations.into_iter().fold(with_circle, add_relation); 148 149 let dimensions = [ 150 SketchDimension::Linear { 151 a: p0, 152 b: p1, 153 value: mm(10.0), 154 kind: DimensionKind::Driving, 155 }, 156 SketchDimension::Radius { 157 target: circle_id, 158 value: mm(2.5), 159 kind: DimensionKind::Driven, 160 }, 161 SketchDimension::Diameter { 162 target: circle_id, 163 value: mm(5.0), 164 kind: DimensionKind::Driving, 165 }, 166 SketchDimension::Angular { 167 a: line_ids[0], 168 b: line_ids[1], 169 value: deg(90.0), 170 kind: DimensionKind::Driving, 171 }, 172 ]; 173 dimensions.into_iter().fold(with_relations, add_dimension) 174} 175 176fn assert_ron<T: serde::Serialize>(value: &T) -> String { 177 let Ok(ron) = to_string(value) else { 178 panic!("ron"); 179 }; 180 ron 181} 182 183#[test] 184fn document_header_ron_surface() { 185 let mut doc = Document::new(document_id(1), "demo".to_owned()); 186 let sid = sketch_id(7); 187 doc.insert_sketch(sid, "Sketch1".to_owned(), build_full_sketch()); 188 doc.set_parameter("width".to_owned(), 10.0); 189 190 let ron = assert_ron(doc.header()); 191 insta::assert_snapshot!("document_header", ron); 192} 193 194#[test] 195fn sketch_file_ron_surface() { 196 let file = SketchFile::new(build_full_sketch()); 197 let ron = assert_ron(&file); 198 insta::assert_snapshot!("sketch_file", ron); 199} 200 201#[test] 202fn extrude_file_ron_surface() { 203 let Ok(depth) = PositiveLength::new(mm(10.0)) else { 204 panic!("positive depth"); 205 }; 206 let file = ExtrudeFile::new( 207 ExtrudeFeature { 208 sketch: sketch_id(7), 209 direction: ExtrudeDirection::Normal { 210 sense: ExtrudeSense::Forward, 211 }, 212 end_condition: ExtrudeEndCondition::Blind { depth }, 213 draft: None, 214 thin_wall: None, 215 merge_result: MergeResult::Merge, 216 }, 217 "Extrude1".to_owned(), 218 ); 219 let ron = assert_ron(&file); 220 insta::assert_snapshot!("extrude_file", ron); 221} 222 223#[test] 224fn saved_folder_layout_is_stable() { 225 let dir = ok_dir(); 226 let folder = DocumentFolder::new(dir.path().join("pinned.bone")); 227 let mut doc = Document::new(document_id(1), "pinned".to_owned()); 228 doc.insert_sketch(sketch_id(1), "Rect".to_owned(), build_full_sketch()); 229 let Ok(()) = save(&doc, &folder) else { 230 panic!("save"); 231 }; 232 233 let mut entries: Vec<String> = walk_relative(folder.path()).collect(); 234 entries.sort(); 235 let listing = entries.join("\n"); 236 insta::assert_snapshot!("folder_listing", listing); 237} 238 239fn walk_relative(root: &std::path::Path) -> impl Iterator<Item = String> + '_ { 240 fn walk(root: &std::path::Path, path: &std::path::Path, out: &mut Vec<String>) { 241 let Ok(iter) = std::fs::read_dir(path) else { 242 return; 243 }; 244 iter.flatten().for_each(|entry| { 245 let p = entry.path(); 246 let rel = p 247 .strip_prefix(root) 248 .unwrap_or(&p) 249 .to_string_lossy() 250 .into_owned(); 251 if p.is_dir() { 252 out.push(format!("{rel}/")); 253 walk(root, &p, out); 254 } else { 255 out.push(rel); 256 } 257 }); 258 } 259 let mut out = Vec::new(); 260 walk(root, root, &mut out); 261 out.into_iter() 262} 263 264#[test] 265fn document_header_roundtrips_through_ron() { 266 let mut doc = Document::new(document_id(1), "rt".to_owned()); 267 doc.insert_sketch(sketch_id(1), "S".to_owned(), build_full_sketch()); 268 let ron = assert_ron(doc.header()); 269 let Ok(parsed) = bone_document::from_str::<DocumentHeader>(&ron) else { 270 panic!("parse"); 271 }; 272 assert_eq!(&parsed, doc.header()); 273}