Skip to main content

geop_ops_model/
elements.rs

1//! The kinds of element a step refers to in the part's model ([`model`]):
2//! a [`Vertex`], an [`Edge`], a [`Face`], a [`Solid`] — each an
3//! [`ElementReference`] into the model by its stable name — an
4//! [`EdgeChain`], the tangent chain of edges through an edge, and the model
5//! itself, read whole. Each says how a dialog picks it
6//! ([`SelectableElement`]): a vertex, an edge or a face as itself, a solid
7//! by a face of it, an edge chain by an edge of it.
8
9use 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
24/// The name the part's model is a target by: there is one.
25const MODEL: &str = "model";
26
27/// An element of the model: the model, and the entity's id in it.
28#[derive(Clone, Copy, Debug)]
29pub struct ModelElement<'a, Id> {
30    pub model: &'a NamedModel,
31    pub id: Id,
32}
33
34/// A vertex of the model.
35pub struct Vertex;
36
37/// An edge of the model, of a solid, a sheet or a wire.
38pub struct Edge;
39
40/// A face of the model.
41pub struct Face;
42
43/// A solid of the model, as a whole.
44pub struct Solid;
45
46/// The tangent chain of edges through an edge of the model (see
47/// [`tangent_chain`]): named by any edge of it, and selected by the
48/// smallest name of its edges, the one name a dialog holds it by however
49/// it was picked.
50pub 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/// An edge chain of the model: the model, and the chain.
73#[derive(Clone, Debug)]
74pub struct ChainElement<'a> {
75    pub model: &'a NamedModel,
76    pub chain: TangentChain,
77}
78
79impl ChainElement<'_> {
80    /// The names of its edges, in the order the chain runs.
81    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
98/// The model read whole: a kind of its own, its one element the empty
99/// name.
100impl 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
113/// Elements of the model of the kind `K`, by the id `id` its names have.
114macro_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
137/// The model as `reader` reads it: empty, before the first step that
138/// builds any topology.
139pub 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
147/// What the editor draws of the model's entity `name`: the entity itself,
148/// a vertex, an edge or a face — or, for a solid, its faces.
149pub(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
169/// An element drawn as itself: a click on it selects it.
170macro_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
187/// A solid is picked by a face of it, and lit as all of its faces.
188impl 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
206/// An edge chain is picked by an edge of it, held by the smallest name of
207/// its edges, and lit as all of them.
208impl 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    /// A part of one step whose model is a unit cube, every entity named
237    /// by its kind and its position among those: `e0` … `e11`.
238    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    /// An edge chain is picked by clicking an edge of it, held by the
247    /// smallest name of its edges, lit as them, and taken out by clicking
248    /// one again. On a cube, whose faces meet square, every edge is a
249    /// chain of its own.
250    #[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    /// Every kind resolves by name in the one model, and is saved by its
271    /// name alone.
272    #[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    /// A solid is picked by a face of it, and lit as its faces.
295    #[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}