Another project
0

Configure Feed

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

at main 9.9 kB View raw
1use std::process::Command; 2 3use bone_document::{ 4 BlobKind, DimensionKind, Document, DocumentFolder, EditOutcome, Sketch, SketchDimension, 5 SketchEdit, SketchEntity, evaluate_extrude, evaluate_sketch, load, save, write_solid, 6}; 7use bone_kernel::{ 8 BrepSolid, ExtrudeDirection, ExtrudeEndCondition, ExtrudeFeature, ExtrudeSense, MergeResult, 9}; 10use bone_types::{ 11 DocumentId, ExtrudeId, Length, Point2, Point3, PositiveLength, SketchId, SketchPlaneBasis, 12 Tolerance, UnitVec3, millimeter, 13}; 14use slotmap::KeyData; 15use tempfile::{TempDir, tempdir}; 16 17fn plane() -> SketchPlaneBasis { 18 let Ok(basis) = SketchPlaneBasis::new( 19 Point3::origin(), 20 UnitVec3::x_axis(), 21 UnitVec3::y_axis(), 22 Tolerance::new(1e-9), 23 ) else { 24 panic!("xy plane"); 25 }; 26 basis 27} 28 29fn mm(v: f64) -> Length { 30 Length::new::<millimeter>(v) 31} 32 33fn ok_dir() -> TempDir { 34 let Ok(dir) = tempdir() else { 35 panic!("tempdir"); 36 }; 37 dir 38} 39 40fn sketch_id(idx: u32) -> SketchId { 41 SketchId::from(KeyData::from_ffi((1u64 << 32) | u64::from(idx))) 42} 43 44fn document_id(idx: u32) -> DocumentId { 45 DocumentId::from(KeyData::from_ffi((1u64 << 32) | u64::from(idx))) 46} 47 48fn jj_available() -> bool { 49 let Ok(out) = Command::new("jj").arg("--version").output() else { 50 return false; 51 }; 52 out.status.success() 53} 54 55fn run(cmd: &mut Command) -> String { 56 let Ok(out) = cmd.output() else { 57 panic!("{cmd:?}"); 58 }; 59 assert!( 60 out.status.success(), 61 "{cmd:?} failed: {}\n{}", 62 String::from_utf8_lossy(&out.stdout), 63 String::from_utf8_lossy(&out.stderr), 64 ); 65 String::from_utf8_lossy(&out.stdout).into_owned() 66} 67 68fn rectangle() -> Sketch { 69 let script = [ 70 SketchEdit::AddEntity(SketchEntity::point(Point2::from_mm(0.0, 0.0))), 71 SketchEdit::AddEntity(SketchEntity::point(Point2::from_mm(10.0, 0.0))), 72 SketchEdit::AddEntity(SketchEntity::point(Point2::from_mm(10.0, 5.0))), 73 SketchEdit::AddEntity(SketchEntity::point(Point2::from_mm(0.0, 5.0))), 74 ]; 75 let Ok((s, _)) = Sketch::new(plane()).apply_all(script) else { 76 panic!("rectangle"); 77 }; 78 s 79} 80 81fn assert_save(doc: &Document, folder: &DocumentFolder) { 82 let Ok(()) = save(doc, folder) else { 83 panic!("save"); 84 }; 85} 86 87fn assert_load(folder: &DocumentFolder) -> Document { 88 let Ok(doc) = load(folder) else { 89 panic!("load"); 90 }; 91 doc 92} 93 94#[test] 95fn jj_diff_on_dimension_edit_is_text_only() { 96 if !jj_available() { 97 eprintln!("skip: jj not on PATH"); 98 return; 99 } 100 101 let dir = ok_dir(); 102 let folder = DocumentFolder::new(dir.path().join("demo.bone")); 103 104 let mut doc = Document::new(document_id(1), "demo".to_owned()); 105 let sid = sketch_id(1); 106 let rect = rectangle(); 107 let Some(&a) = rect.entity_order().first() else { 108 panic!("rect has points"); 109 }; 110 let Some(&b) = rect.entity_order().get(1) else { 111 panic!("rect has points"); 112 }; 113 let Ok((rect, EditOutcome::Dimension(dim_id))) = 114 rect.apply(SketchEdit::AddDimension(SketchDimension::Linear { 115 a, 116 b, 117 value: mm(10.0), 118 kind: DimensionKind::Driving, 119 })) 120 else { 121 panic!("dim"); 122 }; 123 doc.insert_sketch(sid, "Sketch1".to_owned(), rect.clone()); 124 assert_save(&doc, &folder); 125 126 run(Command::new("jj") 127 .arg("git") 128 .arg("init") 129 .arg("--colocate") 130 .current_dir(folder.path())); 131 run(Command::new("jj") 132 .arg("config") 133 .arg("set") 134 .arg("--repo") 135 .arg("user.name") 136 .arg("test") 137 .current_dir(folder.path())); 138 run(Command::new("jj") 139 .arg("config") 140 .arg("set") 141 .arg("--repo") 142 .arg("user.email") 143 .arg("test@example.com") 144 .current_dir(folder.path())); 145 run(Command::new("jj") 146 .arg("describe") 147 .arg("--message") 148 .arg("initial") 149 .current_dir(folder.path())); 150 run(Command::new("jj").arg("new").current_dir(folder.path())); 151 152 let reopened = assert_load(&folder); 153 let Some(sketch) = reopened.sketch(sid) else { 154 panic!("sketch missing"); 155 }; 156 let Ok((updated_sketch, _)) = sketch.clone().apply(SketchEdit::UpdateDimensionValue { 157 id: dim_id, 158 value: bone_document::DimensionValue::Length(mm(12.5)), 159 }) else { 160 panic!("update dim"); 161 }; 162 let mut next_doc = reopened.clone(); 163 next_doc.replace_sketch(sid, updated_sketch); 164 assert_save(&next_doc, &folder); 165 166 let diff = run(Command::new("jj") 167 .arg("diff") 168 .arg("--git") 169 .current_dir(folder.path())); 170 assert!( 171 diff.contains("sketches/"), 172 "expected sketch file in diff:\n{diff}" 173 ); 174 assert!( 175 !diff.contains("document.ron"), 176 "a dimension edit must leave the recipe stable, only the sketch file changes:\n{diff}" 177 ); 178 assert!( 179 diff.contains('+') && diff.contains('-'), 180 "expected text diff markers:\n{diff}" 181 ); 182 assert!( 183 !diff.contains("Binary files"), 184 "expected no binary diff:\n{diff}" 185 ); 186} 187 188fn extrude_id(idx: u32) -> ExtrudeId { 189 ExtrudeId::from(KeyData::from_ffi((1u64 << 32) | u64::from(idx))) 190} 191 192fn add_point(s: Sketch, x: f64, y: f64) -> (Sketch, bone_types::SketchEntityId) { 193 let Ok((next, EditOutcome::Entity(id))) = s.apply(SketchEdit::AddEntity(SketchEntity::point( 194 Point2::from_mm(x, y), 195 ))) else { 196 panic!("add point"); 197 }; 198 (next, id) 199} 200 201fn closed_rectangle() -> Sketch { 202 let s = Sketch::new(plane()); 203 let (s, p0) = add_point(s, 0.0, 0.0); 204 let (s, p1) = add_point(s, 10.0, 0.0); 205 let (s, p2) = add_point(s, 10.0, 5.0); 206 let (s, p3) = add_point(s, 0.0, 5.0); 207 [(p0, p1), (p1, p2), (p2, p3), (p3, p0)] 208 .into_iter() 209 .fold(s, |s, (a, b)| { 210 let Ok((next, _)) = s.apply(SketchEdit::AddEntity(SketchEntity::line(a, b, false))) 211 else { 212 panic!("rectangle edge"); 213 }; 214 next 215 }) 216} 217 218fn blind_extrude(sketch: SketchId, depth_mm: f64) -> ExtrudeFeature { 219 let Ok(depth) = PositiveLength::new(mm(depth_mm)) else { 220 panic!("positive depth"); 221 }; 222 ExtrudeFeature { 223 sketch, 224 direction: ExtrudeDirection::Normal { 225 sense: ExtrudeSense::Forward, 226 }, 227 end_condition: ExtrudeEndCondition::Blind { depth }, 228 draft: None, 229 thin_wall: None, 230 merge_result: MergeResult::Merge, 231 } 232} 233 234fn evaluated_solid(doc: &Document, sketch: SketchId, extrude: ExtrudeId) -> BrepSolid { 235 let Some(sketch_value) = doc.sketch(sketch) else { 236 panic!("sketch present"); 237 }; 238 let Some(feature_id) = doc.feature_tree().feature_of_extrude(extrude) else { 239 panic!("extrude node present"); 240 }; 241 let Some(&feature) = doc.extrude_of_feature(feature_id) else { 242 panic!("extrude feature present"); 243 }; 244 let evaluated = evaluate_sketch(sketch_value); 245 let extruded = evaluate_extrude(feature_id, &evaluated, &feature); 246 let Some(solid) = extruded.solid() else { 247 panic!("rectangle extrudes to a solid"); 248 }; 249 solid.clone() 250} 251 252#[test] 253fn jj_diff_on_extrude_edit_is_text_only() { 254 if !jj_available() { 255 eprintln!("skip: jj not on PATH"); 256 return; 257 } 258 259 let dir = ok_dir(); 260 let folder = DocumentFolder::new(dir.path().join("slab.bone")); 261 let sid = sketch_id(1); 262 let eid = extrude_id(1); 263 264 let mut doc = Document::new(document_id(1), "slab".to_owned()); 265 doc.insert_sketch(sid, "Sketch1".to_owned(), closed_rectangle()); 266 doc.insert_extrude(eid, blind_extrude(sid, 10.0)); 267 assert_save(&doc, &folder); 268 let Ok(_baseline_blob) = write_solid(&folder, &evaluated_solid(&doc, sid, eid)) else { 269 panic!("cache the depth-10 solid"); 270 }; 271 272 run(Command::new("jj") 273 .arg("git") 274 .arg("init") 275 .arg("--colocate") 276 .current_dir(folder.path())); 277 run(Command::new("jj") 278 .arg("config") 279 .arg("set") 280 .arg("--repo") 281 .arg("user.name") 282 .arg("test") 283 .current_dir(folder.path())); 284 run(Command::new("jj") 285 .arg("config") 286 .arg("set") 287 .arg("--repo") 288 .arg("user.email") 289 .arg("test@example.com") 290 .current_dir(folder.path())); 291 run(Command::new("jj") 292 .arg("describe") 293 .arg("--message") 294 .arg("initial") 295 .current_dir(folder.path())); 296 run(Command::new("jj").arg("new").current_dir(folder.path())); 297 298 let document_before = std::fs::read_to_string(folder.document_file()).unwrap_or_default(); 299 300 let mut next = assert_load(&folder); 301 next.insert_extrude(eid, blind_extrude(sid, 14.0)); 302 assert_save(&next, &folder); 303 let Ok(edited_blob) = write_solid(&folder, &evaluated_solid(&next, sid, eid)) else { 304 panic!("cache the depth-14 solid"); 305 }; 306 307 let document_after = std::fs::read_to_string(folder.document_file()).unwrap_or_default(); 308 assert_eq!( 309 document_before, document_after, 310 "an extrude-depth edit must not churn document.ron; geometry is not in the recipe" 311 ); 312 assert!( 313 folder.blob_path(edited_blob, BlobKind::BREP).exists(), 314 "the widened slab content-addresses a fresh brep blob" 315 ); 316 317 let diff = run(Command::new("jj") 318 .arg("diff") 319 .arg("--git") 320 .current_dir(folder.path())); 321 assert!( 322 diff.contains("extrudes/"), 323 "expected the extrude file in the diff:\n{diff}" 324 ); 325 assert!( 326 diff.contains(".brep"), 327 "expected a new geometry blob in the diff:\n{diff}" 328 ); 329 assert!( 330 diff.contains('+') && diff.contains('-'), 331 "expected text diff markers:\n{diff}" 332 ); 333 assert!( 334 !diff.contains("Binary files"), 335 "expected no binary diff:\n{diff}" 336 ); 337}