Another project
1use bone_render::PickedItem;
2use bone_types::{SketchDimensionId, SketchEntityId, SketchItemId, SketchRelationId};
3
4#[derive(Clone, Debug, PartialEq, Eq)]
5pub enum Selection {
6 Entities(Vec<SketchEntityId>),
7 Relation(SketchRelationId),
8 Dimension(SketchDimensionId),
9}
10
11impl Default for Selection {
12 fn default() -> Self {
13 Self::Entities(Vec::new())
14 }
15}
16
17impl Selection {
18 #[must_use]
19 pub fn entity_ids(&self) -> &[SketchEntityId] {
20 match self {
21 Self::Entities(v) => v,
22 Self::Relation(_) | Self::Dimension(_) => &[],
23 }
24 }
25
26 #[must_use]
27 pub fn is_empty(&self) -> bool {
28 matches!(self, Self::Entities(v) if v.is_empty())
29 }
30
31 #[must_use]
32 pub fn picked(self, item: Option<SketchItemId>, additive: bool) -> Self {
33 match (item, additive) {
34 (None, false) => Self::default(),
35 (None, true) => self,
36 (Some(SketchItemId::Entity(id)), is_additive) => match self {
37 Self::Entities(v) if is_additive => Self::Entities(toggle(v, id)),
38 _ => Self::Entities(vec![id]),
39 },
40 (Some(SketchItemId::Relation(id)), _) => Self::Relation(id),
41 (Some(SketchItemId::Dimension(id)), _) => Self::Dimension(id),
42 }
43 }
44}
45
46#[must_use]
47pub fn picked_to_item(picked: PickedItem) -> Option<SketchItemId> {
48 match picked {
49 PickedItem::Point(id)
50 | PickedItem::Line(id)
51 | PickedItem::Arc(id)
52 | PickedItem::Circle(id) => Some(SketchItemId::Entity(id)),
53 PickedItem::Relation(id) => Some(SketchItemId::Relation(id)),
54 PickedItem::Dimension(id) => Some(SketchItemId::Dimension(id)),
55 PickedItem::BrepFace(_) | PickedItem::BrepEdge(_) | PickedItem::BrepVertex(_) => None,
56 }
57}
58
59fn toggle(mut v: Vec<SketchEntityId>, id: SketchEntityId) -> Vec<SketchEntityId> {
60 match v.iter().position(|x| *x == id) {
61 Some(idx) => {
62 v.remove(idx);
63 }
64 None => v.push(id),
65 }
66 v
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72 use crate::sketch_mode::Plane;
73 use crate::tools::add_point;
74 use bone_document::{
75 DimensionKind, EditOutcome, Sketch, SketchDimension, SketchEdit, SketchRelation,
76 };
77 use bone_types::{Length, Point2};
78 use uom::si::length::millimeter;
79
80 fn three_ids() -> (SketchEntityId, SketchEntityId, SketchEntityId) {
81 let s = Sketch::new(Plane::Xy.basis());
82 let (s, a) = add_point(s, Point2::from_mm(0.0, 0.0));
83 let (s, b) = add_point(s, Point2::from_mm(1.0, 0.0));
84 let (_, c) = add_point(s, Point2::from_mm(2.0, 0.0));
85 (a, b, c)
86 }
87
88 fn rel_id() -> SketchRelationId {
89 let s = Sketch::new(Plane::Xy.basis());
90 let (s, a) = add_point(s, Point2::from_mm(0.0, 0.0));
91 let Ok((_, EditOutcome::Relation(id))) =
92 s.apply(SketchEdit::AddRelation(SketchRelation::Fix(a)))
93 else {
94 panic!("expected Relation outcome");
95 };
96 id
97 }
98
99 fn dim_id() -> SketchDimensionId {
100 let s = Sketch::new(Plane::Xy.basis());
101 let (s, a) = add_point(s, Point2::from_mm(0.0, 0.0));
102 let (s, b) = add_point(s, Point2::from_mm(1.0, 0.0));
103 let dim = SketchDimension::Linear {
104 a,
105 b,
106 value: Length::new::<millimeter>(1.0),
107 kind: DimensionKind::Driving,
108 };
109 let Ok((_, EditOutcome::Dimension(id))) = s.apply(SketchEdit::AddDimension(dim)) else {
110 panic!("expected Dimension outcome");
111 };
112 id
113 }
114
115 #[test]
116 fn default_selection_is_empty_entities() {
117 assert!(Selection::default().entity_ids().is_empty());
118 assert!(matches!(Selection::default(), Selection::Entities(_)));
119 }
120
121 #[test]
122 fn pick_replace_entity_overwrites_existing() {
123 let (a, b, _) = three_ids();
124 let s = Selection::default()
125 .picked(Some(SketchItemId::Entity(a)), false)
126 .picked(Some(SketchItemId::Entity(b)), false);
127 assert_eq!(s.entity_ids(), &[b]);
128 }
129
130 #[test]
131 fn pick_additive_extends_then_toggles_off() {
132 let (a, b, _) = three_ids();
133 let s = Selection::default()
134 .picked(Some(SketchItemId::Entity(a)), true)
135 .picked(Some(SketchItemId::Entity(b)), true);
136 assert_eq!(s.entity_ids(), &[a, b]);
137 let s = s.picked(Some(SketchItemId::Entity(a)), true);
138 assert_eq!(s.entity_ids(), &[b]);
139 }
140
141 #[test]
142 fn pick_replace_with_none_clears() {
143 let (a, b, _) = three_ids();
144 let s = Selection::default()
145 .picked(Some(SketchItemId::Entity(a)), true)
146 .picked(Some(SketchItemId::Entity(b)), true)
147 .picked(None, false);
148 assert!(s.entity_ids().is_empty());
149 }
150
151 #[test]
152 fn pick_additive_with_none_preserves() {
153 let (a, _, _) = three_ids();
154 let s = Selection::default()
155 .picked(Some(SketchItemId::Entity(a)), true)
156 .picked(None, true);
157 assert_eq!(s.entity_ids(), &[a]);
158 }
159
160 #[test]
161 fn pick_relation_replaces_entities() {
162 let (a, b, _) = three_ids();
163 let r = rel_id();
164 let s = Selection::default()
165 .picked(Some(SketchItemId::Entity(a)), true)
166 .picked(Some(SketchItemId::Entity(b)), true)
167 .picked(Some(SketchItemId::Relation(r)), false);
168 assert!(s.entity_ids().is_empty());
169 assert_eq!(s, Selection::Relation(r));
170 }
171
172 #[test]
173 fn pick_entity_after_relation_clears_relation() {
174 let (a, _, _) = three_ids();
175 let r = rel_id();
176 let s = Selection::default()
177 .picked(Some(SketchItemId::Relation(r)), false)
178 .picked(Some(SketchItemId::Entity(a)), false);
179 assert_eq!(s.entity_ids(), &[a]);
180 }
181
182 #[test]
183 fn pick_dimension_replaces_relation() {
184 let r = rel_id();
185 let d = dim_id();
186 let s = Selection::default()
187 .picked(Some(SketchItemId::Relation(r)), false)
188 .picked(Some(SketchItemId::Dimension(d)), false);
189 assert_eq!(s, Selection::Dimension(d));
190 }
191}