Skip to main content

geop_ops/operation/
aspects.rs

1//! [`Aspects`]: what an entity a step builds on can be used as — a point, a
2//! line, a plane, an arc, something round, a curve, a solid, a sketch, or
3//! several of these at once — and the [`Role`]s that lets it fill. Which of them an
4//! entity is decides what it can be picked for, and what can be built on
5//! it.
6
7use geop_core_geometry::{
8    nurb_curve::NurbCurve3D,
9    shape::{Arc, Axis},
10};
11use geop_core_math::{
12    geop_error::{GeopResult, WithContext},
13    primitives::{CoordinateSystem, DatumKind},
14    scalars::Scalar,
15    vector::Vector3,
16    with_context,
17};
18use geop_core_sketch::CurveKind;
19use serde::Serialize;
20
21use super::EntityRef;
22use crate::Part;
23
24/// Everything an entity can be used as. An entity is usually several at
25/// once: a straight edge is a line and a curve, a circular edge an arc, a
26/// curve and something round, a datum point a point and a frame, a datum
27/// frame the same.
28#[derive(Clone, Debug, Default)]
29pub struct Aspects<S: Scalar> {
30    pub point: Option<Vector3<S>>,
31    /// The line it runs along: a straight edge, an axis, a sketch line.
32    pub line: Option<Axis<S>>,
33    /// The plane it lies in, as a frame with `w` the normal and `u`/`v` a
34    /// sketch's `x`/`y` on it.
35    pub plane: Option<CoordinateSystem<S>>,
36    pub arc: Option<Arc<S>>,
37    /// The axis it turns around: a circular edge, a cylinder, a cone, a
38    /// sketch circle.
39    pub round: Option<Axis<S>>,
40    /// An edge's curve, whatever its shape.
41    pub curve: Option<NurbCurve3D<S>>,
42    /// Its own axes, if it has any: a datum's frame.
43    pub frame: Option<CoordinateSystem<S>>,
44    /// A solid, as a whole.
45    pub solid: bool,
46    /// A sketch, as a whole: its regions, to sweep.
47    pub sketch: bool,
48}
49
50impl<S: Scalar> Aspects<S> {
51    /// What `entity` is in `part`. Fails if the part has no such entity.
52    pub fn of(entity: &EntityRef, part: &Part<S>) -> GeopResult<Self> {
53        let ctx = with_context!("resolving {entity}");
54        let mut g = Aspects::default();
55        match entity {
56            EntityRef::Vertex { name } => {
57                let id = part.vertex_id(name).with_context(ctx)?;
58                g.point = Some(part.topology().get_vertex(id).with_context(ctx)?.point);
59            }
60            EntityRef::Edge { name } => {
61                let id = part.edge_id(name).with_context(ctx)?;
62                let curve = part
63                    .topology()
64                    .get_edge(id)
65                    .with_context(ctx)?
66                    .curve
67                    .clone();
68                g.line = curve.as_line().with_context(ctx)?;
69                g.arc = curve.as_arc().with_context(ctx)?;
70                g.round = g.arc.as_ref().map(|arc| arc.circle.axis());
71                g.curve = Some(curve);
72            }
73            EntityRef::Face { name } => {
74                let id = part.face_id(name).with_context(ctx)?;
75                let surface = &part.topology().get_face(id).with_context(ctx)?.surface;
76                g.plane = entity.resolve_plane(part).ok();
77                g.round = surface.axis_of_revolution().with_context(ctx)?;
78            }
79            EntityRef::Datum { .. } => {
80                let datum = entity.resolve_datum(part)?;
81                let frame = datum.frame;
82                match datum.kind {
83                    DatumKind::Point => g.point = Some(*frame.origin()),
84                    DatumKind::Axis => g.line = Some(Axis::try_new(*frame.origin(), *frame.w())?),
85                    DatumKind::Plane => g.plane = Some(frame.clone()),
86                    // A coordinate system is used by its origin, and by its
87                    // axes as the frame below.
88                    DatumKind::Frame => g.point = Some(*frame.origin()),
89                }
90                g.frame = Some(frame);
91            }
92            EntityRef::Solid { name } => {
93                part.solid_id(name).with_context(ctx)?;
94                g.solid = true;
95            }
96            EntityRef::Sketch { name } => {
97                part.sketch_id(name).with_context(ctx)?;
98                g.sketch = true;
99            }
100            EntityRef::SketchPoint { sketch, point } => {
101                let placed = part.sketch(part.sketch_id(sketch).with_context(ctx)?)?;
102                placed.sketch.point(*point).with_context(ctx)?;
103                let geometry = placed.sketch.enclose::<S>().with_context(ctx)?;
104                g.point = Some(placed.plane.uv_to_xyz(&geometry.points[point]));
105            }
106            EntityRef::SketchCurve { sketch, curve } => {
107                let placed = part.sketch(part.sketch_id(sketch).with_context(ctx)?)?;
108                let kind = &placed.sketch.curve(*curve).with_context(ctx)?.kind;
109                let geometry = placed.sketch.enclose::<S>().with_context(ctx)?;
110                let at = |p| placed.plane.uv_to_xyz(&geometry.points[&p]);
111                match *kind {
112                    CurveKind::Line { start, end } => {
113                        let (a, b) = (at(start), at(end));
114                        g.line = Some(Axis::try_new(a, b.sub(&a)).with_context(ctx)?);
115                    }
116                    CurveKind::Circle { center, .. } => {
117                        g.round = Some(Axis::try_new(at(center), *placed.plane.w())?);
118                    }
119                    // Arcs and splines fill no role yet: nothing picks them.
120                    CurveKind::Arc { .. } | CurveKind::Spline { .. } => {}
121                }
122            }
123        }
124        Ok(g)
125    }
126
127    /// Every role it can fill.
128    pub fn roles(&self) -> Vec<Role> {
129        Role::ALL
130            .into_iter()
131            .filter(|role| role.fits(self))
132            .collect()
133    }
134}
135
136/// What a step needs an entity it builds on to be: what a pick looks for,
137/// and what a datum construction needs as an input.
138#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
139#[serde(rename_all = "snake_case")]
140pub enum Role {
141    /// A vertex, a datum point, a frame's origin, a sketch point.
142    Point,
143    /// A straight edge, a datum axis, a frame's axis, a sketch line.
144    Line,
145    /// A planar face, a datum plane, a frame's plane.
146    Plane,
147    /// Any edge.
148    Edge,
149    /// A circular edge.
150    Circle,
151    /// Something that turns around an axis: a circular edge, a cylindrical,
152    /// conical or spherical face, a sketch circle.
153    Round,
154    /// A solid, as a whole.
155    Solid,
156    /// A sketch, as a whole.
157    Sketch,
158}
159
160impl Role {
161    pub const ALL: [Role; 8] = [
162        Role::Point,
163        Role::Line,
164        Role::Plane,
165        Role::Edge,
166        Role::Circle,
167        Role::Round,
168        Role::Solid,
169        Role::Sketch,
170    ];
171
172    pub fn fits<S: Scalar>(self, aspects: &Aspects<S>) -> bool {
173        match self {
174            Role::Point => aspects.point.is_some(),
175            Role::Line => aspects.line.is_some(),
176            Role::Plane => aspects.plane.is_some(),
177            Role::Edge => aspects.curve.is_some(),
178            Role::Circle => aspects.arc.is_some(),
179            Role::Round => aspects.round.is_some(),
180            Role::Solid => aspects.solid,
181            Role::Sketch => aspects.sketch,
182        }
183    }
184
185    /// As a requirement reads: `a point`.
186    pub fn describe(self) -> &'static str {
187        match self {
188            Role::Point => "a point",
189            Role::Line => "a line",
190            Role::Plane => "a plane",
191            Role::Edge => "an edge",
192            Role::Circle => "a circular edge",
193            Role::Round => "a circular edge or a round face",
194            Role::Solid => "a solid",
195            Role::Sketch => "a sketch",
196        }
197    }
198
199    /// Its name, as a selection lists what an entity can be used as.
200    pub fn name(self) -> &'static str {
201        match self {
202            Role::Point => "point",
203            Role::Line => "line",
204            Role::Plane => "plane",
205            Role::Edge => "edge",
206            Role::Circle => "circle",
207            Role::Round => "round",
208            Role::Solid => "solid",
209            Role::Sketch => "sketch",
210        }
211    }
212}
213
214/// `roles` in words, as a requirement reads: `a point or a plane`.
215pub fn describe_roles(roles: &[Role], joiner: &str) -> String {
216    roles
217        .iter()
218        .map(|r| r.describe())
219        .collect::<Vec<_>>()
220        .join(joiner)
221}