1use std::{collections::HashMap, sync::Arc};
20
21use geop_core_editor::selectable::SelectableElement;
22use geop_core_math::geop_error::{GeopError, GeopResult};
23use geop_core_program::{
24 element::{ElementReference, HasElements, Kind, PickedElement},
25 reader::Reader,
26};
27use geop_core_topology::build::BodySpec;
28use serde::{Deserialize, Serialize};
29
30use super::{
31 BodyNames, NamedModel,
32 elements::{Face, read_model},
33 names::{digest, origin_of},
34};
35
36#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
38#[serde(rename_all = "snake_case")]
39pub enum BooleanOp {
40 Union,
42 Intersection,
44 Difference,
46 SymmetricDifference,
48}
49
50#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
53pub struct Feature {
54 pub tools: Vec<FeatureTool>,
55}
56
57#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
61pub struct FeatureTool {
62 pub spec: BodySpec,
63 pub names: BodyNames,
64 pub op: BooleanOp,
65 pub reach: ToolReach,
66}
67
68#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
72pub struct ToolReach {
73 pub up_to_next: Option<(String, String)>,
76 pub scope: Option<String>,
80}
81
82impl NamedModel {
83 pub fn record_feature_tool(&mut self, step: &str, tool: FeatureTool) {
85 let features = &mut self.features;
86 match features.iter_mut().find(|(s, _)| s == step) {
87 Some((_, feature)) => Arc::make_mut(feature).tools.push(tool),
88 None => features.push((step.to_string(), Arc::new(Feature { tools: vec![tool] }))),
89 }
90 }
91
92 pub fn feature(&self, step: &str) -> GeopResult<&Feature> {
95 self.features
96 .iter()
97 .find(|(s, _)| s == step)
98 .map(|(_, f)| f.as_ref())
99 .ok_or_else(|| {
100 let steps: Vec<&str> = self.features.iter().map(|(s, _)| s.as_str()).collect();
101 GeopError::new(format!(
102 "step {step:?} is no feature: only a step that joins, cuts or intersects a tool with a solid is (the features: {steps:?})"
103 ))
104 })
105 }
106
107 pub fn features(&self) -> impl Iterator<Item = (&str, &Feature)> {
109 self.features.iter().map(|(s, f)| (s.as_str(), f.as_ref()))
110 }
111
112 pub fn feature_faces(&self) -> FeatureFaces<'_> {
114 let mut steps = HashMap::new();
115 for (step, feature) in &self.features {
116 for tool in &feature.tools {
117 for face in &tool.names.faces {
118 steps.insert(face.clone(), step.as_str());
119 steps.insert(digest(face), step.as_str());
120 }
121 }
122 }
123 FeatureFaces { steps }
124 }
125}
126
127pub struct FeatureFaces<'a> {
129 steps: HashMap<String, &'a str>,
131}
132
133impl FeatureFaces<'_> {
134 pub fn of(&self, face: &str) -> Option<&str> {
140 std::iter::successors(Some(face), |&name| origin_of(name))
142 .find_map(|name| self.steps.get(name).copied())
143 }
144}
145
146pub struct FeatureElement;
152
153pub type FeatureRef = ElementReference<NamedModel, FeatureElement>;
156
157pub fn feature(step: impl Into<String>) -> FeatureRef {
159 ElementReference::new(super::model(), step)
160}
161
162impl Kind for FeatureElement {
163 const NAME: &'static str = "feature";
164}
165
166impl HasElements<FeatureElement> for NamedModel {
167 type Element<'a> = &'a Feature;
168 const SINGLETON: Option<&'static str> = <Self as HasElements<Face>>::SINGLETON;
169
170 fn element(&self, name: &str) -> GeopResult<Self::Element<'_>> {
171 self.feature(name)
172 }
173}
174
175fn part_of<'a>(reader: &Reader<'a>, instance: &str) -> Option<Reader<'a>> {
178 match instance.is_empty() {
179 true => Some(reader.clone()),
180 false => reader.placed_part(instance).ok(),
181 }
182}
183
184impl SelectableElement<NamedModel> for FeatureElement {
185 const PICKED_FROM: &'static [&'static str] = &[Face::NAME];
186
187 fn select(reader: &Reader, hit: &PickedElement) -> Option<FeatureRef> {
188 let face = ElementReference::<NamedModel, Face>::from_picked(hit)?;
189 let instance = face.instance().to_string();
190 let part = read_model(&part_of(reader, &instance)?).ok()?;
191 part.face_id(face.name()).ok()?;
192 let step = part.feature_faces().of(face.name())?.to_string();
193 Some(match instance.is_empty() {
194 true => feature(step),
195 false => feature(step).behind(&instance),
196 })
197 }
198
199 fn drawn(reader: &Reader, feature: &FeatureRef) -> Vec<PickedElement> {
200 let instance = feature.instance();
201 let Some(part) = part_of(reader, instance).and_then(|r| read_model(&r).ok()) else {
202 return Vec::new();
203 };
204 let made = part.feature_faces();
205 let mut faces: Vec<_> = part.topology().faces.keys().copied().collect();
206 faces.sort_by_key(|face| face.0);
207 (faces.into_iter())
208 .filter_map(|face| part.name_of(face))
209 .filter(|&face| made.of(face) == Some(feature.name()))
210 .map(|face| {
211 let face = ElementReference::<NamedModel, Face>::new(super::model(), face);
212 match instance.is_empty() {
213 true => face.picked(),
214 false => face.behind(instance).picked(),
215 }
216 })
217 .collect()
218 }
219}