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}