Skip to main content

geop_ops/operation/
entity.rs

1//! [`EntityRef`]: how a step refers to geometry it builds on — a vertex,
2//! edge, face, datum, solid or sketch of the part, by name.
3
4use crate::Part;
5use geop_core_math::{
6    geop_error::{GeopError, GeopResult, WithContext},
7    primitives::{CoordinateSystem, Datum, DatumComponent, DatumKind},
8    scalars::Scalar,
9    vector::Vector3,
10    with_context,
11};
12use geop_core_sketch::{CurveId, PointId};
13use serde::{Deserialize, Serialize};
14
15/// Something picked in the viewport, to build on or to use.
16#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
17#[serde(tag = "type")]
18pub enum EntityRef {
19    Vertex {
20        name: String,
21    },
22    Edge {
23        name: String,
24    },
25    /// A face: planar, its plane — normal pointing out of its solid, sketch
26    /// origin the world origin's projection onto it and sketch `x` the world
27    /// axis most parallel to it, projected, so sketches on parallel faces
28    /// line up. Round, its axis.
29    Face {
30        name: String,
31    },
32    /// A datum — or, of a frame, one of its axes or planes (see
33    /// [`geop_core_math::primitives::Datum::component`]). Every part has
34    /// the frame [`ORIGIN`](crate::ORIGIN).
35    Datum {
36        name: String,
37        #[serde(default, skip_serializing_if = "Option::is_none")]
38        component: Option<DatumComponent>,
39    },
40    /// A solid, as a whole.
41    Solid {
42        name: String,
43    },
44    /// A sketch: its plane.
45    Sketch {
46        name: String,
47    },
48    /// A curve of a sketch, by its id in the sketch.
49    SketchCurve {
50        sketch: String,
51        curve: CurveId,
52    },
53    /// A point of a sketch, by its id in the sketch.
54    SketchPoint {
55        sketch: String,
56        point: PointId,
57    },
58}
59
60impl EntityRef {
61    /// A datum, as a whole.
62    pub fn datum(name: impl Into<String>) -> Self {
63        EntityRef::Datum {
64            name: name.into(),
65            component: None,
66        }
67    }
68
69    /// A component of the frame datum `name`.
70    pub fn datum_component(name: impl Into<String>, component: DatumComponent) -> Self {
71        EntityRef::Datum {
72            name: name.into(),
73            component: Some(component),
74        }
75    }
76
77    /// How the entity is shown: its name, and which component of a frame.
78    pub fn label(&self) -> String {
79        match self {
80            EntityRef::Datum {
81                name,
82                component: Some(component),
83            } => format!("{name} {component}"),
84            EntityRef::SketchCurve { sketch, curve } => format!("{sketch} {curve}"),
85            EntityRef::SketchPoint { sketch, point } => format!("{sketch} {point}"),
86            EntityRef::Vertex { name }
87            | EntityRef::Edge { name }
88            | EntityRef::Face { name }
89            | EntityRef::Datum { name, .. }
90            | EntityRef::Solid { name }
91            | EntityRef::Sketch { name } => name.clone(),
92        }
93    }
94}
95
96impl EntityRef {
97    /// Whether it is `scope` or part of it: a curve or a point of a sketch.
98    pub fn lies_in(&self, scope: &EntityRef) -> bool {
99        match (self, scope) {
100            (
101                EntityRef::SketchCurve { sketch, .. } | EntityRef::SketchPoint { sketch, .. },
102                EntityRef::Sketch { name },
103            ) => sketch == name,
104            _ => self == scope,
105        }
106    }
107}
108
109impl std::fmt::Display for EntityRef {
110    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111        match self {
112            EntityRef::Vertex { name } => write!(f, "vertex {name:?}"),
113            EntityRef::Edge { name } => write!(f, "edge {name:?}"),
114            EntityRef::Face { name } => write!(f, "face {name:?}"),
115            EntityRef::Datum {
116                name,
117                component: None,
118            } => write!(f, "datum {name:?}"),
119            EntityRef::Datum {
120                name,
121                component: Some(component),
122            } => write!(f, "the {component} of datum {name:?}"),
123            EntityRef::Solid { name } => write!(f, "solid {name:?}"),
124            EntityRef::Sketch { name } => write!(f, "sketch {name:?}"),
125            EntityRef::SketchCurve { sketch, curve } => {
126                write!(f, "curve {curve} of sketch {sketch:?}")
127            }
128            EntityRef::SketchPoint { sketch, point } => {
129                write!(f, "point {point} of sketch {sketch:?}")
130            }
131        }
132    }
133}
134
135/// A right-handed orthonormal frame at `origin` with `w` along `normal`,
136/// and `u` the world axis most parallel to the plane normal to it,
137/// projected into that plane — so frames with parallel normals line up.
138pub fn frame_along<S: Scalar>(
139    origin: Vector3<S>,
140    normal: &Vector3<S>,
141) -> GeopResult<CoordinateSystem<S>> {
142    let n = normal.normalize()?;
143    let axis = (0..3)
144        .min_by(|&a, &b| n[a].to_f64().abs().total_cmp(&n[b].to_f64().abs()))
145        .expect("three axes");
146    let mut a = Vector3::zero();
147    a[axis] = S::ONE;
148    let u = a.sub(&n.prod_scalar(n.prod_dot(&a))).normalize()?;
149    let v = n.prod_cross(&u);
150    CoordinateSystem::try_new(origin, u, v, n)
151}
152
153impl EntityRef {
154    /// The datum it refers to in `part` — for a component of a frame, that
155    /// component as a datum of its own. Fails if it is no datum of the
156    /// part.
157    pub fn resolve_datum<S: Scalar>(&self, part: &Part<S>) -> GeopResult<Datum<S>> {
158        let ctx = with_context!("resolving {self}");
159        let EntityRef::Datum { name, component } = self else {
160            return Err(GeopError::new(format!("{self} is not a datum")));
161        };
162        let datum = part.datum(part.datum_id(name).with_context(ctx)?)?;
163        match component {
164            Some(component) => datum.component(*component).with_context(ctx),
165            None => Ok(datum.clone()),
166        }
167    }
168
169    /// The plane it lies in, as a frame with `u`/`v` a sketch's `x`/`y` on
170    /// it and `w = u x v` its normal: a planar face's — normal pointing out
171    /// of its solid, origin the world origin's projection onto it and `u`
172    /// the world axis most parallel to it, so sketches on parallel faces
173    /// line up — a datum plane's, a frame's plane's, or a sketch's. Fails
174    /// for anything else.
175    pub fn resolve_plane<S: Scalar>(&self, part: &Part<S>) -> GeopResult<CoordinateSystem<S>> {
176        let ctx = with_context!("resolving the plane of {self}");
177        match self {
178            EntityRef::Face { name } => {
179                let id = part.face_id(name).with_context(ctx)?;
180                let surface = &part.topology().get_face(id).with_context(ctx)?.surface;
181                match surface.as_plane().with_context(ctx)? {
182                    Some(plane) => frame_along(plane.project(&Vector3::zero()), &plane.normal),
183                    None => Err(GeopError::new(format!("{self} is not planar"))),
184                }
185            }
186            EntityRef::Datum { .. } => {
187                let datum = self.resolve_datum(part)?;
188                match datum.kind {
189                    DatumKind::Plane => Ok(datum.frame),
190                    _ => Err(GeopError::new(format!("{self} is not a plane"))),
191                }
192            }
193            EntityRef::Sketch { name } => {
194                let id = part.sketch_id(name).with_context(ctx)?;
195                Ok(part.sketch(id).with_context(ctx)?.plane.clone())
196            }
197            _ => Err(GeopError::new(format!("{self} is not planar"))),
198        }
199    }
200}