Skip to main content

geop_core_program/
element.rs

1//! [`ElementReference`]: an element of a target by its stable name — an edge
2//! or a face of a part's topology, a line of a sketch — typed by the target
3//! it is in and the [`Kind`] of element it is, as a [`TargetReference`] is
4//! typed by its target. What an operation is given to build on, and what a
5//! pick in the viewport becomes ([`PickedElement`]).
6//!
7//! The crate that defines a target defines the kinds of element it holds and
8//! how one is resolved ([`HasElements`]); an operation that builds on one
9//! depends on that crate and names the kind in its arguments' types, so a
10//! reference of the wrong kind does not compile, and no crate needs to know
11//! every kind there is.
12
13use std::marker::PhantomData;
14
15use geop_core_math::geop_error::{GeopError, GeopResult};
16use serde::{Deserialize, Deserializer, Serialize, Serializer};
17
18use crate::{placed::Located, target::TargetReference};
19
20/// A kind of element a target holds by stable name: an edge or a face of a
21/// topology, a line of a sketch. A unit struct, defined by the crate that
22/// defines the target.
23pub trait Kind: 'static {
24    /// The kind's name, as a pick says what it picked: `"edge"`.
25    const NAME: &'static str;
26}
27
28/// A target that holds elements of the kind `K`, each by a stable name.
29/// Elements are the only way a target is read: one meant to be read whole —
30/// a sketch, to sweep — is a kind of its own, `impl Kind for Sketch` and
31/// `impl HasElements<Sketch> for Sketch`, its one element the empty name
32/// ([`ElementReference::whole`]).
33pub trait HasElements<K: Kind>: 'static {
34    /// What an element resolves to: a borrow of it, or what it gives.
35    type Element<'a>;
36
37    /// The name of the one target of this type a part has, if it has only
38    /// one — the topology, say: a reference into it is saved as the
39    /// element's name alone.
40    const SINGLETON: Option<&'static str> = None;
41
42    /// The element `name`, or why there is none.
43    fn element(&self, name: &str) -> GeopResult<Self::Element<'_>>;
44
45    /// What the target reads as where no step made it, if it reads as
46    /// anything then — the datums every part starts with. None by default:
47    /// reading it fails.
48    fn undefined() -> Option<&'static Self> {
49        None
50    }
51
52    /// Whether the target named `name` could be one of this type: what
53    /// tells a pick of an element of it from one of a target of another
54    /// type with elements of the same kind ([`ElementReference::from_picked`]).
55    /// The singleton's name, for a singleton; any name, by default.
56    fn names_target(name: &str) -> bool {
57        Self::SINGLETON.is_none_or(|singleton| singleton == name)
58    }
59}
60
61/// The element of the kind `K` named `name` in the target `target`. Saved
62/// as `{"target": "/:s1", "name": "c3"}`, its target spelled as a
63/// [`Located`] — `/bolt:s1` in the part placed as `bolt`; in a target of
64/// the part itself that is its only one of its type
65/// ([`HasElements::SINGLETON`]) as `"c3"`; the whole target
66/// ([`ElementReference::whole`]) as `{"target": "/:s1"}`.
67pub struct ElementReference<T, K> {
68    target: TargetReference<T>,
69    name: String,
70    kind: PhantomData<fn() -> K>,
71}
72
73// By hand, since a derive would ask the same of `T` and `K`, and a
74// reference is only names.
75impl<T, K> Clone for ElementReference<T, K> {
76    fn clone(&self) -> Self {
77        Self::new(self.target.clone(), self.name.clone())
78    }
79}
80
81impl<T, K> PartialEq for ElementReference<T, K> {
82    fn eq(&self, other: &Self) -> bool {
83        self.target == other.target && self.name == other.name
84    }
85}
86
87impl<T, K> Eq for ElementReference<T, K> {}
88
89impl<T, K: Kind> std::fmt::Debug for ElementReference<T, K> {
90    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
91        write!(
92            f,
93            "{}({:?}, {:?})",
94            K::NAME,
95            self.target.location(),
96            self.name
97        )
98    }
99}
100
101impl<T, K> ElementReference<T, K> {
102    pub fn new(target: TargetReference<T>, name: impl Into<String>) -> Self {
103        Self {
104            target,
105            name: name.into(),
106            kind: PhantomData,
107        }
108    }
109
110    /// The target the element is in.
111    pub fn target(&self) -> &TargetReference<T> {
112        &self.target
113    }
114
115    /// The element's stable name in its target.
116    pub fn name(&self) -> &str {
117        &self.name
118    }
119
120    /// The path of the instance the element is of: empty for one of the
121    /// part itself (see [`crate::instance`]).
122    pub fn instance(&self) -> &str {
123        self.target.instance()
124    }
125
126    /// The same element as its instance's own part names it: itself, for
127    /// one of the part itself.
128    pub fn local(&self) -> Self {
129        Self::new(self.target.local(), self.name.clone())
130    }
131
132    /// The same element of the instance `instance` of the part — of the
133    /// instance at that path, if it is already of one: what an element of
134    /// a placed part is, read from the part it is placed in.
135    pub fn behind(&self, instance: &str) -> Self {
136        Self::new(self.target.behind(instance), self.name.clone())
137    }
138}
139
140impl<T: HasElements<K>, K: Kind> ElementReference<T, K> {
141    /// The whole of `target`, as its element of the empty name: for a
142    /// target meant to be read whole, which is a kind of its own (see
143    /// [`HasElements`]).
144    pub fn whole(target: TargetReference<T>) -> Self {
145        Self::new(target, "")
146    }
147
148    /// The element `name` of the singleton target ([`HasElements::SINGLETON`]).
149    pub fn in_singleton(name: impl Into<String>) -> GeopResult<Self> {
150        let target = T::SINGLETON.ok_or_else(|| {
151            GeopError::new(format!(
152                "a `{}` is no part's only target of its type: name the target of {}",
153                std::any::type_name::<T>(),
154                K::NAME
155            ))
156        })?;
157        Ok(Self::new(TargetReference::new(target), name))
158    }
159
160    /// The reference `picked` is, if it picked an element of the kind `K`
161    /// of a target of the type `T` — of the part itself, or of an instance
162    /// placed in it.
163    pub fn from_picked(picked: &PickedElement) -> Option<Self> {
164        (picked.kind == K::NAME && T::names_target(picked.target.name())).then(|| {
165            Self::new(
166                TargetReference::located(picked.target.clone()),
167                &picked.name,
168            )
169        })
170    }
171
172    /// The reference as a pick names it: what the front end shows selected.
173    pub fn picked(&self) -> PickedElement {
174        PickedElement {
175            target: self.target.location().clone(),
176            kind: K::NAME.to_string(),
177            name: self.name.clone(),
178        }
179    }
180}
181
182/// What a target read whole resolves its one element to: itself, for the
183/// empty name ([`ElementReference::whole`]); any other names nothing.
184pub fn whole<'a, T>(target: &'a T, name: &str) -> GeopResult<&'a T> {
185    match name {
186        "" => Ok(target),
187        _ => Err(GeopError::new(format!(
188            "`{}` is read whole, not by `{name}`",
189            std::any::type_name::<T>()
190        ))),
191    }
192}
193
194/// An element as a pick in the viewport names it: what a front end sends
195/// and is sent, which knows no types. Only [`ElementReference::from_picked`]
196/// reads it, so an operation never matches on it.
197#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
198pub struct PickedElement {
199    /// The target the element is in, behind the path of the instance it
200    /// is of: `/bolt:model`, `/:model` for one of the part itself.
201    pub target: Located,
202    /// Its kind's [`Kind::NAME`].
203    pub kind: String,
204    /// Its stable name in the target.
205    pub name: String,
206}
207
208impl PickedElement {
209    /// How a dialog names it: its kind and its name.
210    pub fn label(&self) -> String {
211        format!("{} {}", self.kind, self.name)
212    }
213
214    /// The path of the instance it is of: empty for the part itself.
215    pub fn instance(&self) -> &str {
216        self.target.path()
217    }
218
219    /// The same pick as its instance's own part names it.
220    pub fn local(&self) -> Self {
221        Self {
222            target: self.target.local(),
223            ..self.clone()
224        }
225    }
226
227    /// The same pick of the instance `instance` of the part.
228    pub fn behind(&self, instance: &str) -> Self {
229        Self {
230            target: self.target.behind(instance),
231            ..self.clone()
232        }
233    }
234}
235
236/// The saved form of an [`ElementReference`]: its name alone in the
237/// singleton target of the part itself, else the target too.
238#[derive(Serialize, Deserialize)]
239#[serde(untagged)]
240enum Saved {
241    Name(String),
242    InTarget { target: Located, name: String },
243    Whole { target: Located },
244}
245
246impl<T: HasElements<K>, K: Kind> Serialize for ElementReference<T, K> {
247    fn serialize<Se: Serializer>(&self, serializer: Se) -> Result<Se::Ok, Se::Error> {
248        let target = self.target.location().clone();
249        let saved = if target.path().is_empty() && T::SINGLETON == Some(target.name()) {
250            Saved::Name(self.name.clone())
251        } else if self.name.is_empty() {
252            Saved::Whole { target }
253        } else {
254            Saved::InTarget {
255                target,
256                name: self.name.clone(),
257            }
258        };
259        saved.serialize(serializer)
260    }
261}
262
263impl<'de, T: HasElements<K>, K: Kind> Deserialize<'de> for ElementReference<T, K> {
264    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
265        match Saved::deserialize(deserializer)? {
266            Saved::Name(name) => Self::in_singleton(name).map_err(serde::de::Error::custom),
267            Saved::InTarget { target, name } => {
268                Ok(Self::new(TargetReference::located(target), name))
269            }
270            Saved::Whole { target } => Ok(Self::whole(TargetReference::located(target))),
271        }
272    }
273}
274
275#[cfg(test)]
276mod tests {
277    use std::collections::BTreeMap;
278
279    use super::*;
280    use crate::{
281        operation_registry::OperationRegistry, part::Part, runner::Runner, target::Target,
282    };
283
284    /// A target of named lengths, of which a part has many.
285    #[derive(Clone, PartialEq)]
286    struct Lengths(BTreeMap<String, f64>);
287
288    impl Target for Lengths {}
289
290    /// A target of named lengths, of which a part has one.
291    #[derive(Clone, PartialEq)]
292    struct Stock(Lengths);
293
294    struct Length;
295
296    impl Kind for Length {
297        const NAME: &'static str = "length";
298    }
299
300    impl HasElements<Length> for Lengths {
301        type Element<'a> = f64;
302
303        fn element(&self, name: &str) -> GeopResult<f64> {
304            (self.0.get(name).copied()).ok_or_else(|| GeopError::new(format!("no length {name}")))
305        }
306    }
307
308    impl HasElements<Length> for Stock {
309        type Element<'a> = f64;
310        const SINGLETON: Option<&'static str> = Some("stock");
311
312        fn element(&self, name: &str) -> GeopResult<f64> {
313            self.0.element(name)
314        }
315    }
316
317    fn lengths(entries: &[(&str, f64)]) -> Lengths {
318        Lengths(entries.iter().map(|&(n, v)| (n.to_string(), v)).collect())
319    }
320
321    /// An element resolves through a reader, which makes the target reading
322    /// it depend on the target it is in.
323    #[test]
324    fn a_step_reading_an_element_is_rebuilt_when_its_target_changes() {
325        let runner = Runner::new(OperationRegistry::default());
326        let mut part = Part::default();
327        let total = TargetReference::<f64>::new("total");
328        let run = |part: &mut Part, a: f64| {
329            let defined = TargetReference::<Lengths>::new("s1");
330            (runner.run_step(part, "s1", |ctx| {
331                ctx.produce(&defined, &a, |_, &a| Ok(lengths(&[("a", a)])))
332            }))
333            .unwrap();
334            let a = ElementReference::<Lengths, Length>::new(defined, "a");
335            (runner.run_step(part, "s2", |ctx| {
336                ctx.produce(
337                    &total,
338                    &a,
339                    |reader, a| Ok(2.0 * reader.retrieve_element(a)?),
340                )
341            }))
342            .unwrap();
343        };
344        run(&mut part, 1.0);
345        assert_eq!(part.current().target(&total).unwrap(), &2.0);
346        run(&mut part, 3.0);
347        assert_eq!(part.current().target(&total).unwrap(), &6.0);
348    }
349
350    #[test]
351    fn a_missing_element_says_which() {
352        let runner = Runner::new(OperationRegistry::default());
353        let mut part = Part::default();
354        let defined = TargetReference::<Lengths>::new("s1");
355        (runner.run_step(&mut part, "s1", |ctx| {
356            ctx.produce(&defined, &(), |_, _| Ok(lengths(&[("a", 1.0)])))
357        }))
358        .unwrap();
359        let b = ElementReference::<Lengths, Length>::new(defined, "b");
360        let error = part.current().retrieve_element(&b).unwrap_err().to_string();
361        assert!(error.contains("no length b"), "{error}");
362        assert!(error.contains("length(\"/:s1\", \"b\")"), "{error}");
363    }
364
365    /// In a singleton target an element is saved by its name alone; in any
366    /// other with its target's.
367    #[test]
368    fn saved_forms() {
369        let in_stock = ElementReference::<Stock, Length>::in_singleton("a").unwrap();
370        let saved = serde_json::to_value(&in_stock).unwrap();
371        assert_eq!(saved, serde_json::json!("a"));
372        let read: ElementReference<Stock, Length> = serde_json::from_value(saved).unwrap();
373        assert_eq!(read, in_stock);
374
375        let in_s1 = ElementReference::<Lengths, Length>::new(TargetReference::new("s1"), "a");
376        let saved = serde_json::to_value(&in_s1).unwrap();
377        assert_eq!(saved, serde_json::json!({"target": "/:s1", "name": "a"}));
378        let read: ElementReference<Lengths, Length> = serde_json::from_value(saved).unwrap();
379        assert_eq!(read, in_s1);
380
381        // Of a placed part, the target says which: the singleton too.
382        let behind = in_stock.behind("p");
383        let saved = serde_json::to_value(&behind).unwrap();
384        assert_eq!(
385            saved,
386            serde_json::json!({"target": "/p:stock", "name": "a"})
387        );
388        let read: ElementReference<Stock, Length> = serde_json::from_value(saved).unwrap();
389        assert_eq!(read, behind);
390        assert_eq!(read.local(), in_stock);
391
392        // A bare name needs a singleton target to be in, and a target that
393        // does not say where it is names nothing.
394        assert!(serde_json::from_value::<ElementReference<Lengths, Length>>("a".into()).is_err());
395        let bare = serde_json::json!({"target": "s1", "name": "a"});
396        assert!(serde_json::from_value::<ElementReference<Lengths, Length>>(bare).is_err());
397    }
398
399    /// A pick becomes a reference only of its own kind.
400    #[test]
401    fn a_pick_becomes_a_reference_of_its_kind() {
402        let picked = PickedElement {
403            target: Located::new("s1"),
404            kind: "length".into(),
405            name: "a".into(),
406        };
407        let reference = ElementReference::<Lengths, Length>::from_picked(&picked).unwrap();
408        assert_eq!(reference.picked(), picked);
409        let other = PickedElement {
410            kind: "edge".into(),
411            ..picked
412        };
413        assert!(ElementReference::<Lengths, Length>::from_picked(&other).is_none());
414    }
415}