1use geop_core_program::{
16 element::{ElementReference, HasElements, Kind, PickedElement, whole},
17 reader::Reader,
18 target::{Target, TargetReference},
19};
20use std::{borrow::Cow, collections::BTreeMap, sync::LazyLock};
21
22use crate::{Line, Plane, Point};
23use geop_core_editor::view::{PartView, ViewDatum, drawn};
24use geop_core_editor::{
25 registry::{Drawer, StructureItem},
26 selectable::SelectableElement,
27};
28use geop_core_geometry::shape::Axis;
29use geop_core_math::{
30 geop_error::{GeopError, GeopResult},
31 primitives::{CoordinateSystem, Datum, DatumComponent, DatumKind, FrameAxis},
32 vector::Vector3,
33};
34
35pub const ORIGIN: &str = "origin";
38
39const DATUMS: &str = "datums";
41
42const COMPONENT: char = ':';
45
46#[derive(Clone, Debug, Default, PartialEq)]
48pub struct Datums(BTreeMap<String, Datum>);
49
50pub fn datums() -> TargetReference<Datums> {
52 TargetReference::new(DATUMS)
53}
54
55pub fn datum<K>(name: impl Into<String>) -> ElementReference<Datums, K> {
58 ElementReference::new(datums(), name)
59}
60
61fn origin() -> &'static Datums {
64 static ORIGIN_ONLY: LazyLock<Datums> = LazyLock::new(Datums::default);
65 &ORIGIN_ONLY
66}
67
68pub fn read_datums<'a>(reader: &Reader<'a>) -> GeopResult<Cow<'a, Datums>> {
71 if !reader.defines(&datums()) {
72 return Ok(Cow::Borrowed(origin()));
73 }
74 Ok(Cow::Borrowed(reader.retrieve_element(
75 &ElementReference::<Datums, Datums>::whole(datums()),
76 )?))
77}
78
79pub fn component_name(frame: &str, component: DatumComponent) -> String {
82 let component = match component {
83 DatumComponent::Axis(axis) => axis.letter().to_string(),
84 DatumComponent::Plane(normal) => {
85 let [a, b] = in_plane(normal);
86 format!("{}{}", a.letter(), b.letter())
87 }
88 };
89 format!("{frame}{COMPONENT}{component}")
90}
91
92fn in_plane(normal: FrameAxis) -> [FrameAxis; 2] {
94 match normal {
95 FrameAxis::X => [FrameAxis::Y, FrameAxis::Z],
96 FrameAxis::Y => [FrameAxis::Z, FrameAxis::X],
97 FrameAxis::Z => [FrameAxis::X, FrameAxis::Y],
98 }
99}
100
101fn component(text: &str) -> GeopResult<DatumComponent> {
103 for axis in FrameAxis::ALL {
104 if text == axis.letter() {
105 return Ok(DatumComponent::Axis(axis));
106 }
107 let [a, b] = in_plane(axis);
108 if text == format!("{}{}", a.letter(), b.letter()) {
109 return Ok(DatumComponent::Plane(axis));
110 }
111 }
112 Err(GeopError::new(format!(
113 "{text:?} is no component of a frame: name an axis, `x`, or a plane, `xy`"
114 )))
115}
116
117pub fn origin_plane(normal: FrameAxis) -> ElementReference<Datums, Plane> {
119 ElementReference::new(
120 datums(),
121 component_name(ORIGIN, DatumComponent::Plane(normal)),
122 )
123}
124
125pub fn origin_axis(axis: FrameAxis) -> ElementReference<Datums, Line> {
127 ElementReference::new(datums(), component_name(ORIGIN, DatumComponent::Axis(axis)))
128}
129
130impl Datums {
131 pub fn get(&self, name: &str) -> GeopResult<Datum> {
134 let (frame, component) = match name.split_once(COMPONENT) {
135 Some((frame, text)) => (frame, Some(self::component(text)?)),
136 None => (name, None),
137 };
138 let datum = match frame {
139 ORIGIN => Datum {
140 kind: DatumKind::Frame,
141 frame: CoordinateSystem::world_at(Vector3::zero()),
142 },
143 _ => (self.0.get(frame).cloned()).ok_or_else(|| {
144 GeopError::new(format!(
145 "there is no datum {frame:?} (datums: {:?})",
146 self.names()
147 ))
148 })?,
149 };
150 match component {
151 Some(component) => datum.component(component),
152 None => Ok(datum),
153 }
154 }
155
156 pub fn insert(&mut self, name: &str, datum: Datum) -> GeopResult<()> {
158 if name == ORIGIN || name.contains(COMPONENT) || self.0.contains_key(name) {
159 return Err(GeopError::new(format!("there is a datum {name:?} already")));
160 }
161 self.0.insert(name.to_string(), datum);
162 Ok(())
163 }
164
165 pub fn names(&self) -> Vec<&str> {
167 self.0.keys().map(String::as_str).collect()
168 }
169
170 pub fn iter(&self) -> impl Iterator<Item = (&str, &Datum)> {
172 self.0.iter().map(|(name, datum)| (name.as_str(), datum))
173 }
174}
175
176impl Datums {
177 pub fn draw(&self, view: &mut PartView) -> GeopResult<()> {
180 view.datum_target = DATUMS.to_string();
181 let origin = self.get(ORIGIN)?;
182 let datums = std::iter::once((ORIGIN, &origin)).chain(self.iter());
183 view.datums = datums
184 .map(|(name, datum)| {
185 let components = match datum.kind {
186 DatumKind::Frame => (FrameAxis::ALL.into_iter())
187 .flat_map(|axis| [DatumComponent::Axis(axis), DatumComponent::Plane(axis)])
188 .map(|c| (c, component_name(name, c)))
189 .collect(),
190 _ => Vec::new(),
191 };
192 ViewDatum {
193 name: name.to_string(),
194 kind: datum.kind,
195 frame: datum.frame.clone(),
196 components,
197 }
198 })
199 .collect();
200 Ok(())
201 }
202}
203
204pub struct AnyDatum;
206
207impl Kind for AnyDatum {
208 const NAME: &'static str = drawn::DATUM;
209}
210
211impl Target for Datums {
213 const CHAIN: bool = true;
214}
215
216impl Kind for Datums {
218 const NAME: &'static str = "datums";
219}
220
221impl HasElements<Datums> for Datums {
222 type Element<'a> = &'a Datums;
223 const SINGLETON: Option<&'static str> = Some(DATUMS);
224
225 fn undefined() -> Option<&'static Self> {
226 Some(origin())
227 }
228
229 fn element(&self, name: &str) -> GeopResult<&Datums> {
230 whole(self, name)
231 }
232}
233
234impl HasElements<AnyDatum> for Datums {
235 type Element<'a> = Datum;
236 const SINGLETON: Option<&'static str> = Some(DATUMS);
237
238 fn undefined() -> Option<&'static Self> {
239 Some(origin())
240 }
241
242 fn element(&self, name: &str) -> GeopResult<Datum> {
243 self.get(name)
244 }
245}
246
247impl HasElements<Point> for Datums {
249 type Element<'a> = Vector3;
250 const SINGLETON: Option<&'static str> = Some(DATUMS);
251
252 fn undefined() -> Option<&'static Self> {
253 Some(origin())
254 }
255
256 fn element(&self, name: &str) -> GeopResult<Vector3> {
257 let datum = self.get(name)?;
258 match datum.kind {
259 DatumKind::Point | DatumKind::Frame => Ok(*datum.frame.origin()),
260 kind => Err(GeopError::new(format!(
261 "the datum {name:?} is an {kind:?}, not a point"
262 ))),
263 }
264 }
265}
266
267impl HasElements<Line> for Datums {
269 type Element<'a> = Axis;
270 const SINGLETON: Option<&'static str> = Some(DATUMS);
271
272 fn undefined() -> Option<&'static Self> {
273 Some(origin())
274 }
275
276 fn element(&self, name: &str) -> GeopResult<Axis> {
277 let datum = self.get(name)?;
278 match datum.kind {
279 DatumKind::Axis => Axis::try_new(*datum.frame.origin(), *datum.frame.w()),
280 kind => Err(GeopError::new(format!(
281 "the datum {name:?} is a {kind:?}, not an axis"
282 ))),
283 }
284 }
285}
286
287impl HasElements<Plane> for Datums {
289 type Element<'a> = CoordinateSystem;
290 const SINGLETON: Option<&'static str> = Some(DATUMS);
291
292 fn undefined() -> Option<&'static Self> {
293 Some(origin())
294 }
295
296 fn element(&self, name: &str) -> GeopResult<CoordinateSystem> {
297 let datum = self.get(name)?;
298 match datum.kind {
299 DatumKind::Plane => Ok(datum.frame),
300 kind => Err(GeopError::new(format!(
301 "the datum {name:?} is a {kind:?}, not a plane"
302 ))),
303 }
304 }
305}
306
307macro_rules! picked_as_drawn {
310 ($($kind:ty),*) => {$(
311 impl SelectableElement<Datums> for $kind {
312 const PICKED_FROM: &'static [&'static str] = &[AnyDatum::NAME];
313
314 fn select(
315 reader: &Reader,
316 hit: &PickedElement,
317 ) -> Option<ElementReference<Datums, Self>> {
318 (hit.target == *datums().location() && hit.kind == AnyDatum::NAME).then_some(())?;
319 let reference = ElementReference::new(datums(), &hit.name);
320 reader.retrieve_element(&reference).ok()?;
321 Some(reference)
322 }
323
324 fn drawn(
325 _: &Reader,
326 element: &ElementReference<Datums, Self>,
327 ) -> Vec<PickedElement> {
328 vec![ElementReference::<Datums, AnyDatum>::new(datums(), element.name()).picked()]
329 }
330 }
331 )*};
332}
333
334picked_as_drawn!(AnyDatum, Point, Line, Plane);
335
336pub struct DatumsDrawer;
338
339impl Drawer for DatumsDrawer {
340 fn draw(&self, reader: &Reader, view: &mut PartView) -> GeopResult<()> {
341 read_datums(reader)?.draw(view)
342 }
343
344 fn structure(&self, reader: &Reader) -> GeopResult<Vec<StructureItem>> {
345 let item = |name: &str| StructureItem::drawn("datum", "Datums", name);
346 let mut items = vec![item(ORIGIN)];
347 items.extend(read_datums(reader)?.names().into_iter().map(item));
348 Ok(items)
349 }
350}
351
352#[cfg(test)]
353mod tests {
354 use geop_core_editor::selectable::{Selectable, selected};
355 use geop_core_math::scalars::Scalar;
356 use geop_core_program::{operation_registry::OperationRegistry, part::Part, runner::Runner};
357
358 use super::*;
359
360 fn part() -> Part {
363 let mut part = Part::default();
364 let runner = Runner::new(OperationRegistry::default());
365 (runner.run_step(&mut part, "a", |ctx| {
366 ctx.produce(&datums(), &(), |reader, _| {
367 let mut datums: Datums = reader.previous()?;
368 let world = CoordinateSystem::world_at(Vector3::zero());
369 let axis = Datum {
370 kind: DatumKind::Frame,
371 frame: world,
372 }
373 .component(DatumComponent::Axis(FrameAxis::X))?;
374 datums.insert("a", axis)?;
375 Ok(datums)
376 })
377 }))
378 .unwrap();
379 part
380 }
381
382 #[test]
385 fn datums_are_drawn_and_picked_by_their_names() {
386 use geop_core_editor::editing::{Pointer, Reach};
387 use geop_core_math::primitives::Ray;
388 let part = part();
389 let reader = part.current();
390 let mut view = PartView::new();
391 read_datums(&reader).unwrap().draw(&mut view).unwrap();
392 let view = view.finish();
393 assert_eq!(view.datums.len(), 2);
394 let v = |p: [f64; 3]| Vector3::from_array(p.map(Scalar::from_f64));
395 let pointer = Pointer {
397 cycle: 0,
398 ray: Ray::try_new(v([0.45, 0.45, 10.0]), v([0.0, 0.0, -1.0])).unwrap(),
399 reach: Reach::Tube {
400 radius: Scalar::from_f64(0.1),
401 },
402 };
403 let hit = view
404 .pick(&pointer, &[drawn::DATUM.to_string()], &[])
405 .unwrap();
406 assert_eq!(hit.element, self::hit("origin:xy"));
407 let planes: Vec<ElementReference<Datums, Plane>> = selected(&reader, &[hit.element]);
408 assert_eq!(planes, [origin_plane(FrameAxis::Z)]);
409 }
410
411 fn hit(name: &str) -> PickedElement {
412 ElementReference::<Datums, AnyDatum>::new(datums(), name).picked()
413 }
414
415 #[test]
418 fn datums_are_what_they_are() {
419 let part = part();
420 let reader = part.current();
421 let origin =
422 reader.retrieve_element(&ElementReference::<Datums, Point>::new(datums(), ORIGIN));
423 assert_eq!(origin.unwrap(), Vector3::zero());
424 let xy = reader
425 .retrieve_element(&origin_plane(FrameAxis::Z))
426 .unwrap();
427 assert!(xy.w().to_array()[2].could_be_equal(Scalar::ONE));
428 let z = reader.retrieve_element(&origin_axis(FrameAxis::Z)).unwrap();
429 assert!(z.direction.to_array()[2].could_be_equal(Scalar::ONE));
430 let a = reader.retrieve_element(&ElementReference::<Datums, Line>::new(datums(), "a"));
431 assert!(a.unwrap().direction.to_array()[0].could_be_equal(Scalar::ONE));
432 let not_a_plane = ElementReference::<Datums, Plane>::new(datums(), "a");
433 assert!(reader.retrieve_element(¬_a_plane).is_err());
434 assert!(
435 reader
436 .retrieve_element(&ElementReference::<Datums, Line>::new(datums(), "b"))
437 .is_err()
438 );
439 assert_eq!(read_datums(&reader).unwrap().names(), ["a"]);
440 assert_eq!(
441 component_name(ORIGIN, DatumComponent::Plane(FrameAxis::Y)),
442 "origin:zx"
443 );
444 }
445
446 #[test]
449 fn a_datum_is_picked_as_what_it_is() {
450 let part = part();
451 let reader = part.current();
452 let lines: Vec<ElementReference<Datums, Line>> =
453 selected(&reader, &[hit("a"), hit("origin:xy"), hit("origin:z")]);
454 let names: Vec<&str> = lines.iter().map(|l| l.name()).collect();
455 assert_eq!(names, ["a", "origin:z"]);
456 assert_eq!(lines[1].drawn(&reader), [hit("origin:z")]);
457 assert_eq!(
458 serde_json::to_value(&lines[0]).unwrap(),
459 serde_json::json!("a")
460 );
461 let given = Part::default();
462 let reader = given.current();
463 assert!(read_datums(&reader).unwrap().names().is_empty());
464 let plane = reader.retrieve_element(&origin_plane(FrameAxis::X));
465 assert!(plane.unwrap().w().to_array()[0].could_be_equal(Scalar::ONE));
466 }
467
468 #[test]
472 fn frames() {
473 use crate::{
474 NamedModel, Solid,
475 testing::{given_part, unit_cube},
476 };
477 use geop_core_editor::{registry::Registry, selectable, testing::pointer};
478
479 let part = given_part(&unit_cube()).unwrap();
480 let registry = crate::register(Registry::default()).unwrap();
481 let view = registry
482 .draw(&part.current(), &Default::default(), None)
483 .unwrap();
484 let down = |x: f64, y: f64| pointer([x, y, 5.0], [0.0, 0.0, -1.0]);
485 let frame = [drawn::DATUM, drawn::VERTEX, drawn::EDGE, drawn::FACE].map(String::from);
486 let name = |x: f64, y: f64| view.pick(&down(x, y), &frame, &[]).map(|h| h.element.name);
487 assert_eq!(name(0.0, 0.0), Some(ORIGIN.to_string()));
489 assert_eq!(
490 name(0.06, 0.0),
491 Some(origin_axis(FrameAxis::X).name().to_string())
492 );
493 assert_eq!(
494 name(0.04, 0.04),
495 Some(origin_plane(FrameAxis::Z).name().to_string())
496 );
497 let solid = selectable::pick::<ElementReference<NamedModel, Solid>>(
499 &view,
500 &part.current(),
501 &down(0.04, 0.04),
502 );
503 assert!(solid.is_some());
504 }
505}