1use 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#[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 Face {
30 name: String,
31 },
32 Datum {
36 name: String,
37 #[serde(default, skip_serializing_if = "Option::is_none")]
38 component: Option<DatumComponent>,
39 },
40 Solid {
42 name: String,
43 },
44 Sketch {
46 name: String,
47 },
48 SketchCurve {
50 sketch: String,
51 curve: CurveId,
52 },
53 SketchPoint {
55 sketch: String,
56 point: PointId,
57 },
58}
59
60impl EntityRef {
61 pub fn datum(name: impl Into<String>) -> Self {
63 EntityRef::Datum {
64 name: name.into(),
65 component: None,
66 }
67 }
68
69 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 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 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
135pub 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 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 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}