geop_ops_model/spacing/
holes.rs1use std::collections::BTreeMap;
10
11use geop_core_geometry::shape::Axis;
12use geop_core_math::{
13 geop_error::{GeopError, GeopResult, WithContext},
14 primitives::{CoordinateSystem, Motion, Pose, Quaternion},
15 scalars::Scalar,
16 with_context,
17};
18use geop_core_program::{
19 element::{ElementReference, HasElements, Kind, whole},
20 reader::Reader,
21 target::{Target, TargetReference},
22};
23use serde::{Deserialize, Serialize};
24
25use crate::{FeatureRef, frame_along, operation_of, parameters::located};
26
27#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
29#[serde(rename_all = "snake_case")]
30pub enum HoleFit {
31 #[default]
33 Clearance,
34 Counterbore,
36 Countersink,
38 Tapped,
40}
41
42#[derive(Clone, Debug, PartialEq)]
45pub struct HoleSite {
46 pub axis: Axis,
49 pub size: Option<String>,
52 pub fit: HoleFit,
53 pub seat: f64,
56 pub depth: f64,
59 pub through: bool,
61}
62
63impl HoleSite {
64 pub fn seat_frame(&self) -> GeopResult<CoordinateSystem> {
69 let at = self.along(self.seat);
70 frame_along(at, &self.axis.direction.neg())
71 }
72
73 pub fn exit_frame(&self) -> GeopResult<Option<CoordinateSystem>> {
77 if !self.through {
78 return Ok(None);
79 }
80 Ok(Some(frame_along(
81 self.along(self.depth),
82 &self.axis.direction,
83 )?))
84 }
85
86 pub fn grip(&self) -> f64 {
89 self.depth - self.seat
90 }
91
92 fn along(&self, distance: f64) -> geop_core_math::vector::Vector3 {
94 (self.axis.point).add(&self.axis.direction.prod_scalar(Scalar::from_f64(distance)))
95 }
96
97 pub fn moved(&self, motion: &Motion) -> Self {
99 Self {
100 axis: Axis {
101 point: motion.apply(&self.axis.point),
102 direction: motion.rotate(&self.axis.direction),
103 },
104 ..self.clone()
105 }
106 }
107}
108
109pub fn pose_of(frame: &CoordinateSystem) -> GeopResult<Pose> {
112 let rotation = Quaternion::from_rotation_columns([*frame.u(), *frame.v(), *frame.w()])?;
113 Pose::new(*frame.origin(), rotation)
114}
115
116#[derive(Clone, Debug, Default, PartialEq)]
118pub struct HoleSites(BTreeMap<String, HoleSite>);
119
120pub fn hole_sites() -> TargetReference<HoleSites> {
122 TargetReference::new("holes")
123}
124
125static NO_HOLES: HoleSites = HoleSites(BTreeMap::new());
127
128impl HoleSites {
129 pub fn add(&mut self, name: impl Into<String>, site: HoleSite) -> GeopResult<()> {
132 let name = name.into();
133 if self.0.contains_key(&name) {
134 return Err(GeopError::new(format!(
135 "the part has a hole named {name:?} already"
136 )));
137 }
138 self.0.insert(name, site);
139 Ok(())
140 }
141
142 pub fn iter(&self) -> impl Iterator<Item = (&str, &HoleSite)> {
144 self.0.iter().map(|(name, site)| (name.as_str(), site))
145 }
146
147 pub fn of_step(&self, step: &str) -> Vec<(&str, &HoleSite)> {
150 (self.iter())
151 .filter(|(name, _)| operation_of(name) == Some(step))
152 .collect()
153 }
154}
155
156impl Target for HoleSites {
158 const CHAIN: bool = true;
159}
160
161impl Kind for HoleSites {
163 const NAME: &'static str = "holes";
164}
165
166impl HasElements<HoleSites> for HoleSites {
167 type Element<'a> = &'a HoleSites;
168 const SINGLETON: Option<&'static str> = Some("holes");
169
170 fn element(&self, name: &str) -> GeopResult<&HoleSites> {
171 whole(self, name)
172 }
173
174 fn undefined() -> Option<&'static Self> {
175 Some(&NO_HOLES)
176 }
177}
178
179pub fn read_hole_sites<'a>(reader: &Reader<'a>) -> GeopResult<&'a HoleSites> {
182 reader.retrieve_element(&ElementReference::whole(hole_sites()))
183}
184
185pub fn holes_of(reader: &Reader, features: &[FeatureRef]) -> GeopResult<Vec<(String, HoleSite)>> {
190 let mut found = Vec::new();
191 for feature in features {
192 let ctx = with_context!("the holes of {feature:?}");
193 let instance = feature.instance();
194 let all = ElementReference::<HoleSites, HoleSites>::whole(hole_sites());
195 let all = match instance.is_empty() {
196 true => all,
197 false => all.behind(instance),
198 };
199 let sites = reader.retrieve_placed(&all).with_context(ctx)?;
200 let motion = located(reader, instance).with_context(ctx)?.motion();
201 let of = sites.element.of_step(feature.name());
202 if of.is_empty() {
203 return Err(GeopError::new(format!(
204 "the feature {:?} drilled no holes: pick the wall of a hole a hole step drilled, or a pattern of one",
205 feature.name()
206 )))
207 .with_context(ctx);
208 }
209 for (name, site) in of {
210 let name = match instance.is_empty() {
211 true => name.to_string(),
212 false => geop_core_program::placed::Located::in_instance(instance, name).key(),
213 };
214 found.push((name, site.moved(&motion)));
215 }
216 }
217 Ok(found)
218}