fj_operations/
sketch.rs

1use std::ops::Deref;
2
3use fj_interop::{debug::DebugInfo, mesh::Color};
4use fj_kernel::{
5    objects::{Cycle, Face, HalfEdge, Sketch},
6    operations::{BuildCycle, BuildHalfEdge, Insert, UpdateCycle},
7    services::Services,
8};
9use fj_math::{Aabb, Point};
10use itertools::Itertools;
11
12use super::Shape;
13
14impl Shape for fj::Sketch {
15    type Brep = Sketch;
16
17    fn compute_brep(
18        &self,
19        services: &mut Services,
20        _: &mut DebugInfo,
21    ) -> Self::Brep {
22        let surface = services.objects.surfaces.xy_plane();
23
24        let face = match self.chain() {
25            fj::Chain::Circle(circle) => {
26                let half_edge = HalfEdge::circle(circle.radius(), services)
27                    .insert(services);
28                let exterior = Cycle::new([half_edge]).insert(services);
29
30                Face::new(
31                    surface,
32                    exterior,
33                    Vec::new(),
34                    Some(Color(self.color())),
35                )
36            }
37            fj::Chain::PolyChain(poly_chain) => {
38                let segments = poly_chain.to_segments();
39                assert!(
40                    !segments.is_empty(),
41                    "Attempted to compute a Brep from an empty sketch"
42                );
43
44                let exterior = {
45                    let mut cycle = Cycle::empty();
46
47                    let segments = poly_chain
48                        .to_segments()
49                        .into_iter()
50                        .map(|fj::SketchSegment { endpoint, route }| {
51                            let endpoint = Point::from(endpoint);
52                            (endpoint, route)
53                        })
54                        .circular_tuple_windows();
55
56                    for ((start, route), (end, _)) in segments {
57                        let half_edge = match route {
58                            fj::SketchSegmentRoute::Direct => {
59                                HalfEdge::line_segment(
60                                    [start, end],
61                                    None,
62                                    services,
63                                )
64                            }
65                            fj::SketchSegmentRoute::Arc { angle } => {
66                                HalfEdge::arc(start, end, angle.rad(), services)
67                            }
68                        };
69                        let half_edge = half_edge.insert(services);
70
71                        cycle = cycle.add_half_edges([half_edge]);
72                    }
73
74                    cycle.insert(services)
75                };
76
77                Face::new(
78                    surface,
79                    exterior,
80                    Vec::new(),
81                    Some(Color(self.color())),
82                )
83            }
84        };
85
86        let sketch = Sketch::new(vec![face.insert(services)]).insert(services);
87        sketch.deref().clone()
88    }
89
90    fn bounding_volume(&self) -> Aabb<3> {
91        match self.chain() {
92            fj::Chain::Circle(circle) => Aabb {
93                min: Point::from([-circle.radius(), -circle.radius(), 0.0]),
94                max: Point::from([circle.radius(), circle.radius(), 0.0]),
95            },
96            fj::Chain::PolyChain(poly_chain) => {
97                let segments = poly_chain.to_segments();
98                assert!(
99                    !segments.is_empty(),
100                    "Attempted to compute a bounding box from an empty sketch"
101                );
102
103                let mut points = vec![];
104
105                let mut start_point = segments[segments.len() - 1].endpoint;
106                segments.iter().for_each(|segment| {
107                    match segment.route {
108                        fj::SketchSegmentRoute::Direct => (),
109                        fj::SketchSegmentRoute::Arc { angle } => {
110                            use std::f64::consts::PI;
111                            let arc = fj_math::Arc::from_endpoints_and_angle(
112                                start_point,
113                                segment.endpoint,
114                                fj_math::Scalar::from_f64(angle.rad()),
115                            );
116                            for circle_min_max_angle in
117                                [0., PI / 2., PI, 3. * PI / 2.]
118                            {
119                                let mm_angle = fj_math::Scalar::from_f64(
120                                    circle_min_max_angle,
121                                );
122                                if arc.start_angle < mm_angle
123                                    && mm_angle < arc.end_angle
124                                {
125                                    points.push(
126                                        arc.center
127                                            + [
128                                                arc.radius
129                                                    * circle_min_max_angle
130                                                        .cos(),
131                                                arc.radius
132                                                    * circle_min_max_angle
133                                                        .sin(),
134                                            ],
135                                    );
136                                }
137                            }
138                        }
139                    }
140                    points.push(Point::from(segment.endpoint));
141                    start_point = segment.endpoint;
142                });
143
144                Aabb::<3>::from_points(points.into_iter().map(Point::to_xyz))
145            }
146        }
147    }
148}