1use geop_core_editor::selectable::SelectableElement;
10use geop_core_editor::view::drawn;
11use geop_core_math::geop_error::GeopResult;
12use geop_core_program::{
13 element::{ElementReference, HasElements, Kind, PickedElement, whole},
14 placed::Located,
15 reader::Reader,
16};
17use geop_core_topology::{EdgeId, FaceId, SolidId, VertexId};
18
19use crate::{
20 NamedModel, RefId, model,
21 tangent::{TangentChain, tangent_chain},
22};
23
24const MODEL: &str = "model";
26
27#[derive(Clone, Copy, Debug)]
29pub struct ModelElement<'a, Id> {
30 pub model: &'a NamedModel,
31 pub id: Id,
32}
33
34pub struct Vertex;
36
37pub struct Edge;
39
40pub struct Face;
42
43pub struct Solid;
45
46pub struct EdgeChain;
51
52impl Kind for Vertex {
53 const NAME: &'static str = drawn::VERTEX;
54}
55
56impl Kind for Edge {
57 const NAME: &'static str = drawn::EDGE;
58}
59
60impl Kind for Face {
61 const NAME: &'static str = drawn::FACE;
62}
63
64impl Kind for Solid {
65 const NAME: &'static str = "solid";
66}
67
68impl Kind for EdgeChain {
69 const NAME: &'static str = "edge_chain";
70}
71
72#[derive(Clone, Debug)]
74pub struct ChainElement<'a> {
75 pub model: &'a NamedModel,
76 pub chain: TangentChain,
77}
78
79impl ChainElement<'_> {
80 pub fn edge_names(&self) -> GeopResult<Vec<&str>> {
82 (self.chain.edges().into_iter())
83 .map(|edge| self.model.name(edge))
84 .collect()
85 }
86}
87
88impl HasElements<EdgeChain> for NamedModel {
89 type Element<'a> = ChainElement<'a>;
90 const SINGLETON: Option<&'static str> = Some(MODEL);
91
92 fn element(&self, name: &str) -> GeopResult<ChainElement<'_>> {
93 let chain = tangent_chain(self, self.edge_id(name)?)?;
94 Ok(ChainElement { model: self, chain })
95 }
96}
97
98impl Kind for NamedModel {
101 const NAME: &'static str = "model";
102}
103
104impl HasElements<NamedModel> for NamedModel {
105 type Element<'a> = &'a NamedModel;
106 const SINGLETON: Option<&'static str> = Some(MODEL);
107
108 fn element(&self, name: &str) -> GeopResult<&NamedModel> {
109 whole(self, name)
110 }
111}
112
113macro_rules! named_elements {
115 ($($kind:ty => $id:ty, $lookup:ident;)*) => {$(
116 impl HasElements<$kind> for NamedModel {
117 type Element<'a> = ModelElement<'a, $id>;
118 const SINGLETON: Option<&'static str> = Some(MODEL);
119
120 fn element(&self, name: &str) -> GeopResult<ModelElement<'_, $id>> {
121 Ok(ModelElement {
122 model: self,
123 id: self.$lookup(name)?,
124 })
125 }
126 }
127 )*};
128}
129
130named_elements! {
131 Vertex => VertexId, vertex_id;
132 Edge => EdgeId, edge_id;
133 Face => FaceId, face_id;
134 Solid => SolidId, solid_id;
135}
136
137pub fn read_model<'a>(reader: &Reader<'a>) -> GeopResult<std::borrow::Cow<'a, NamedModel>> {
140 if !reader.defines(&model()) {
141 return Ok(std::borrow::Cow::Owned(NamedModel::new()));
142 }
143 let whole = ElementReference::<NamedModel, NamedModel>::whole(model());
144 Ok(std::borrow::Cow::Borrowed(reader.retrieve_element(&whole)?))
145}
146
147pub(crate) fn drawn_entity(model: &NamedModel, name: &str) -> Vec<PickedElement> {
150 let picked = |kind: &str, name: &str| PickedElement {
151 target: Located::new(MODEL),
152 kind: kind.to_string(),
153 name: name.to_string(),
154 };
155 match model.id_of(name) {
156 Some(RefId::Vertex(_)) => vec![picked(Vertex::NAME, name)],
157 Some(RefId::Edge(_)) => vec![picked(Edge::NAME, name)],
158 Some(RefId::Face(_)) => vec![picked(Face::NAME, name)],
159 Some(RefId::Solid(solid)) => (model.topology().solid_faces(solid))
160 .unwrap_or_default()
161 .into_iter()
162 .filter_map(|face| model.name_of(face))
163 .map(|face| picked(Face::NAME, face))
164 .collect(),
165 None => Vec::new(),
166 }
167}
168
169macro_rules! drawn_as_itself {
171 ($($kind:ty),*) => {$(
172 impl SelectableElement<NamedModel> for $kind {
173 const PICKED_FROM: &'static [&'static str] = &[<$kind as Kind>::NAME];
174
175 fn select(
176 _: &Reader,
177 hit: &PickedElement,
178 ) -> Option<ElementReference<NamedModel, Self>> {
179 ElementReference::from_picked(hit)
180 }
181 }
182 )*};
183}
184
185drawn_as_itself!(Vertex, Edge, Face);
186
187impl SelectableElement<NamedModel> for Solid {
189 const PICKED_FROM: &'static [&'static str] = &[Face::NAME];
190
191 fn select(reader: &Reader, hit: &PickedElement) -> Option<ElementReference<NamedModel, Self>> {
192 let face = ElementReference::<NamedModel, Face>::from_picked(hit)?;
193 let face = reader.retrieve_element(&face).ok()?;
194 let solid = face.model.topology().solid_of_face(face.id).ok()??;
195 let name = face.model.name_of(solid)?;
196 Some(ElementReference::new(model(), name))
197 }
198
199 fn drawn(reader: &Reader, solid: &ElementReference<NamedModel, Self>) -> Vec<PickedElement> {
200 (read_model(reader).ok())
201 .map(|model| drawn_entity(&model, solid.name()))
202 .unwrap_or_default()
203 }
204}
205
206impl SelectableElement<NamedModel> for EdgeChain {
209 const PICKED_FROM: &'static [&'static str] = &[Edge::NAME];
210
211 fn select(reader: &Reader, hit: &PickedElement) -> Option<ElementReference<NamedModel, Self>> {
212 let through = ElementReference::<NamedModel, Self>::new(model(), &hit.name);
213 (hit.kind == Edge::NAME).then_some(())?;
214 let chain = reader.retrieve_element(&through).ok()?;
215 let first = chain.edge_names().ok()?.into_iter().min()?;
216 Some(ElementReference::new(model(), first))
217 }
218
219 fn drawn(reader: &Reader, chain: &ElementReference<NamedModel, Self>) -> Vec<PickedElement> {
220 let Ok(chain) = reader.retrieve_element(chain) else {
221 return Vec::new();
222 };
223 (chain.edge_names().unwrap_or_default().into_iter())
224 .map(|name| ElementReference::<NamedModel, Edge>::new(model(), name).picked())
225 .collect()
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use geop_core_editor::selectable::selected;
232 use geop_core_program::part::Part;
233
234 use super::*;
235
236 fn cube() -> Part {
239 crate::testing::given_part(&crate::testing::unit_cube()).unwrap()
240 }
241
242 fn edge(name: &str) -> PickedElement {
243 ElementReference::<NamedModel, Edge>::new(model(), name).picked()
244 }
245
246 #[test]
251 fn an_edge_chain_is_picked_by_an_edge() {
252 let part = cube();
253 let reader = part.current();
254 let chain = |name: &str| ElementReference::<NamedModel, EdgeChain>::new(model(), name);
255 let picked: Vec<ElementReference<NamedModel, EdgeChain>> = selected(&reader, &[edge("e3")]);
256 assert_eq!(picked, [chain("e3")]);
257 assert_eq!(EdgeChain::drawn(&reader, &chain("e3")), [edge("e3")]);
258 let both: Vec<ElementReference<NamedModel, EdgeChain>> =
259 selected(&reader, &[chain("e3").picked(), edge("e5")]);
260 assert_eq!(both, [chain("e3"), chain("e5")]);
261 let again: Vec<ElementReference<NamedModel, EdgeChain>> =
262 selected(&reader, &[chain("e3").picked(), edge("e3")]);
263 assert_eq!(again, Vec::<ElementReference<NamedModel, EdgeChain>>::new());
264 }
265
266 fn face(name: &str) -> ElementReference<NamedModel, Face> {
267 ElementReference::new(model(), name)
268 }
269
270 #[test]
273 fn elements_resolve_by_name() {
274 let part = cube();
275 let reader = part.current();
276 let vertex = ElementReference::<NamedModel, Vertex>::in_singleton("v0").unwrap();
277 let found = reader.retrieve_element(&vertex).unwrap();
278 assert_eq!(found.model.name_of(found.id), Some("v0"));
279 assert!(reader.retrieve_element(&face("f6")).is_err());
280 assert_eq!(
281 serde_json::to_value(face("f0")).unwrap(),
282 serde_json::json!("f0")
283 );
284 assert_eq!(read_model(&reader).unwrap().solid_names(), ["s0"]);
285 let empty = Part::default();
286 assert!(
287 read_model(&empty.current())
288 .unwrap()
289 .solid_names()
290 .is_empty()
291 );
292 }
293
294 #[test]
296 fn a_solid_is_picked_by_its_faces() {
297 let part = cube();
298 let reader = part.current();
299 let solid = ElementReference::<NamedModel, Solid>::new(model(), "s0");
300 let picked: Vec<ElementReference<NamedModel, Solid>> =
301 selected(&reader, &[face("f0").picked()]);
302 assert_eq!(picked, std::slice::from_ref(&solid));
303 let faces: Vec<PickedElement> = (0..6).map(|k| face(&format!("f{k}")).picked()).collect();
304 let drawn = Solid::drawn(&reader, &solid);
305 assert_eq!(drawn.len(), faces.len());
306 assert!(faces.iter().all(|f| drawn.contains(f)));
307 assert_eq!(Solid::PICKED_FROM, ["face"]);
308 }
309}