1use geop_core_math::{
12 geop_error::GeopResult,
13 polygon::loops_contain,
14 primitives::{CoordinateSystem, DatumComponent, DatumKind, FrameAxis},
15 scalars::{Scalar, as_f64},
16 vector::{Vector2, Vector3},
17};
18use geop_core_sketch::{CurveId, PointId, point::P2, profile::curve_polyline};
19use geop_core_topology::{FaceId, Model, SolidId};
20use geop_ops_rasterize::rasterize;
21use serde::Serialize;
22
23use super::{Pointer, hit::nearer};
24use crate::{
25 Part,
26 operation::{Aspects, EntityRef, Role},
27};
28
29const RESOLUTION: usize = 24;
33
34pub const FRAME: f64 = 10.0;
39
40#[derive(Clone, Debug, PartialEq)]
42pub struct PartHit<S: Scalar> {
43 pub entity: EntityRef,
44 pub point: Vector3<S>,
45 pub t: S,
46}
47
48#[derive(Clone, Debug, Serialize)]
50#[serde(bound = "S: Scalar")]
51pub struct ViewVertex<S: Scalar> {
52 pub name: String,
53 pub at: Vector3<S>,
54}
55
56fn roles_of<S: Scalar>(entity: &EntityRef, part: &Part<S>) -> Vec<Role> {
59 Aspects::of(entity, part)
60 .map(|g| g.roles())
61 .unwrap_or_default()
62}
63
64#[derive(Clone, Debug, Serialize)]
66#[serde(bound = "S: Scalar")]
67pub struct ViewEdge<S: Scalar> {
68 pub name: String,
69 pub polyline: Vec<Vector3<S>>,
70 #[serde(skip)]
73 pub roles: Vec<Role>,
74}
75
76#[derive(Clone, Debug, Serialize)]
79#[serde(bound = "S: Scalar")]
80pub struct ViewFace<S: Scalar> {
81 pub name: String,
82 pub solid: Option<String>,
84 pub triangles: Vec<[Vector3<S>; 3]>,
85 pub normals: Vec<[Vector3<S>; 3]>,
86 #[serde(skip)]
89 pub roles: Vec<Role>,
90}
91
92#[derive(Clone, Debug, Serialize)]
94#[serde(bound = "S: Scalar")]
95pub struct ViewCurve<S: Scalar> {
96 pub id: CurveId,
97 pub construction: bool,
98 pub polyline: Vec<Vector2<S>>,
99 #[serde(skip)]
101 pub roles: Vec<Role>,
102}
103
104#[derive(Clone, Debug, Serialize)]
106#[serde(bound = "S: Scalar")]
107pub struct ViewSketchPoint<S: Scalar> {
108 pub id: PointId,
109 pub at: Vector2<S>,
110}
111
112#[derive(Clone, Debug, Serialize)]
116#[serde(bound = "S: Scalar")]
117pub struct ViewSketch<S: Scalar> {
118 pub name: String,
119 pub plane: CoordinateSystem<S>,
120 #[serde(skip)]
121 pub regions: Vec<Vec<Vec<Vector2<S>>>>,
122 pub curves: Vec<ViewCurve<S>>,
123 pub points: Vec<ViewSketchPoint<S>>,
124}
125
126#[derive(Clone, Debug, Serialize)]
128#[serde(bound = "S: Scalar")]
129pub struct ViewDatum<S: Scalar> {
130 pub name: String,
131 pub kind: DatumKind,
132 pub frame: CoordinateSystem<S>,
133}
134
135#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
139#[serde(bound = "S: Scalar")]
140pub struct Extent<S: Scalar> {
141 pub center: Vector3<S>,
142 #[serde(with = "as_f64")]
143 pub size: S,
144}
145
146#[derive(Clone, Debug, Serialize)]
149#[serde(bound = "S: Scalar")]
150pub struct PartView<S: Scalar> {
151 pub vertices: Vec<ViewVertex<S>>,
152 pub edges: Vec<ViewEdge<S>>,
153 pub faces: Vec<ViewFace<S>>,
154 pub sketches: Vec<ViewSketch<S>>,
155 pub datums: Vec<ViewDatum<S>>,
156 pub solids: Vec<String>,
158 pub extent: Extent<S>,
159}
160
161pub fn solid_of_face<S: Scalar>(model: &Model<S>, face: FaceId) -> Option<SolidId> {
164 model
165 .get_face(face)
166 .ok()
167 .and_then(|f| model.get_shell(f.shell).ok())
168 .map(|s| s.solid)
169}
170
171impl<S: Scalar> PartView<S> {
172 pub fn of(part: &Part<S>) -> GeopResult<Self> {
174 let model = part.topology();
175 let raster = rasterize(model, RESOLUTION)?;
176 let name = |id: crate::RefId| part.name_of(id).unwrap_or_default().to_string();
177 let uv = |polyline: Vec<P2>| {
178 polyline
179 .into_iter()
180 .map(|p| Vector2::from_array(p.map(S::from_f64)))
181 .collect::<Vec<_>>()
182 };
183
184 let mut vertices: Vec<_> = raster.vertices.iter().collect();
185 vertices.sort_by_key(|(id, _)| id.0);
186 let mut edges: Vec<_> = raster.edges.iter().collect();
187 edges.sort_by_key(|(id, _)| id.0);
188 let mut faces: Vec<_> = raster.faces.iter().collect();
189 faces.sort_by_key(|(id, _)| id.0);
190 let mut solids: Vec<SolidId> = model.solids.keys().copied().collect();
191 solids.sort_by_key(|s| s.0);
192
193 let sketches = part
194 .sketches()
195 .map(|(id, placed)| {
196 let sketch = name(id.into());
197 let s = &placed.sketch;
198 let positions = s.positions();
199 let regions = s
202 .regions()
203 .map(|regions| {
204 regions
205 .iter()
206 .map(|r| {
207 std::iter::once(&r.outer)
208 .chain(&r.holes)
209 .map(|l| uv(l.polyline(s, &positions)))
210 .collect()
211 })
212 .collect()
213 })
214 .unwrap_or_default();
215 ViewSketch {
216 name: sketch.clone(),
217 plane: placed.plane.clone(),
218 regions,
219 curves: s
220 .curves
221 .iter()
222 .map(|(&id, c)| ViewCurve {
223 id,
224 construction: c.construction,
225 polyline: uv(curve_polyline(s, &positions, id)),
226 roles: roles_of(
227 &EntityRef::SketchCurve {
228 sketch: sketch.clone(),
229 curve: id,
230 },
231 part,
232 ),
233 })
234 .collect(),
235 points: positions
236 .iter()
237 .map(|(&id, &p)| ViewSketchPoint {
238 id,
239 at: Vector2::from_array(p.map(S::from_f64)),
240 })
241 .collect(),
242 }
243 })
244 .collect();
245
246 let mut view = PartView {
247 vertices: vertices
248 .into_iter()
249 .map(|(&id, p)| ViewVertex {
250 name: name(id.into()),
251 at: *p,
252 })
253 .collect(),
254 edges: edges
255 .into_iter()
256 .map(|(&id, polyline)| {
257 let edge = name(id.into());
258 ViewEdge {
259 roles: roles_of(&EntityRef::Edge { name: edge.clone() }, part),
260 name: edge,
261 polyline: polyline.clone(),
262 }
263 })
264 .collect(),
265 faces: faces
266 .into_iter()
267 .map(|(&id, tris)| {
268 let face = name(id.into());
269 ViewFace {
270 roles: roles_of(&EntityRef::Face { name: face.clone() }, part),
271 name: face,
272 solid: solid_of_face(model, id).map(|s| name(s.into())),
273 triangles: tris.iter().map(|t| [t.a, t.b, t.c]).collect(),
274 normals: tris
275 .iter()
276 .map(|t| t.vertex_normals.unwrap_or([t.normal; 3]))
277 .collect(),
278 }
279 })
280 .collect(),
281 sketches,
282 datums: part
283 .datums()
284 .map(|(id, datum)| ViewDatum {
285 name: name(id.into()),
286 kind: datum.kind,
287 frame: datum.frame.clone(),
288 })
289 .collect(),
290 solids: solids.into_iter().map(|s| name(s.into())).collect(),
291 extent: Extent {
292 center: Vector3::zero(),
293 size: S::ONE,
294 },
295 };
296 view.extent = view.measure();
297 Ok(view)
298 }
299
300 fn measure(&self) -> Extent<S> {
302 let sketch_points = self.sketches.iter().flat_map(|sketch| {
303 sketch
304 .curves
305 .iter()
306 .flat_map(|c| &c.polyline)
307 .map(|p| sketch.plane.uv_to_xyz(p))
308 });
309 let hull = self
310 .vertices
311 .iter()
312 .map(|v| v.at)
313 .chain(self.edges.iter().flat_map(|e| e.polyline.iter().copied()))
314 .chain(
315 self.faces
316 .iter()
317 .flat_map(|f| f.triangles.iter().flatten().copied()),
318 )
319 .chain(sketch_points)
320 .reduce(|a, b| a.union(&b));
321 let Some(hull) = hull else {
322 return Extent {
323 center: Vector3::zero(),
324 size: S::ONE,
325 };
326 };
327 let size = Vector3::from_array([0, 1, 2].map(|k| hull[k].width())).norm();
328 Extent {
329 center: Vector3::from_array([0, 1, 2].map(|k| hull[k].midpoint())),
330 size: if size.definitely_less(S::ONE) {
331 S::ONE
332 } else {
333 size
334 },
335 }
336 }
337
338 pub fn pick(
346 &self,
347 pointer: &Pointer<S>,
348 roles: &[Role],
349 scope: Option<&EntityRef>,
350 ) -> Option<PartHit<S>> {
351 let accept = |entity: &EntityRef, its: &[Role]| {
352 its.iter().any(|r| roles.contains(r)) && scope.is_none_or(|s| entity.lies_in(s))
353 };
354 if let Some(hit) = self.pick_frame(pointer, &accept) {
355 return Some(hit);
356 }
357 let ray = &pointer.ray;
358 let face = self.pick_face(pointer);
359 let visible = |t: S| {
362 face.as_ref()
363 .is_none_or(|(ft, _)| !t.definitely_greater(ft.add(pointer.reach_at(1.0, *ft))))
364 };
365 let near = |(dist, t): (S, S)| (pointer.within(dist, t, 1.0) && visible(t)).then_some(t);
366 let nearest = |hits: Vec<PartHit<S>>| hits.into_iter().min_by(|a, b| nearer(a.t, b.t));
367
368 let vertices = self.vertices.iter().map(|v| {
369 let entity = EntityRef::Vertex {
370 name: v.name.clone(),
371 };
372 (entity, v.at)
373 });
374 let sketch_points = self.sketches.iter().flat_map(|sketch| {
375 sketch.points.iter().map(|p| {
376 let entity = EntityRef::SketchPoint {
377 sketch: sketch.name.clone(),
378 point: p.id,
379 };
380 (entity, sketch.plane.uv_to_xyz(&p.at))
381 })
382 });
383 let points = vertices
384 .chain(sketch_points)
385 .filter(|(entity, _)| accept(entity, &[Role::Point]))
386 .filter_map(|(entity, at)| {
387 near(ray.distance_to_point(&at)).map(|t| PartHit {
388 entity,
389 point: at,
390 t,
391 })
392 })
393 .collect();
394 if let Some(hit) = nearest(points) {
395 return Some(hit);
396 }
397
398 let polyline_hit =
399 |entity: EntityRef, polyline: &mut dyn Iterator<Item = (Vector3<S>, Vector3<S>)>| {
400 polyline
401 .filter_map(|(a, b)| near(ray.distance_to_segment(&a, &b)))
402 .min_by(|&a, &b| nearer(a, b))
403 .map(|t| PartHit {
404 entity: entity.clone(),
405 point: ray.at(t),
406 t,
407 })
408 };
409 let mut curves = Vec::new();
410 for e in &self.edges {
411 let entity = EntityRef::Edge {
412 name: e.name.clone(),
413 };
414 if accept(&entity, &e.roles) {
415 let mut segments = e.polyline.windows(2).map(|w| (w[0], w[1]));
416 curves.extend(polyline_hit(entity, &mut segments));
417 }
418 }
419 for sketch in &self.sketches {
420 for c in &sketch.curves {
421 let entity = EntityRef::SketchCurve {
422 sketch: sketch.name.clone(),
423 curve: c.id,
424 };
425 if accept(&entity, &c.roles) {
426 let world = |p: &Vector2<S>| sketch.plane.uv_to_xyz(p);
427 let mut segments = c.polyline.windows(2).map(|w| (world(&w[0]), world(&w[1])));
428 curves.extend(polyline_hit(entity, &mut segments));
429 }
430 }
431 }
432 if let Some(hit) = nearest(curves) {
433 return Some(hit);
434 }
435
436 let mut hits: Vec<PartHit<S>> = Vec::new();
437 if let Some((t, f)) = face {
438 let face = EntityRef::Face {
439 name: f.name.clone(),
440 };
441 let solid = f.solid.clone().map(|name| EntityRef::Solid { name });
442 let entity = if accept(&face, &f.roles) {
443 Some(face)
444 } else {
445 solid.filter(|solid| accept(solid, &[Role::Solid]))
446 };
447 hits.extend(entity.map(|entity| PartHit {
448 entity,
449 point: ray.at(t),
450 t,
451 }));
452 }
453 hits.extend(self.pick_sketch(pointer, &accept));
454 hits.extend(self.pick_datum(pointer, &accept));
455 nearest(hits)
456 }
457
458 pub fn can_fill(&self, entity: &EntityRef, roles: &[Role]) -> bool {
461 let fills = |its: &[Role]| its.iter().any(|r| roles.contains(r));
462 match entity {
463 EntityRef::Sketch { name } => self.sketches.iter().any(|s| {
464 s.name == *name
465 && (fills(&[Role::Sketch])
466 || (!s.points.is_empty() && fills(&[Role::Point]))
467 || s.curves.iter().any(|c| fills(&c.roles)))
468 }),
469 EntityRef::Datum { name, .. } => self.datums.iter().any(|d| {
470 d.name == *name
471 && match d.kind {
472 DatumKind::Frame => fills(&[Role::Point, Role::Line, Role::Plane]),
473 kind => fills(&[datum_role(kind)]),
474 }
475 }),
476 _ => false,
477 }
478 }
479
480 fn pick_face(&self, pointer: &Pointer<S>) -> Option<(S, &ViewFace<S>)> {
482 self.faces
483 .iter()
484 .flat_map(|f| f.triangles.iter().map(move |tri| (f, tri)))
485 .filter_map(|(f, [a, b, c])| pointer.ray.intersect_triangle(a, b, c).map(|t| (t, f)))
486 .min_by(|a, b| nearer(a.0, b.0))
487 }
488
489 fn pick_sketch(
492 &self,
493 pointer: &Pointer<S>,
494 accept: &impl Fn(&EntityRef, &[Role]) -> bool,
495 ) -> Option<PartHit<S>> {
496 self.sketches
497 .iter()
498 .filter(|sketch| {
499 let entity = EntityRef::Sketch {
500 name: sketch.name.clone(),
501 };
502 accept(&entity, &[Role::Sketch])
503 })
504 .filter_map(|sketch| {
505 let (t, p) = pointer.ray.intersect_uv_plane(&sketch.plane)?;
506 let reach = pointer.reach_at(1.0, t);
507 let in_region = sketch.regions.iter().any(|loops| loops_contain(loops, &p));
508 let on_curve = || {
509 sketch.curves.iter().any(|c| {
510 c.polyline.windows(2).any(|w| {
511 !p.distance_to_segment(&w[0], &w[1])
512 .definitely_greater(reach)
513 })
514 })
515 };
516 (in_region || on_curve()).then(|| PartHit {
517 entity: EntityRef::Sketch {
518 name: sketch.name.clone(),
519 },
520 point: pointer.ray.at(t),
521 t,
522 })
523 })
524 .min_by(|a, b| nearer(a.t, b.t))
525 }
526
527 fn pick_datum(
532 &self,
533 pointer: &Pointer<S>,
534 accept: &impl Fn(&EntityRef, &[Role]) -> bool,
535 ) -> Option<PartHit<S>> {
536 let Extent { center, size } = self.extent;
537 let ray = &pointer.ray;
538 let half = size.div(S::TWO).ok()?;
539 self.datums
540 .iter()
541 .filter(|d| {
542 d.kind != DatumKind::Frame
543 && accept(&EntityRef::datum(d.name.clone()), &[datum_role(d.kind)])
544 })
545 .filter_map(|d| {
546 let f = &d.frame;
547 let (origin, w) = (f.origin(), f.w());
548 let t = match d.kind {
549 DatumKind::Point => {
550 let (dist, t) = ray.distance_to_point(origin);
551 pointer.within(dist, t, 1.0).then_some(t)?
552 }
553 DatumKind::Axis => {
554 let mid = origin.add(&w.prod_scalar(center.sub(origin).prod_dot(w)));
555 let reach = w.prod_scalar(size);
556 let (dist, t) = ray.distance_to_segment(&mid.sub(&reach), &mid.add(&reach));
557 pointer.within(dist, t, 1.0).then_some(t)?
558 }
559 DatumKind::Plane => {
560 let (t, point) = ray.intersect_plane(origin, w)?;
561 let d = point.sub(¢er);
562 let inside =
563 |axis: &Vector3<S>| !d.prod_dot(axis).abs().definitely_greater(half);
564 (inside(f.u()) && inside(f.v())).then_some(t)?
565 }
566 DatumKind::Frame => unreachable!("frames are filtered out above"),
567 };
568 Some(PartHit {
569 entity: EntityRef::datum(d.name.clone()),
570 point: ray.at(t),
571 t,
572 })
573 })
574 .min_by(|a, b| nearer(a.t, b.t))
575 }
576
577 fn pick_frame(
584 &self,
585 pointer: &Pointer<S>,
586 accept: &impl Fn(&EntityRef, &[Role]) -> bool,
587 ) -> Option<PartHit<S>> {
588 let ray = &pointer.ray;
589 let frac = |size: S, x: f64| size.mul(S::from_f64(x));
590 let (mut balls, mut axes, mut planes) = (Vec::new(), Vec::new(), Vec::new());
591 for d in self.datums.iter().filter(|d| d.kind == DatumKind::Frame) {
592 let f = &d.frame;
593 let origin = f.origin();
594 let size = pointer.reach_at(FRAME, ray.closest_to_point(origin));
595 if accept(&EntityRef::datum(d.name.clone()), &[Role::Point]) {
596 let (dist, t) = ray.distance_to_point(origin);
597 if !dist.definitely_greater(frac(size, 0.12)) {
598 balls.push(PartHit {
599 entity: EntityRef::datum(d.name.clone()),
600 point: *origin,
601 t,
602 });
603 }
604 }
605 for axis in FrameAxis::ALL {
606 let dir = axis.of(f);
607 let component = |c| EntityRef::datum_component(d.name.clone(), c);
608 if accept(&component(DatumComponent::Axis(axis)), &[Role::Line]) {
609 let (a, b) = (
610 origin.add(&dir.prod_scalar(frac(size, 0.1))),
611 origin.add(&dir.prod_scalar(size)),
612 );
613 let (dist, t) = ray.distance_to_segment(&a, &b);
614 if pointer.within(dist, t, 0.6) {
615 axes.push(PartHit {
616 entity: EntityRef::datum_component(
617 d.name.clone(),
618 DatumComponent::Axis(axis),
619 ),
620 point: b,
621 t,
622 });
623 }
624 }
625 if accept(&component(DatumComponent::Plane(axis)), &[Role::Plane])
626 && let Some((t, point)) = ray.intersect_plane(origin, &dir)
627 {
628 let local = point.sub(origin);
629 let in_square =
630 FrameAxis::ALL
631 .iter()
632 .filter(|&&other| other != axis)
633 .all(|other| {
634 let x = local
635 .prod_dot(&other.of(f))
636 .div(size)
637 .unwrap_or(S::INFINITY);
638 !x.sub(S::from_f64(0.45))
639 .abs()
640 .definitely_greater(S::from_f64(0.15))
641 });
642 if in_square {
643 planes.push(PartHit {
644 entity: EntityRef::datum_component(
645 d.name.clone(),
646 DatumComponent::Plane(axis),
647 ),
648 point,
649 t,
650 });
651 }
652 }
653 }
654 }
655 let nearest = |hits: Vec<PartHit<S>>| hits.into_iter().min_by(|a, b| nearer(a.t, b.t));
656 nearest(balls)
657 .or_else(|| nearest(axes))
658 .or_else(|| nearest(planes))
659 }
660}
661
662fn datum_role(kind: DatumKind) -> Role {
664 match kind {
665 DatumKind::Point | DatumKind::Frame => Role::Point,
666 DatumKind::Axis => Role::Line,
667 DatumKind::Plane => Role::Plane,
668 }
669}