geop_ops_parts/operation/
entity.rs1use geop_core_geometry::{
8 nurb_curve::NurbCurve3D,
9 shape::{Arc, Axis},
10};
11use geop_core_math::{
12 geop_error::{GeopError, GeopResult, WithContext},
13 primitives::CoordinateSystem,
14 scalars::Scalar,
15 vector::Vector3,
16 with_context,
17};
18use geop_core_part::{DatumKind, Part};
19use serde::{Deserialize, Serialize};
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
23pub enum WorldAxis {
24 X,
25 Y,
26 Z,
27}
28
29impl WorldAxis {
30 fn unit<S: Scalar>(self) -> Vector3<S> {
31 match self {
32 WorldAxis::X => v3(1., 0., 0.),
33 WorldAxis::Y => v3(0., 1., 0.),
34 WorldAxis::Z => v3(0., 0., 1.),
35 }
36 }
37}
38
39fn v3<S: Scalar>(x: f64, y: f64, z: f64) -> Vector3<S> {
40 Vector3::from_array([x, y, z].map(S::from_f64))
41}
42
43#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
45#[serde(tag = "type")]
46pub enum EntityRef {
47 Origin,
49 Axis {
51 axis: WorldAxis,
52 },
53 Plane {
57 normal: WorldAxis,
58 },
59 Vertex {
60 name: String,
61 },
62 Edge {
63 name: String,
64 },
65 Face {
70 name: String,
71 },
72 Datum {
73 name: String,
74 },
75}
76
77impl std::fmt::Display for EntityRef {
78 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79 match self {
80 EntityRef::Origin => write!(f, "the origin"),
81 EntityRef::Axis { axis } => write!(f, "the {axis:?} axis"),
82 EntityRef::Plane { normal } => write!(f, "the {normal:?} plane"),
83 EntityRef::Vertex { name } => write!(f, "vertex {name:?}"),
84 EntityRef::Edge { name } => write!(f, "edge {name:?}"),
85 EntityRef::Face { name } => write!(f, "face {name:?}"),
86 EntityRef::Datum { name } => write!(f, "datum {name:?}"),
87 }
88 }
89}
90
91#[derive(Clone, Debug)]
95pub struct Geometry<S: Scalar> {
96 pub point: Option<Vector3<S>>,
97 pub line: Option<Axis<S>>,
99 pub plane: Option<CoordinateSystem<S>>,
102 pub arc: Option<Arc<S>>,
103 pub round: Option<Axis<S>>,
105 pub curve: Option<NurbCurve3D<S>>,
107 pub frame: Option<CoordinateSystem<S>>,
110}
111
112impl<S: Scalar> Geometry<S> {
113 fn none() -> Self {
114 Self {
115 point: None,
116 line: None,
117 plane: None,
118 arc: None,
119 round: None,
120 curve: None,
121 frame: None,
122 }
123 }
124
125 pub fn roles(&self) -> Vec<Role> {
127 Role::ALL
128 .into_iter()
129 .filter(|role| role.fits(self))
130 .collect()
131 }
132}
133
134#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
136#[serde(rename_all = "snake_case")]
137pub enum Role {
138 Point,
140 Line,
142 Plane,
144 Edge,
146 Circle,
148 Round,
151}
152
153impl Role {
154 const ALL: [Role; 6] = [
155 Role::Point,
156 Role::Line,
157 Role::Plane,
158 Role::Edge,
159 Role::Circle,
160 Role::Round,
161 ];
162
163 pub fn fits<S: Scalar>(self, geometry: &Geometry<S>) -> bool {
164 match self {
165 Role::Point => geometry.point.is_some(),
166 Role::Line => geometry.line.is_some(),
167 Role::Plane => geometry.plane.is_some(),
168 Role::Edge => geometry.curve.is_some(),
169 Role::Circle => geometry.arc.is_some(),
170 Role::Round => geometry.round.is_some(),
171 }
172 }
173}
174
175pub fn frame_along<S: Scalar>(
179 origin: Vector3<S>,
180 normal: &Vector3<S>,
181) -> GeopResult<CoordinateSystem<S>> {
182 let n = normal.normalize()?;
183 let axis = (0..3)
184 .min_by(|&a, &b| n[a].to_f64().abs().total_cmp(&n[b].to_f64().abs()))
185 .expect("three axes");
186 let mut a = Vector3::zero();
187 a[axis] = S::ONE;
188 let u = a.sub(&n.prod_scalar(n.prod_dot(&a))).normalize()?;
189 let v = n.prod_cross(&u);
190 CoordinateSystem::try_new(origin, u, v, n)
191}
192
193pub fn world_frame<S: Scalar>(origin: Vector3<S>) -> GeopResult<CoordinateSystem<S>> {
195 CoordinateSystem::try_new(origin, v3(1., 0., 0.), v3(0., 1., 0.), v3(0., 0., 1.))
196}
197
198fn base_plane<S: Scalar>(normal: WorldAxis) -> GeopResult<CoordinateSystem<S>> {
200 let origin = Vector3::zero();
201 match normal {
202 WorldAxis::X => {
203 CoordinateSystem::try_new(origin, v3(0., 1., 0.), v3(0., 0., 1.), v3(1., 0., 0.))
204 }
205 WorldAxis::Y => {
206 CoordinateSystem::try_new(origin, v3(1., 0., 0.), v3(0., 0., -1.), v3(0., 1., 0.))
207 }
208 WorldAxis::Z => world_frame(origin),
209 }
210}
211
212impl EntityRef {
213 pub fn resolve<S: Scalar>(&self, part: &Part<S>) -> GeopResult<Geometry<S>> {
215 let ctx = with_context!("resolving {self}");
216 let mut g = Geometry::none();
217 match self {
218 EntityRef::Origin => {
219 g.point = Some(Vector3::zero());
220 g.frame = Some(world_frame(Vector3::zero())?);
221 }
222 EntityRef::Axis { axis } => {
223 let direction = axis.unit();
224 g.line = Some(Axis::try_new(Vector3::zero(), direction)?);
225 g.frame = Some(frame_along(Vector3::zero(), &direction)?);
226 }
227 EntityRef::Plane { normal } => {
228 let frame = base_plane(*normal)?;
229 g.plane = Some(frame.clone());
230 g.frame = Some(frame);
231 }
232 EntityRef::Vertex { name } => {
233 let id = part.vertex_id(name).with_context(ctx)?;
234 g.point = Some(part.topology().get_vertex(id).with_context(ctx)?.point);
235 }
236 EntityRef::Edge { name } => {
237 let id = part.edge_id(name).with_context(ctx)?;
238 let curve = part
239 .topology()
240 .get_edge(id)
241 .with_context(ctx)?
242 .curve
243 .clone();
244 g.line = curve.as_line().with_context(ctx)?;
245 g.arc = curve.as_arc().with_context(ctx)?;
246 g.round = g.arc.as_ref().map(|arc| arc.circle.axis());
247 g.curve = Some(curve);
248 }
249 EntityRef::Face { name } => {
250 let id = part.face_id(name).with_context(ctx)?;
251 let surface = &part.topology().get_face(id).with_context(ctx)?.surface;
252 if let Some(plane) = surface.as_plane().with_context(ctx)? {
253 let origin = plane.project(&Vector3::zero());
254 g.plane = Some(frame_along(origin, &plane.normal)?);
255 }
256 g.round = surface.axis_of_revolution().with_context(ctx)?;
257 }
258 EntityRef::Datum { name } => {
259 let id = part.datum_id(name).with_context(ctx)?;
260 let datum = part.datum(id).with_context(ctx)?;
261 let frame = datum.frame.clone();
262 match datum.kind {
263 DatumKind::Point => g.point = Some(*frame.origin()),
264 DatumKind::Axis => g.line = Some(Axis::try_new(*frame.origin(), *frame.w())?),
265 DatumKind::Plane => g.plane = Some(frame.clone()),
266 }
267 g.frame = Some(frame);
268 }
269 }
270 Ok(g)
271 }
272}
273
274pub fn resolve_plane<S: Scalar>(
277 part: &Part<S>,
278 plane: &EntityRef,
279) -> GeopResult<CoordinateSystem<S>> {
280 plane
281 .resolve(part)?
282 .plane
283 .ok_or_else(|| GeopError::new(format!("{plane} is not planar")))
284}