Skip to main content

geop_core_editor/view/
lit.rs

1//! [`Lit`]: what of a drawn part a set of elements lights — the faces,
2//! edges, vertices, solids, sketches and datums a viewer draws lit for the
3//! highlights of a [`crate::editing::Presentation`], or for what an answer
4//! or an item of the structure points at — and [`Lighting`], what of the
5//! part drawn and of each part placed in it is lit. The editor sends the
6//! lighting ([`crate::editor::Update::lit`]); a front end only draws it,
7//! so that every front end lights the same things.
8
9use std::collections::{BTreeMap, BTreeSet};
10
11use geop_core_program::element::PickedElement;
12use serde::Serialize;
13
14use super::{PartView, drawn};
15
16/// What a drawn part's elements are named in: the targets a lit element
17/// is of, which tell what it lights.
18#[derive(Clone, Debug, Default, PartialEq)]
19pub struct Targets {
20    /// The target the part's vertices, edges, faces and solids are of.
21    pub model: String,
22    /// The target its datums are of.
23    pub datum_target: String,
24    /// Each sketch's name, by the target it is.
25    pub sketches: BTreeMap<String, String>,
26}
27
28/// What a set of elements lights of one drawn part (see the module docs).
29#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize)]
30pub struct Lit {
31    /// Whether all of it is lit: a placed part hovered or selected whole.
32    pub whole: bool,
33    pub faces: BTreeSet<String>,
34    pub edges: BTreeSet<String>,
35    pub vertices: BTreeSet<String>,
36    pub solids: BTreeSet<String>,
37    /// Steps whose features' faces are lit, wherever they are.
38    pub features: BTreeSet<String>,
39    /// Whole sketches, by name.
40    pub sketches: BTreeSet<String>,
41    /// Curves of sketches, by the sketch's name and the curve's id.
42    pub curves: BTreeSet<(String, String)>,
43    /// Points of sketches, by the sketch's name and the point's id.
44    pub sketch_points: BTreeSet<(String, String)>,
45    /// Datums and their components, by name as drawn: `origin:x` an axis.
46    pub datums: BTreeSet<String>,
47}
48
49impl Targets {
50    /// The targets the elements of `view` are named in.
51    pub fn of(view: &PartView) -> Self {
52        Self {
53            model: view.model.clone(),
54            datum_target: view.datum_target.clone(),
55            sketches: (view.sketches.iter())
56                .map(|s| (s.target.clone(), s.name.clone()))
57                .collect(),
58        }
59    }
60}
61
62impl Lit {
63    /// What `elements` light of the part named in `targets`, drawn as the
64    /// placed part `instance` — `""` for the part itself: those of the
65    /// part's own model lighting its faces, edges, vertices and solids by
66    /// name, whatever kind of thing drawn as them they are (the plane a
67    /// face is, the line an edge runs along); those of a sketch, it, or
68    /// its curves and points; those of its datums, them; a feature, the
69    /// faces it made.
70    pub fn of<'e>(
71        targets: &Targets,
72        instance: &str,
73        elements: impl IntoIterator<Item = &'e PickedElement>,
74    ) -> Self {
75        let mut lit = Self::default();
76        for e in elements {
77            if e.target.path() != instance {
78                continue;
79            }
80            let target = e.target.name();
81            let name = e.name.clone();
82            if e.kind == "feature" {
83                lit.features.insert(name);
84            } else if let Some(sketch) = targets.sketches.get(target) {
85                match e.kind.as_str() {
86                    drawn::SKETCH_CURVE => lit.curves.insert((sketch.clone(), name)),
87                    drawn::SKETCH_POINT => lit.sketch_points.insert((sketch.clone(), name)),
88                    _ => lit.sketches.insert(sketch.clone()),
89                };
90            } else if target == targets.datum_target {
91                lit.datums.insert(name);
92            } else if target == targets.model {
93                match e.kind.as_str() {
94                    drawn::FACE | "plane" | "round" => lit.faces.insert(name),
95                    drawn::EDGE | "line" | "curve" | "circle" | "path" => lit.edges.insert(name),
96                    drawn::VERTEX | "point" => lit.vertices.insert(name),
97                    "solid" | "body" => lit.solids.insert(name),
98                    _ => false,
99                };
100            }
101        }
102        lit
103    }
104
105    /// Whether the face `name`, of `solid`, made by `feature`, is lit.
106    pub fn face(&self, name: &str, solid: Option<&str>, feature: Option<&str>) -> bool {
107        self.whole
108            || self.faces.contains(name)
109            || solid.is_some_and(|s| self.solids.contains(s))
110            || feature.is_some_and(|f| self.features.contains(f))
111    }
112
113    pub fn is_empty(&self) -> bool {
114        *self == Self::default()
115    }
116}
117
118/// What is lit of the part drawn — under `""` — and of each part placed in
119/// it, by its name (`bolt`, `asm/bolt`): only those of which something is.
120pub type Lighting = BTreeMap<String, Lit>;
121
122/// What `elements` light of `part` and the parts placed in it, the placed
123/// parts `whole` lit whole. Only the parts an element is of are looked at,
124/// so lighting one face of an assembly of thousands of parts is cheap.
125pub fn lighting(part: &PartView, elements: &[PickedElement], whole: &BTreeSet<&str>) -> Lighting {
126    let mut by_part: BTreeMap<&str, Vec<&PickedElement>> = BTreeMap::new();
127    for e in elements {
128        by_part.entry(e.target.path()).or_default().push(e);
129    }
130    let mut lighting = Lighting::new();
131    let mut light = |name: &str, view: &PartView, whole: bool| {
132        let mut lit = Lit::of(
133            &Targets::of(view),
134            name,
135            by_part.get(name).into_iter().flatten().copied(),
136        );
137        lit.whole = whole;
138        if !lit.is_empty() {
139            lighting.insert(name.to_string(), lit);
140        }
141    };
142    if by_part.contains_key("") {
143        light("", part, false);
144    }
145    if by_part.keys().any(|path| !path.is_empty()) || !whole.is_empty() {
146        for instance in &part.instances {
147            let name = instance.name.as_str();
148            if by_part.contains_key(name) || whole.contains(name) {
149                light(name, &instance.view, whole.contains(name));
150            }
151        }
152    }
153    lighting
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159    use geop_core_program::placed::Located;
160
161    fn element(path: &str, target: &str, kind: &str, name: &str) -> PickedElement {
162        PickedElement {
163            target: Located::in_instance(path, target),
164            kind: kind.into(),
165            name: name.into(),
166        }
167    }
168
169    fn targets() -> Targets {
170        Targets {
171            model: "model".into(),
172            datum_target: "datums".into(),
173            sketches: BTreeMap::from([("sketch:s".into(), "s".into())]),
174        }
175    }
176
177    #[test]
178    fn elements_light_what_they_are_drawn_as_in_their_own_part() {
179        let lit = Lit::of(
180            &targets(),
181            "",
182            &[
183                element("", "model", "face", "f"),
184                element("", "model", "plane", "p"),
185                element("", "model", "line", "l"),
186                element("", "model", "solid", "body"),
187                element("", "sketch:s", "sketch_curve", "c"),
188                element("", "sketch:s", "sketch", ""),
189                element("", "datums", "datum", "origin:x"),
190                element("", "model", "feature", "hole"),
191                element("bolt", "model", "face", "elsewhere"),
192            ],
193        );
194        assert_eq!(lit.faces, BTreeSet::from(["f".into(), "p".into()]));
195        assert_eq!(lit.edges, BTreeSet::from(["l".into()]));
196        assert!(lit.face("other", Some("body"), None));
197        assert!(lit.face("other", None, Some("hole")));
198        assert!(!lit.face("elsewhere", None, None));
199        assert!(lit.curves.contains(&("s".into(), "c".into())));
200        assert!(lit.sketches.contains("s"));
201        assert!(lit.datums.contains("origin:x"));
202        let placed = Lit::of(&targets(), "bolt", &[element("bolt", "model", "face", "x")]);
203        assert!(placed.faces.contains("x"));
204    }
205}