1use geop_core_editor::selectable::SelectableElement;
18use geop_core_geometry::{
19 nurb_curve::NurbCurve3D,
20 shape::{Arc, Axis},
21};
22use geop_core_math::{
23 geop_error::{GeopError, GeopResult},
24 primitives::CoordinateSystem,
25 vector::Vector3,
26};
27use geop_core_program::{
28 element::{ElementReference, HasElements, Kind, PickedElement},
29 reader::Reader,
30};
31use geop_core_topology::{Body as TopologyBody, EdgeId, FaceId, Sense};
32
33use crate::{
34 NamedModel, RefId,
35 curves::Chain,
36 elements::{Edge, Face, Vertex, drawn_entity},
37 model, read_model,
38};
39
40const MODEL: &str = "model";
42
43pub struct Point;
45
46pub struct Line;
48
49pub struct Plane;
52
53pub struct Round;
56
57pub struct Circle;
59
60pub struct Curve;
63
64pub struct Path;
67
68pub struct Body;
71
72impl Kind for Point {
73 const NAME: &'static str = "point";
74}
75
76impl Kind for Line {
77 const NAME: &'static str = "line";
78}
79
80impl Kind for Plane {
81 const NAME: &'static str = "plane";
82}
83
84impl Kind for Round {
85 const NAME: &'static str = "round";
86}
87
88impl Kind for Circle {
89 const NAME: &'static str = "circle";
90}
91
92impl Kind for Curve {
93 const NAME: &'static str = "curve";
94}
95
96impl Kind for Path {
97 const NAME: &'static str = "path";
98}
99
100impl Kind for Body {
101 const NAME: &'static str = "body";
102}
103
104pub fn frame_along(origin: Vector3, normal: &Vector3) -> GeopResult<CoordinateSystem> {
111 let n = normal.normalize()?;
112 let [u, v] = n.across()?;
113 CoordinateSystem::try_new(origin, u, v, n)
114}
115
116impl NamedModel {
117 fn edge_curve(&self, name: &str) -> GeopResult<(EdgeId, &NurbCurve3D)> {
119 let id = self.edge_id(name)?;
120 Ok((id, &self.topology().get_edge(id)?.curve))
121 }
122
123 pub fn frame_of_face(&self, face: FaceId) -> GeopResult<CoordinateSystem> {
128 let plane = self.plane_of_face(face, "only a flat face is a plane")?;
129 frame_along(plane.project(&Vector3::zero()), &plane.normal)
130 }
131
132 pub fn edge_chain(&self, edge: EdgeId) -> GeopResult<Chain<NurbCurve3D>> {
135 let name = self.name(edge)?.to_string();
136 let edge = self.topology().get_edge(edge)?;
137 Ok(Chain {
138 curves: vec![edge.curve.clone()],
139 curve_names: vec![name.clone()],
140 joint_names: vec![
141 self.name(edge.start_vertex)?.to_string(),
142 self.name(edge.end_vertex)?.to_string(),
143 ],
144 name,
145 })
146 }
147
148 pub fn body(&self, name: &str) -> GeopResult<TopologyBody> {
150 match self.id_of(name) {
151 Some(RefId::Solid(solid)) => Ok(TopologyBody::Solid(solid)),
152 Some(RefId::Face(face)) => self.topology().body_of_face(face),
153 _ => Err(GeopError::new(format!(
154 "{name:?} is no body: pick a solid or a face standing on its own"
155 ))),
156 }
157 }
158}
159
160impl HasElements<Point> for NamedModel {
161 type Element<'a> = Vector3;
162 const SINGLETON: Option<&'static str> = Some(MODEL);
163
164 fn element(&self, name: &str) -> GeopResult<Vector3> {
165 Ok(self.topology().get_vertex(self.vertex_id(name)?)?.point)
166 }
167}
168
169impl HasElements<Line> for NamedModel {
170 type Element<'a> = Axis;
171 const SINGLETON: Option<&'static str> = Some(MODEL);
172
173 fn element(&self, name: &str) -> GeopResult<Axis> {
174 let (_, curve) = self.edge_curve(name)?;
175 (curve.as_line()?).ok_or_else(|| GeopError::new(format!("edge {name:?} is not straight")))
176 }
177}
178
179impl HasElements<Plane> for NamedModel {
180 type Element<'a> = CoordinateSystem;
181 const SINGLETON: Option<&'static str> = Some(MODEL);
182
183 fn element(&self, name: &str) -> GeopResult<CoordinateSystem> {
184 self.frame_of_face(self.face_id(name)?)
185 }
186}
187
188impl HasElements<Round> for NamedModel {
189 type Element<'a> = Axis;
190 const SINGLETON: Option<&'static str> = Some(MODEL);
191
192 fn element(&self, name: &str) -> GeopResult<Axis> {
193 let axis = match self.id_of(name) {
194 Some(RefId::Edge(_)) => {
195 let arc = HasElements::<Circle>::element(self, name)?;
196 Some(arc.circle.axis())
197 }
198 Some(RefId::Face(face)) => {
199 (self.topology().get_face(face)?.surface).axis_of_revolution()?
200 }
201 _ => None,
202 };
203 axis.ok_or_else(|| {
204 GeopError::new(format!(
205 "{name:?} turns around no axis: pick a circular edge, or a cylindrical or conical face"
206 ))
207 })
208 }
209}
210
211impl HasElements<Circle> for NamedModel {
212 type Element<'a> = Arc;
213 const SINGLETON: Option<&'static str> = Some(MODEL);
214
215 fn element(&self, name: &str) -> GeopResult<Arc> {
216 let (_, curve) = self.edge_curve(name)?;
217 (curve.as_arc()?).ok_or_else(|| GeopError::new(format!("edge {name:?} is not circular")))
218 }
219}
220
221impl HasElements<Curve> for NamedModel {
222 type Element<'a> = Chain<NurbCurve3D>;
223 const SINGLETON: Option<&'static str> = Some(MODEL);
224
225 fn element(&self, name: &str) -> GeopResult<Chain<NurbCurve3D>> {
226 self.edge_chain(self.edge_id(name)?)
227 }
228}
229
230impl HasElements<Path> for NamedModel {
236 type Element<'a> = Chain<NurbCurve3D>;
237 const SINGLETON: Option<&'static str> = Some(MODEL);
238
239 fn element(&self, name: &str) -> GeopResult<Chain<NurbCurve3D>> {
240 let edge = self.edge_id(name)?;
241 let topology = self.topology();
242 let Some(wire) = topology.wire_of_edge(edge) else {
243 return self.edge_chain(edge);
244 };
245 let chains = topology.wire_chains(wire)?;
246 let run = (chains.into_iter())
247 .find(|chain| chain.iter().any(|&(e, _)| e == edge))
248 .ok_or_else(|| GeopError::new(format!("edge {name:?} is in no run of its wire")))?;
249 let mut chain = Chain {
250 name: name.to_string(),
251 curves: Vec::new(),
252 curve_names: Vec::new(),
253 joint_names: Vec::new(),
254 };
255 let mut end = None;
256 for &(e, sense) in &run {
257 let e_name = self.name(e)?.to_string();
258 let e = topology.get_edge(e)?;
259 let (start, stop, curve) = match sense {
260 Sense::Forward => (e.start_vertex, e.end_vertex, e.curve.clone()),
261 Sense::Reversed => (e.end_vertex, e.start_vertex, e.curve.reverse()),
262 };
263 chain.curves.push(curve);
264 chain.curve_names.push(e_name);
265 chain.joint_names.push(self.name(start)?.to_string());
266 end = Some(stop);
267 }
268 let first = topology.get_edge(run[0].0)?;
269 let start = match run[0].1 {
270 Sense::Forward => first.start_vertex,
271 Sense::Reversed => first.end_vertex,
272 };
273 match end {
276 Some(end) if end != start => chain.joint_names.push(self.name(end)?.to_string()),
277 _ => {}
278 }
279 Ok(chain)
280 }
281}
282
283impl HasElements<Body> for NamedModel {
284 type Element<'a> = TopologyBody;
285 const SINGLETON: Option<&'static str> = Some(MODEL);
286
287 fn element(&self, name: &str) -> GeopResult<TopologyBody> {
288 self.body(name)
289 }
290}
291
292macro_rules! picked_where_it_has_the_shape {
295 ($($kind:ty => [$($from:ty),+];)*) => {$(
296 impl SelectableElement<NamedModel> for $kind {
297 const PICKED_FROM: &'static [&'static str] = &[$(<$from as Kind>::NAME),+];
298
299 fn select(
300 reader: &Reader,
301 hit: &PickedElement,
302 ) -> Option<ElementReference<NamedModel, Self>> {
303 (hit.target == *model().location()
304 && <Self as SelectableElement<NamedModel>>::PICKED_FROM
305 .contains(&hit.kind.as_str()))
306 .then_some(())?;
307 let reference = ElementReference::new(model(), &hit.name);
308 reader.retrieve_element(&reference).ok()?;
309 Some(reference)
310 }
311
312 fn drawn(
313 reader: &Reader,
314 element: &ElementReference<NamedModel, Self>,
315 ) -> Vec<PickedElement> {
316 (read_model(reader).ok())
317 .map(|model| drawn_entity(&model, element.name()))
318 .unwrap_or_default()
319 }
320 }
321 )*};
322}
323
324picked_where_it_has_the_shape! {
325 Point => [Vertex];
326 Line => [Edge];
327 Plane => [Face];
328 Round => [Edge, Face];
329 Circle => [Edge];
330 Curve => [Edge];
331 Path => [Edge];
332}
333
334impl SelectableElement<NamedModel> for Body {
337 const PICKED_FROM: &'static [&'static str] = &[Face::NAME];
338
339 fn select(reader: &Reader, hit: &PickedElement) -> Option<ElementReference<NamedModel, Self>> {
340 let face = ElementReference::<NamedModel, Face>::from_picked(hit)?;
341 let face = reader.retrieve_element(&face).ok()?;
342 let name = match face.model.topology().body_of_face(face.id).ok()? {
343 TopologyBody::Solid(solid) => face.model.name_of(solid)?,
344 TopologyBody::Sheet(_) => &hit.name,
345 };
346 Some(ElementReference::new(model(), name))
347 }
348
349 fn drawn(reader: &Reader, body: &ElementReference<NamedModel, Self>) -> Vec<PickedElement> {
350 (read_model(reader).ok())
351 .map(|model| drawn_entity(&model, body.name()))
352 .unwrap_or_default()
353 }
354}
355
356pub fn resolve_bodies(
359 reader: &Reader,
360 bodies: &[ElementReference<NamedModel, Body>],
361) -> GeopResult<Vec<TopologyBody>> {
362 if bodies.is_empty() {
363 return Err(GeopError::new("pick the bodies first"));
364 }
365 let mut resolved = Vec::new();
366 for body in bodies {
367 let body = reader.retrieve_element(body)?;
368 if !resolved.contains(&body) {
369 resolved.push(body);
370 }
371 }
372 Ok(resolved)
373}
374
375geop_core_editor::one_of! {
376 pub enum PathRef -> Chain<NurbCurve3D> {
380 Sketch(ElementReference<crate::sketch::PlacedSketch, Path>),
381 Sketch3d(ElementReference<crate::sketch3d::PlacedSketch3d, Path>),
382 Edge(ElementReference<NamedModel, Path>),
383 }
384}
385
386impl PathRef {
387 pub fn is_sketch(&self, sketch: &crate::sketch::SketchRef) -> bool {
389 matches!(self, PathRef::Sketch(path) if path.target() == sketch.target())
390 }
391}
392
393#[cfg(test)]
394mod tests {
395 use geop_core_editor::selectable::{Selectable, selected};
396 use geop_core_math::scalars::Scalar;
397 use geop_core_program::part::Part;
398
399 use super::*;
400 use crate::testing::{given_part, unit_cube};
401
402 fn hit(kind: &str, name: &str) -> PickedElement {
403 PickedElement {
404 target: model().location().clone(),
405 kind: kind.to_string(),
406 name: name.to_string(),
407 }
408 }
409
410 #[test]
413 fn a_cube_has_points_lines_and_planes() {
414 let part: Part = given_part(&unit_cube()).unwrap();
415 let reader = part.current();
416 let point = ElementReference::<NamedModel, Point>::new(model(), "v0");
417 let at = reader.retrieve_element(&point).unwrap();
418 assert!(
419 at.to_array()
420 .iter()
421 .all(|c| c.to_f64() == 0.0 || c.to_f64() == 1.0)
422 );
423
424 let line = ElementReference::<NamedModel, Line>::new(model(), "e0");
425 let axis = reader.retrieve_element(&line).unwrap();
426 assert!(axis.direction.norm().definitely_greater(Scalar::ZERO));
427 let plane = ElementReference::<NamedModel, Plane>::new(model(), "f0");
428 let frame = reader.retrieve_element(&plane).unwrap();
429 assert!(frame.w().norm().could_be_equal(Scalar::ONE));
430 let curve = ElementReference::<NamedModel, Curve>::new(model(), "e0");
431 let chain = reader.retrieve_element(&curve).unwrap();
432 assert_eq!(chain.curve_names, ["e0"]);
433 assert_eq!(chain.joint_names.len(), 2);
434
435 let round = ElementReference::<NamedModel, Round>::new(model(), "f0");
436 assert!(reader.retrieve_element(&round).is_err());
437 let wrong = ElementReference::<NamedModel, Line>::new(model(), "f0");
438 assert!(reader.retrieve_element(&wrong).is_err());
439 }
440
441 #[test]
444 fn a_geometric_kind_is_picked_where_the_model_has_its_shape() {
445 let part: Part = given_part(&unit_cube()).unwrap();
446 let reader = part.current();
447 let lines: Vec<ElementReference<NamedModel, Line>> =
448 selected(&reader, &[hit("edge", "e3"), hit("face", "f0")]);
449 assert_eq!(lines.len(), 1);
450 assert_eq!(lines[0].name(), "e3");
451 assert_eq!(lines[0].drawn(&reader), [hit("edge", "e3")]);
452 let rounds: Vec<ElementReference<NamedModel, Round>> =
453 selected(&reader, &[hit("edge", "e3"), hit("face", "f0")]);
454 assert!(rounds.is_empty());
455 assert_eq!(
456 <ElementReference<NamedModel, Round>>::picked_from(),
457 ["edge", "face"]
458 );
459 }
460
461 #[test]
464 fn a_body_is_picked_by_a_face_of_it() {
465 let part: Part = given_part(&unit_cube()).unwrap();
466 let reader = part.current();
467 let bodies: Vec<ElementReference<NamedModel, Body>> =
468 selected(&reader, &[hit("face", "f2")]);
469 assert_eq!(bodies.len(), 1);
470 assert_eq!(bodies[0].name(), "s0");
471 assert_eq!(bodies[0].drawn(&reader).len(), 6);
472 let twice = [bodies[0].clone(), ElementReference::new(model(), "f1")];
473 assert_eq!(resolve_bodies(&reader, &twice).unwrap().len(), 1);
474 assert!(resolve_bodies(&reader, &[]).is_err());
475 }
476}