1use std::collections::{BTreeMap, BTreeSet};
10
11use geop_core_program::element::PickedElement;
12use serde::Serialize;
13
14use super::{PartView, drawn};
15
16#[derive(Clone, Debug, Default, PartialEq)]
19pub struct Targets {
20 pub model: String,
22 pub datum_target: String,
24 pub sketches: BTreeMap<String, String>,
26}
27
28#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize)]
30pub struct Lit {
31 pub whole: bool,
33 pub faces: BTreeSet<String>,
34 pub edges: BTreeSet<String>,
35 pub vertices: BTreeSet<String>,
36 pub solids: BTreeSet<String>,
37 pub features: BTreeSet<String>,
39 pub sketches: BTreeSet<String>,
41 pub curves: BTreeSet<(String, String)>,
43 pub sketch_points: BTreeSet<(String, String)>,
45 pub datums: BTreeSet<String>,
47}
48
49impl Targets {
50 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 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 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
118pub type Lighting = BTreeMap<String, Lit>;
121
122pub 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}