geop_ops_inspect/
scope.rs1use geop_core_math::geop_error::{GeopError, GeopResult};
5use geop_core_program::placed::INSTANCE_SEPARATOR;
6use geop_ops_model::placed::solids::PlacedSolid;
7use serde::{Deserialize, Serialize};
8
9#[derive(Clone, Debug, Default, PartialEq, Deserialize)]
13#[serde(default)]
14pub struct Scope {
15 pub parts: Vec<String>,
16 pub only_among: bool,
19}
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
24pub struct Stopped {
25 pub checked: usize,
26 pub of: usize,
27}
28
29impl Scope {
30 fn names(&self, instance: &str) -> bool {
33 self.parts.iter().any(|part| {
34 (instance.strip_prefix(part.as_str()))
35 .is_some_and(|rest| rest.is_empty() || rest.starts_with(INSTANCE_SEPARATOR))
36 })
37 }
38
39 pub fn holds(&self, instance: &str) -> bool {
41 self.parts.is_empty() || self.names(instance)
42 }
43
44 pub fn holds_pair(&self, a: &str, b: &str) -> bool {
47 match self.only_among {
48 true => self.holds(a) && self.holds(b),
49 false => self.holds(a) || self.holds(b),
50 }
51 }
52
53 pub fn check(&self, solids: &[PlacedSolid]) -> GeopResult<()> {
56 for part in &self.parts {
57 let one = Scope {
58 parts: vec![part.clone()],
59 only_among: false,
60 };
61 if !solids.iter().any(|solid| one.names(&solid.instance)) {
62 let mut there: Vec<&str> = (solids.iter())
63 .map(|solid| solid.instance.as_str())
64 .filter(|instance| !instance.is_empty())
65 .collect();
66 there.sort_unstable();
67 there.dedup();
68 return Err(GeopError::new(format!(
69 "no solid is of a part placed as {part:?}; the parts with solids are: {}",
70 there.join(", ")
71 )));
72 }
73 }
74 Ok(())
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81
82 fn of(parts: &[&str], only_among: bool) -> Scope {
83 Scope {
84 parts: parts.iter().map(|p| p.to_string()).collect(),
85 only_among,
86 }
87 }
88
89 #[test]
90 fn a_part_holds_what_is_placed_in_it_and_no_part_named_like_it() {
91 let scope = of(&["leg"], false);
92 assert!(scope.holds("leg") && scope.holds("leg/shin"));
93 assert!(!scope.holds("leg2") && !scope.holds("arm") && !scope.holds(""));
94 assert!(Scope::default().holds("") && Scope::default().holds("arm"));
95 }
96
97 #[test]
98 fn a_pair_is_held_by_either_or_only_by_both() {
99 assert!(of(&["leg"], false).holds_pair("leg/shin", "pelvis"));
100 assert!(!of(&["leg"], false).holds_pair("arm", "pelvis"));
101 assert!(!of(&["leg"], true).holds_pair("leg/shin", "pelvis"));
102 assert!(of(&["leg"], true).holds_pair("leg/shin", "leg/foot"));
103 }
104}