Another project
1use bone_kernel::{BrepReattach, BrepSolid};
2use bone_types::SolidKey;
3use serde::{Deserialize, Serialize};
4
5use super::ron_io::{self, RonError};
6
7#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
8#[serde(deny_unknown_fields)]
9pub struct LabelSidecar {
10 solid: SolidKey,
11 reattach: BrepReattach,
12}
13
14impl LabelSidecar {
15 #[must_use]
16 pub fn capture(solid: &BrepSolid) -> Self {
17 Self {
18 solid: solid.content_key(),
19 reattach: solid.reattach_data().clone(),
20 }
21 }
22
23 #[must_use]
24 pub fn solid(&self) -> SolidKey {
25 self.solid
26 }
27
28 #[must_use]
29 pub fn reattach(&self) -> &BrepReattach {
30 &self.reattach
31 }
32
33 #[must_use]
34 pub fn matches(&self, key: SolidKey) -> bool {
35 self.solid == key
36 }
37
38 pub fn to_ron(&self) -> Result<String, RonError> {
39 ron_io::to_string(self)
40 }
41
42 pub fn from_ron(text: &str) -> Result<Self, RonError> {
43 ron_io::from_str(text)
44 }
45}
46
47#[cfg(test)]
48mod tests {
49 use super::LabelSidecar;
50 use bone_types::SolidKey;
51
52 fn cube() -> bone_kernel::BrepSolid {
53 use bone_kernel::{
54 Curve2Kind, ExtrudeDirection, ExtrudeEndCondition, ExtrudeFeature, ExtrudeProfile,
55 ExtrudeSense, Line2, MergeResult, ProfileEdge, ProfileLoop, evaluate_extrude,
56 };
57 use bone_types::{
58 FeatureId, Length, Plane3, Point2, PositiveLength, SketchEntityId, SketchId, Tolerance,
59 UnitVec3, millimeter,
60 };
61 use slotmap::{Key, SlotMap};
62
63 let tol = Tolerance::new(1.0e-9);
64 let mut entities: SlotMap<SketchEntityId, ()> = SlotMap::with_key();
65 let mut features: SlotMap<FeatureId, ()> = SlotMap::with_key();
66 let point = |x: f64, y: f64| Point2::from_mm(x, y);
67 let line = |a: Point2, b: Point2| {
68 let Ok(segment) = Line2::new(a, b, tol) else {
69 panic!("distinct endpoints");
70 };
71 Curve2Kind::Line(segment)
72 };
73 let corners = [
74 point(0.0, 0.0),
75 point(2.0, 0.0),
76 point(2.0, 3.0),
77 point(0.0, 3.0),
78 ];
79 let edges = (0..4)
80 .map(|index| {
81 ProfileEdge::new(
82 line(corners[index], corners[(index + 1) % 4]),
83 entities.insert(()),
84 entities.insert(()),
85 )
86 })
87 .collect();
88 let Ok(plane) = Plane3::new(
89 bone_types::Point3::origin(),
90 UnitVec3::x_axis(),
91 UnitVec3::y_axis(),
92 tol,
93 ) else {
94 panic!("orthonormal axes");
95 };
96 let profile = ExtrudeProfile::new(plane, vec![ProfileLoop::Open(edges)]);
97 let Ok(depth) = PositiveLength::new(Length::new::<millimeter>(5.0)) else {
98 panic!("positive depth");
99 };
100 let feature = ExtrudeFeature {
101 sketch: SketchId::null(),
102 direction: ExtrudeDirection::Normal {
103 sense: ExtrudeSense::Forward,
104 },
105 end_condition: ExtrudeEndCondition::Blind { depth },
106 draft: None,
107 thin_wall: None,
108 merge_result: MergeResult::Merge,
109 };
110 let Ok(solid) = evaluate_extrude(features.insert(()), &profile, &feature) else {
111 panic!("cube extrudes");
112 };
113 solid
114 }
115
116 #[test]
117 fn ron_round_trips() {
118 let solid = cube();
119 let sidecar = LabelSidecar::capture(&solid);
120 let Ok(text) = sidecar.to_ron() else {
121 panic!("serialize sidecar");
122 };
123 let Ok(back) = LabelSidecar::from_ron(&text) else {
124 panic!("deserialize sidecar");
125 };
126 assert_eq!(sidecar, back);
127 }
128
129 #[test]
130 fn matches_only_its_own_solid() {
131 let solid = cube();
132 let sidecar = LabelSidecar::capture(&solid);
133 assert!(sidecar.matches(solid.content_key()));
134 assert!(!sidecar.matches(SolidKey::from_bytes([0u8; 16])));
135 }
136
137 #[test]
138 fn reattach_payload_round_trips_a_solid() {
139 let solid = cube();
140 let sidecar = LabelSidecar::capture(&solid);
141 let Ok(blob) = solid.to_blob() else {
142 panic!("blob serializes");
143 };
144 let Ok(restored) = bone_kernel::BrepSolid::from_blob(&blob, sidecar.reattach()) else {
145 panic!("sidecar reattaches");
146 };
147 assert_eq!(sidecar.solid(), restored.content_key());
148 }
149}