Sketch on a cylinder primitive's caps; format 15 (#460)
* Sketch on a cylinder primitive's caps (#420) A solid.cylinder's caps carried no construction certificate, so a sketch on its top or bottom was refused unless fixed:true (#420's crossed cylinders, built from primitives). - kernel: FaceSource::CylinderCap { producer, high, plane }; Body::with_cylinder_certificate turns a cylinder's two extrude caps into certified caps. kernel::cylinder's output is unchanged for its other callers. - doc: FaceCertificate::CylinderCap { high }, named F<id>:top / F<id>:bottom as the cylinder's face names already are; New Body and Join cylinders are certified, shells carry it as their planar source. - format 15: designs can store the new certificate kind; version 14 designs need no change, and older builds ask for an update instead of meeting an unknown kind. Tests: top and bottom attachments follow a height and base edit and a reopen; crossed joined cylinders keep both caps attachable; a shelled cylinder's floor keeps its cap identity; malformed certificate JSON is refused. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01YDDPmNdRoBgkHcLeUDKVfb * Format 15 also covers sketch_axis links (#448) Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01YDDPmNdRoBgkHcLeUDKVfb --------- Co-authored-by: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
b24e478bc1
commit
49956c2797
10 files changed
+247
-23
No files matched your search
@@ -317,3 +317,66 @@ fn join_covering_a_whole_attached_face_loses_the_attachment_without_moving_it()
|
||||
assert!(!failed(&s, top));
|
||||
assert!((s.model.state().sketch(top).unwrap().plane.origin.z - 10.).abs() < 1e-9);
|
||||
}
|
||||
|
||||
fn certificate_of(s: &Session, sketch: u64) -> Option<solidcraft_doc::config::FaceCertificate> {
|
||||
let FeatureKind::Sketch { plane: PlaneRef::Face { anchor: Some(anchor), .. }, .. } = &s.doc.feature(sketch).unwrap().kind else {
|
||||
return None;
|
||||
};
|
||||
anchor.target.certificate.clone()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cylinder_primitive_caps_take_attachments_that_follow_edits_and_reopen() {
|
||||
use solidcraft_doc::config::FaceCertificate;
|
||||
let mut s = Session::default();
|
||||
run(&mut s, "solid.cylinder", json!({"radius":10,"height":40,"name":"Post"}));
|
||||
let top = run(&mut s, "sketch.create", json!({"plane":{"face":[2,1,40]}}))["sketch"].as_u64().unwrap();
|
||||
run(&mut s, "sketch.finish", json!({}));
|
||||
let bottom = run(&mut s, "sketch.create", json!({"plane":{"face":[2,1,0]}}))["sketch"].as_u64().unwrap();
|
||||
run(&mut s, "sketch.finish", json!({}));
|
||||
assert_eq!(certificate_of(&s, top), Some(FaceCertificate::CylinderCap { high: true }));
|
||||
assert_eq!(certificate_of(&s, bottom), Some(FaceCertificate::CylinderCap { high: false }));
|
||||
let heights = |s: &Session| {
|
||||
let state = s.model.state();
|
||||
(state.sketch(top).unwrap().plane.origin.z, state.sketch(bottom).unwrap().plane.origin.z)
|
||||
};
|
||||
run(&mut s, "timeline.edit", json!({"feature":"Post","set":{"height":"55","base":[0,0,5]}}));
|
||||
s.refresh();
|
||||
assert!(s.model.results.iter().all(|result| result.error.is_none()), "{:?}", s.model.results);
|
||||
let (high, low) = heights(&s);
|
||||
assert!((high - 60.).abs() < 1e-9 && (low - 5.).abs() < 1e-9, "{high} {low}");
|
||||
let mut reopened = Session::new(Document::from_json(&s.doc.to_json()).unwrap());
|
||||
reopened.refresh();
|
||||
assert!(reopened.model.results.iter().all(|result| result.error.is_none()), "{:?}", reopened.model.results);
|
||||
assert_eq!(heights(&reopened), (high, low));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn crossed_cylinder_primitives_keep_their_caps_attachable() {
|
||||
let mut s = Session::default();
|
||||
run(&mut s, "solid.cylinder", json!({"radius":10,"height":40}));
|
||||
run(&mut s, "solid.cylinder", json!({"radius":5,"height":40,"base":[-20,0,20],"axis":"X","operation":"join"}));
|
||||
assert_eq!(s.model.state().bodies.len(), 1);
|
||||
for face in [[0., 0., 40.], [20., 0., 20.]] {
|
||||
let sketch = run(&mut s, "sketch.create", json!({"plane":{"face":face}}))["sketch"].as_u64().unwrap();
|
||||
run(&mut s, "sketch.finish", json!({}));
|
||||
assert!(certificate_of(&s, sketch).is_some(), "{face:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shelled_cylinder_floor_keeps_its_cap_identity() {
|
||||
use solidcraft_doc::config::FaceCertificate;
|
||||
let mut s = Session::default();
|
||||
run(&mut s, "solid.cylinder", json!({"radius":10,"height":40}));
|
||||
run(&mut s, "solid.shell", json!({"faces":[[2,1,40]],"thickness":2}));
|
||||
let floor = run(&mut s, "sketch.create", json!({"plane":{"face":[2,1,2]}}))["sketch"].as_u64().unwrap();
|
||||
assert!(
|
||||
matches!(
|
||||
certificate_of(&s, floor),
|
||||
Some(FaceCertificate::ShellPlanar { source, inner: true, .. }) if *source == FaceCertificate::CylinderCap { high: false }
|
||||
),
|
||||
"{:?}",
|
||||
certificate_of(&s, floor)
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user