Skip to main content

geop_ops_model/
selection.rs

1//! One element of a part, picked in the viewport, whatever it is
2//! ([`Selected`]) — a vertex, an edge, a face, a datum, a sketch's point or
3//! curve — and what it can be used as ([`Shape`], [`Role`]): a point, a
4//! line, a plane, a curve, a circle, something round. What a datum is built
5//! from, what a mate holds by, what is measured: a crate that lets the user
6//! pick any element of a part reads the pick through `Selected` rather than
7//! naming each kind of element itself.
8
9use crate::{
10    Circle, Curve, Edge, Face, Line, NamedModel, Point, Round, Vertex,
11    datums::{AnyDatum, Datums},
12    parameters::located,
13    sketch::PlacedSketch,
14};
15use geop_core_editor::one_of;
16use geop_core_geometry::{
17    nurb_curve::NurbCurve3D,
18    shape::{Arc, Axis},
19};
20use geop_core_math::{
21    geop_error::{GeopError, GeopResult},
22    primitives::{CoordinateSystem, DatumKind, Motion},
23    vector::Vector3,
24};
25use geop_core_program::{element::ElementReference, placed::InInstance, reader::Reader};
26
27one_of! {
28    /// An element a datum is built from.
29    pub enum Selected {
30        Vertex(ElementReference<NamedModel, Vertex>),
31        Edge(ElementReference<NamedModel, Edge>),
32        Face(ElementReference<NamedModel, Face>),
33        Datum(ElementReference<Datums, AnyDatum>),
34        SketchPoint(ElementReference<PlacedSketch, Point>),
35        SketchCurve(ElementReference<PlacedSketch, Curve>),
36    }
37}
38
39/// What a construction takes one element as.
40#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41pub enum Role {
42    Point,
43    Line,
44    Plane,
45    /// Any curve: an edge, a sketch's curve.
46    Curve,
47    /// A circle or an arc of one.
48    Circle,
49    /// Something turning around an axis: a circular edge, a cylinder, a
50    /// cone, a sketch's circle.
51    Round,
52}
53
54impl Role {
55    pub const ALL: [Role; 6] = [
56        Role::Point,
57        Role::Line,
58        Role::Plane,
59        Role::Curve,
60        Role::Circle,
61        Role::Round,
62    ];
63
64    /// Its name: `point`.
65    pub fn name(self) -> &'static str {
66        match self {
67            Role::Point => "point",
68            Role::Line => "line",
69            Role::Plane => "plane",
70            Role::Curve => "curve",
71            Role::Circle => "circle",
72            Role::Round => "round",
73        }
74    }
75
76    /// It in words, with its article: `a point`.
77    pub fn describe(self) -> &'static str {
78        match self {
79            Role::Point => "a point",
80            Role::Line => "a line",
81            Role::Plane => "a plane",
82            Role::Curve => "an edge or a curve",
83            Role::Circle => "a circle or an arc",
84            Role::Round => "something round",
85        }
86    }
87}
88
89/// Everything an element can be used as. An element is usually several at
90/// once: a straight edge is a line and a curve, a circular edge a circle, a
91/// curve and something round, a datum point a point with a frame.
92#[derive(Clone, Debug, Default)]
93pub struct Shape {
94    pub point: Option<Vector3>,
95    /// The line it runs along: a straight edge, an axis, a sketch line.
96    pub line: Option<Axis>,
97    /// The plane it lies in, as a frame with `w` the normal.
98    pub plane: Option<CoordinateSystem>,
99    pub arc: Option<Arc>,
100    /// The axis it turns around.
101    pub round: Option<Axis>,
102    /// The curve it runs along, whatever its shape.
103    pub curve: Option<NurbCurve3D>,
104    /// Its own axes, if it has any: a datum's frame.
105    pub frame: Option<CoordinateSystem>,
106}
107
108impl Shape {
109    /// Whether it can be used as `role`.
110    pub fn fits(&self, role: Role) -> bool {
111        match role {
112            Role::Point => self.point.is_some(),
113            Role::Line => self.line.is_some(),
114            Role::Plane => self.plane.is_some(),
115            Role::Curve => self.curve.is_some(),
116            Role::Circle => self.arc.is_some(),
117            Role::Round => self.round.is_some(),
118        }
119    }
120
121    /// Every role it can fill.
122    pub fn roles(&self) -> Vec<Role> {
123        Role::ALL
124            .into_iter()
125            .filter(|&role| self.fits(role))
126            .collect()
127    }
128
129    /// It moved by `motion`.
130    pub fn transform(&self, motion: &Motion) -> GeopResult<Self> {
131        let frame = |frame: &Option<CoordinateSystem>| {
132            frame.as_ref().map(|f| motion.apply_frame(f)).transpose()
133        };
134        Ok(Shape {
135            point: self.point.map(|p| motion.apply(&p)),
136            line: self.line.as_ref().map(|l| l.transform(motion)),
137            plane: frame(&self.plane)?,
138            arc: self.arc.as_ref().map(|a| a.transform(motion)),
139            round: self.round.as_ref().map(|r| r.transform(motion)),
140            curve: self.curve.as_ref().map(|c| c.transform(motion)),
141            frame: frame(&self.frame)?,
142        })
143    }
144
145    /// What a curve is: a line, a circle, something round, as it is one.
146    fn of_curve(curve: NurbCurve3D) -> GeopResult<Self> {
147        let arc = curve.as_arc()?;
148        Ok(Shape {
149            line: curve.as_line()?,
150            round: arc.as_ref().map(|arc| arc.circle.axis()),
151            arc,
152            curve: Some(curve),
153            ..Shape::default()
154        })
155    }
156}
157
158impl Selected {
159    /// What it is in the part `reader` reads: where the part, or the
160    /// instance placed in it that it is of, has it.
161    pub fn shape(&self, reader: &Reader) -> GeopResult<Shape> {
162        let InInstance { element, instance } = self.shape_in_instance(reader)?;
163        match instance.is_empty() {
164            true => Ok(element),
165            false => element.transform(&located(reader, &instance)?.motion()),
166        }
167    }
168
169    /// What it is in its own part — the part `reader` reads, or the
170    /// instance placed in it that it is of — and that instance's path:
171    /// where that is, [`crate::parameters::placement`] says.
172    pub fn shape_in_instance(&self, reader: &Reader) -> GeopResult<InInstance<Shape>> {
173        let instance = self.instance();
174        let element = match instance.is_empty() {
175            true => self.own_shape(reader)?,
176            false => self.local().own_shape(&reader.placed_part(instance)?)?,
177        };
178        Ok(InInstance {
179            element,
180            instance: instance.to_string(),
181        })
182    }
183
184    /// What it is, of the part `reader` reads itself.
185    fn own_shape(&self, reader: &Reader) -> GeopResult<Shape> {
186        Ok(match self {
187            Selected::Vertex(vertex) => {
188                let found = reader.retrieve_element(vertex)?;
189                Shape {
190                    point: Some(found.model.topology().get_vertex(found.id)?.point),
191                    ..Shape::default()
192                }
193            }
194            Selected::Edge(edge) => {
195                let found = reader.retrieve_element(edge)?;
196                Shape::of_curve(found.model.topology().get_edge(found.id)?.curve.clone())?
197            }
198            Selected::Face(face) => {
199                let found = reader.retrieve_element(face)?;
200                let surface = &found.model.topology().get_face(found.id)?.surface;
201                Shape {
202                    plane: found.model.frame_of_face(found.id).ok(),
203                    round: surface.axis_of_revolution()?,
204                    ..Shape::default()
205                }
206            }
207            Selected::Datum(datum) => {
208                let datum = reader.retrieve_element(datum)?;
209                let frame = datum.frame;
210                let mut shape = Shape::default();
211                match datum.kind {
212                    DatumKind::Point | DatumKind::Frame => shape.point = Some(*frame.origin()),
213                    DatumKind::Axis => {
214                        shape.line = Some(Axis::try_new(*frame.origin(), *frame.w())?)
215                    }
216                    DatumKind::Plane => shape.plane = Some(frame.clone()),
217                }
218                shape.frame = Some(frame);
219                shape
220            }
221            Selected::SketchPoint(point) => Shape {
222                point: Some(reader.retrieve_element(point)?),
223                ..Shape::default()
224            },
225            Selected::SketchCurve(curve) => {
226                let chain = reader.retrieve_element(curve)?;
227                let [one] = <[NurbCurve3D; 1]>::try_from(chain.curves).map_err(|_| {
228                    GeopError::new(format!("the curve {:?} is more than one", curve.name()))
229                })?;
230                // What the sketch says the curve is, where its curve alone
231                // does not: a circle is round about its center.
232                let mut shape = Shape::of_curve(one)?;
233                shape.line = shape
234                    .line
235                    .or_else(|| reader.retrieve_element(&as_kind::<Line>(curve)).ok());
236                shape.arc = shape
237                    .arc
238                    .or_else(|| reader.retrieve_element(&as_kind::<Circle>(curve)).ok());
239                shape.round = (shape.round)
240                    .or_else(|| reader.retrieve_element(&as_kind::<Round>(curve)).ok());
241                shape
242            }
243        })
244    }
245}
246
247/// The sketch's curve `curve` as what `K` it is.
248fn as_kind<K>(curve: &ElementReference<PlacedSketch, Curve>) -> ElementReference<PlacedSketch, K> {
249    ElementReference::new(curve.target().clone(), curve.name())
250}