1use axiolid_brep::{ExactBRep, ExactBRepBuilder};
32use axiolid_contracts::{GeomError, GeomResult, Operation};
33use axiolid_core::{Frame2, Frame3, Interval, Point2, Point3, Scalar, Tolerance, Vec2, Vec3};
34use axiolid_curve::{Circle2, Circle3, Curve2, Curve3, Line2, Line3};
35use axiolid_surface::{Cone, Cylinder, Plane, Sphere, Surface, Torus};
36use axiolid_topology::{
37 Edge, EdgeId, EdgeUse, Face, FaceBound, FaceId, Loop, LoopId, Orientation, Shell, Solid,
38 Vertex, VertexId,
39};
40
41use axiolid_guarantees::Sign;
42use axiolid_overlay::{ArcRing, ArcVertex};
43
44use crate::contour_lower::arc_ring_signed_area;
45use crate::revolve::side_of_axis;
46use crate::BACKEND_ID;
47
48const TAU: Scalar = core::f64::consts::TAU;
49
50fn unsupported(input: &'static str) -> GeomError {
51 GeomError::UnsupportedInput {
52 backend: BACKEND_ID,
53 operation: Operation::Sweep,
54 input,
55 }
56}
57
58#[derive(Debug, Clone, Copy)]
60struct Station {
61 radius: Scalar,
63 height: Scalar,
65 bulge: Scalar,
67 on_axis: bool,
69 magnitude: Scalar,
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77enum Contact {
78 Axis,
80 Cap,
82 Arc,
85 Cone,
88}
89
90pub fn revolve_arc_ring(
114 ring: &ArcRing,
115 axis_origin: Point3,
116 tolerance: Tolerance,
117) -> GeomResult<ExactBRep> {
118 revolve_ring_scaled(ring, axis_origin, 0.0, tolerance)
119}
120
121pub(crate) fn revolve_ring_scaled(
125 ring: &ArcRing,
126 axis_origin: Point3,
127 magnitude: Scalar,
128 tolerance: Tolerance,
129) -> GeomResult<ExactBRep> {
130 if axis_origin.z != 0.0 {
133 return Err(unsupported(
134 "exact revolution about an axis out of the profile plane",
135 ));
136 }
137 let epsilon = tolerance.linear();
138 let halved = split_axis_semicircles(ring, axis_origin);
139 let ring = &halved;
140 let count = ring.vertices.len();
141 if count < 3 {
142 return Err(GeomError::Degenerate(format!(
143 "a revolved section needs at least three vertices, got {count}"
144 )));
145 }
146
147 let area = arc_ring_signed_area(ring);
150 if area == 0.0 {
151 return Err(GeomError::Degenerate(
152 "revolved section encloses no area".to_owned(),
153 ));
154 }
155 let forward = area > 0.0;
156
157 let axis_point = Point2::new(axis_origin.x, axis_origin.y);
158 let (mut positive, mut negative) = (false, false);
159 let stations = (0..count)
160 .map(|index| {
161 let source = if forward { index } else { count - 1 - index };
162 let vertex = ring.vertices[source];
163 if !(vertex.point.x.is_finite() && vertex.point.y.is_finite()) {
164 return Err(GeomError::InvalidInput(
165 "revolved section must have finite vertices".to_owned(),
166 ));
167 }
168 let bulge = if forward {
169 vertex.bulge
170 } else {
171 -ring.vertices[(source + count - 1) % count].bulge
174 };
175 let side = side_of_axis(axis_point, Vec3::Y, vertex.point);
176 match side {
177 Sign::Positive => positive = true,
178 Sign::Negative => negative = true,
179 _ => {}
180 }
181 Ok(Station {
182 radius: vertex.point.x - axis_origin.x,
183 height: vertex.point.y,
184 bulge,
185 on_axis: side == Sign::Zero,
186 magnitude: vertex
187 .point
188 .x
189 .abs()
190 .max(vertex.point.y.abs())
191 .max(axis_origin.x.abs())
192 .max(magnitude),
193 })
194 })
195 .collect::<GeomResult<Vec<Station>>>()?;
196
197 if positive && negative {
198 return Err(unsupported(
199 "exact revolution of a section crossing the axis",
200 ));
201 }
202 if stations
203 .iter()
204 .any(|station| !station.on_axis && station.radius.abs() <= epsilon)
205 {
206 return Err(unsupported(
207 "exact revolution of a section within tolerance of the axis but not on it to the bit",
208 ));
209 }
210 check_axis_contact(&stations, epsilon)?;
211 let stations: Vec<Station> = if positive {
218 (0..count)
219 .map(|index| {
220 let source = count - 1 - index;
221 Station {
222 radius: -stations[source].radius,
223 height: stations[source].height,
224 bulge: stations[(source + count - 1) % count].bulge,
225 on_axis: stations[source].on_axis,
226 magnitude: stations[source].magnitude,
227 }
228 })
229 .collect()
230 } else {
231 stations
232 };
233
234 build(&stations, axis_origin, epsilon)
235}
236
237fn split_axis_semicircles(ring: &ArcRing, axis_origin: Point3) -> ArcRing {
243 let axis_point = Point2::new(axis_origin.x, axis_origin.y);
244 let count = ring.vertices.len();
245 let mut vertices = Vec::with_capacity(count + 1);
246 for (index, vertex) in ring.vertices.iter().enumerate() {
247 vertices.push(*vertex);
248 let to = ring.vertices[(index + 1) % count].point;
249 let on_axis = |p: Point2| {
250 p.x.is_finite() && p.y.is_finite() && side_of_axis(axis_point, Vec3::Y, p) == Sign::Zero
251 };
252 if vertex.bulge.abs() == 1.0 && on_axis(vertex.point) && on_axis(to) {
253 let chord = to - vertex.point;
254 let length = chord.length();
255 if length > 0.0 && length.is_finite() {
256 let side = vertex.bulge.signum();
259 let middle = vertex.point
260 + chord * 0.5
261 + Vec2::new(chord.y, -chord.x) / length * (0.5 * length * side);
262 let quarter = side * (core::f64::consts::FRAC_PI_8).tan();
263 let last = vertices.len() - 1;
264 vertices[last].bulge = quarter;
265 vertices.push(ArcVertex {
266 point: middle,
267 bulge: quarter,
268 });
269 }
270 }
271 }
272 ArcRing::new(vertices)
273}
274
275fn contact(stations: &[Station], index: usize) -> Option<Contact> {
277 let here = stations[index];
278 let there = stations[(index + 1) % stations.len()];
279 match (here.on_axis, there.on_axis) {
280 (false, false) => None,
281 _ if here.bulge != 0.0 => Some(Contact::Arc),
282 (true, true) => Some(Contact::Axis),
283 _ if here.height == there.height => Some(Contact::Cap),
284 _ => Some(Contact::Cone),
285 }
286}
287
288fn check_axis_contact(stations: &[Station], epsilon: Scalar) -> GeomResult<()> {
294 let count = stations.len();
295 for (index, station) in stations.iter().enumerate() {
296 if !station.on_axis {
297 continue;
298 }
299 if station.bulge != 0.0 && stations[(index + 1) % count].on_axis {
300 return Err(unsupported(
301 "exact revolution of a section with an arc with both ends on the axis",
302 ));
303 }
304 let incoming = contact(stations, (index + count - 1) % count);
305 let outgoing = contact(stations, index);
306 match (incoming, outgoing) {
307 (Some(Contact::Axis), Some(_)) | (Some(_), Some(Contact::Axis)) => {}
308 (Some(Contact::Arc), _) | (_, Some(Contact::Arc)) => {
309 return Err(unsupported(
310 "exact revolution of a section touching the axis at an isolated vertex where an arc ends (a pinch point or a horn torus)",
311 ));
312 }
313 _ => {
314 return Err(unsupported(
315 "exact revolution of a section touching the axis at an isolated vertex (a pinch point)",
316 ));
317 }
318 }
319 for (edge, other) in [
323 (incoming, (index + count - 1) % count),
324 (outgoing, (index + 1) % count),
325 ] {
326 if edge == Some(Contact::Cone)
327 && (station.height - stations[other].height).abs() <= epsilon
328 {
329 return Err(unsupported(
330 "exact revolution of an edge ending on the axis within the tolerance of perpendicular to it but not exactly",
331 ));
332 }
333 }
334 }
335 Ok(())
336}
337
338fn build(stations: &[Station], axis_origin: Point3, epsilon: Scalar) -> GeomResult<ExactBRep> {
341 let count = stations.len();
342 let mut builder = ExactBRepBuilder::default();
343 builder
344 .topology_mut()
345 .try_reserve(count * 2, count * 2, count * 2, count, 1, 1)
346 .map_err(|_| GeomError::BudgetExceeded {
347 resource: "exact revolution topology",
348 })?;
349
350 let axis_frame = |height: Scalar| frame_at(axis_origin.x, height);
351
352 let mut circles = Vec::with_capacity(count);
355 let mut vertices = Vec::with_capacity(count);
356 let mut points = Vec::with_capacity(count);
357 for (index, station) in stations.iter().enumerate() {
358 let position = Point3::new(axis_origin.x + station.radius, station.height, 0.0);
359 points.push(position);
360 if station.on_axis {
361 circles.push(None);
362 let apex = [(index + count - 1) % count, index]
365 .into_iter()
366 .any(|edge| matches!(contact(stations, edge), Some(Contact::Cone | Contact::Arc)));
367 vertices.push(apex.then(|| builder.topology_mut().add_vertex(Vertex { position })));
368 continue;
369 }
370 let vertex = builder.topology_mut().add_vertex(Vertex { position });
371 let curve = builder.add_curve3(Curve3::Circle(Circle3 {
372 frame: axis_frame(station.height),
373 radius: station.radius,
374 }));
375 let edge = builder.topology_mut().add_edge(Edge {
376 start: vertex,
377 end: vertex,
378 curve: Some(curve),
379 });
380 builder.set_edge_interval(edge, Interval::new(0.0, TAU));
381 circles.push(Some(edge));
382 vertices.push(Some(vertex));
383 }
384
385 let mut faces: Vec<(FaceId, usize, usize)> = Vec::with_capacity(count);
387 for index in 0..count {
388 let next = (index + 1) % count;
389 let here = stations[index];
390 let there = stations[next];
391 let face = if here.on_axis || there.on_axis {
392 if here.on_axis && there.on_axis {
395 continue;
396 }
397 if here.bulge != 0.0 {
398 sphere_wall(
399 &mut builder,
400 axis_origin.x,
401 &circles,
402 &vertices,
403 stations,
404 index,
405 )?
406 } else if here.height == there.height {
407 cap_wall(&mut builder, axis_origin.x, &circles, stations, index)?
408 } else {
409 straight_wall(
410 &mut builder,
411 axis_origin.x,
412 &circles,
413 &vertices,
414 &points,
415 stations,
416 index,
417 true,
418 epsilon,
419 )?
420 }
421 } else if here.bulge != 0.0 {
422 torus_wall(
423 &mut builder,
424 axis_origin.x,
425 &circles,
426 &vertices,
427 stations,
428 index,
429 )?
430 } else if (here.radius - there.radius).abs() <= epsilon {
431 if (here.height - there.height).abs() <= epsilon {
434 continue;
435 }
436 straight_wall(
437 &mut builder,
438 axis_origin.x,
439 &circles,
440 &vertices,
441 &points,
442 stations,
443 index,
444 false,
445 epsilon,
446 )?
447 } else if (here.height - there.height).abs() <= epsilon {
448 annulus_wall(&mut builder, axis_origin.x, &circles, stations, index)?
450 } else {
451 straight_wall(
453 &mut builder,
454 axis_origin.x,
455 &circles,
456 &vertices,
457 &points,
458 stations,
459 index,
460 true,
461 epsilon,
462 )?
463 };
464 faces.push((face, index, next));
465 }
466
467 if faces.len() < 2 {
468 return Err(GeomError::Degenerate(format!(
469 "a revolved solid needs at least two faces, got {}",
470 faces.len()
471 )));
472 }
473 finish(builder, stations, faces)
474}
475#[allow(clippy::too_many_arguments)]
485fn straight_wall(
486 builder: &mut ExactBRepBuilder,
487 axis_x: Scalar,
488 circles: &[Option<EdgeId>],
489 vertices: &[Option<VertexId>],
490 points: &[Point3],
491 stations: &[Station],
492 index: usize,
493 cone: bool,
494 epsilon: Scalar,
495) -> GeomResult<FaceId> {
496 let count = stations.len();
497 let next = (index + 1) % count;
498 let here = stations[index];
499 let there = stations[next];
500 let rise = there.height - here.height;
501
502 let seam_curve = builder.add_curve3(Curve3::Line(Line3 {
503 origin: points[index],
504 direction: points[next] - points[index],
505 }));
506 let seam = builder.topology_mut().add_edge(Edge {
507 start: slot(vertices, index)?,
508 end: slot(vertices, next)?,
509 curve: Some(seam_curve),
510 });
511 builder.set_edge_interval(seam, Interval::UNIT);
512
513 let basis = if there.on_axis { there } else { here };
516 let surface = if cone {
517 let semi_angle = ((there.radius - here.radius) / rise).atan();
520 let scale = here
527 .radius
528 .abs()
529 .max(there.radius.abs())
530 .max(here.height.abs())
531 .max(there.height.abs());
532 let carried = rise * semi_angle.tan() - (there.radius - here.radius);
533 if carried.is_nan() || carried.abs() > epsilon.max(8.0 * Scalar::EPSILON * scale) {
534 return Err(unsupported(
535 "exact revolution of an edge so near perpendicular to the axis that a cone cannot carry its slope",
536 ));
537 }
538 builder.add_surface(Surface::Cone(Cone {
539 frame: frame_at(axis_x, basis.height),
540 radius: basis.radius,
541 semi_angle,
542 }))
543 } else {
544 builder.add_surface(Surface::Cylinder(Cylinder {
545 frame: frame_at(axis_x, here.height),
546 radius: here.radius,
547 }))
548 };
549
550 let loop_id = seam_loop_between(
551 builder,
552 circles[index],
553 circles[next],
554 seam,
555 here.height - basis.height,
556 there.height - basis.height,
557 );
558 Ok(add_face(builder, surface, loop_id, Orientation::Forward))
559}
560
561const CENTRE_ROUNDING: Scalar = 64.0 * Scalar::EPSILON;
574
575fn sphere_wall(
589 builder: &mut ExactBRepBuilder,
590 axis_x: Scalar,
591 circles: &[Option<EdgeId>],
592 vertices: &[Option<VertexId>],
593 stations: &[Station],
594 index: usize,
595) -> GeomResult<FaceId> {
596 let next = (index + 1) % stations.len();
597 let (here, there) = (stations[index], stations[next]);
598 let arc = crate::extrude_arc::arc_geometry(
599 Point2::new(here.radius, here.height),
600 Point2::new(there.radius, there.height),
601 here.bulge,
602 )?;
603 let (pole, other) = if here.on_axis {
604 (here, there)
605 } else {
606 (there, here)
607 };
608 let rise = other.height - pole.height;
609 let not_centred = || {
610 unsupported(
611 "exact revolution of an arc ending on the axis whose circle is not centred on it (spindle or horn torus)",
612 )
613 };
614 if rise == 0.0 {
615 return Err(not_centred());
616 }
617 let centre = pole.height + (other.radius * other.radius + rise * rise) / (2.0 * rise);
618 let radius = (centre - pole.height).abs();
619 let scale = here
620 .radius
621 .abs()
622 .max(here.height.abs())
623 .max(there.radius.abs())
624 .max(there.height.abs())
625 .max(here.magnitude)
626 .max(there.magnitude)
627 + arc.radius;
628 let offset = (arc.centre - Point2::new(0.0, centre)).length();
629 if offset.is_nan() || offset > CENTRE_ROUNDING * scale {
630 return Err(not_centred());
631 }
632 let latitude = |station: Station| {
633 if station.on_axis {
634 if station.height > centre {
635 core::f64::consts::FRAC_PI_2
636 } else {
637 -core::f64::consts::FRAC_PI_2
638 }
639 } else {
640 (station.height - centre).atan2(station.radius)
641 }
642 };
643 let (start, end) = (latitude(here), latitude(there));
644 if (arc.sweep - (end - start)).abs() >= core::f64::consts::PI {
647 return Err(unsupported(
648 "exact revolution of a section crossing the axis",
649 ));
650 }
651
652 let surface = builder.add_surface(Surface::Sphere(Sphere {
653 frame: frame_at(axis_x, centre),
654 radius,
655 }));
656 let seam_curve = builder.add_curve3(Curve3::Circle(Circle3 {
659 frame: Frame3 {
660 origin: Point3::new(axis_x, centre, 0.0),
661 x: Vec3::X,
662 y: Vec3::Y,
663 z: Vec3::Z,
664 },
665 radius,
666 }));
667 let seam = builder.topology_mut().add_edge(Edge {
668 start: slot(vertices, index)?,
669 end: slot(vertices, next)?,
670 curve: Some(seam_curve),
671 });
672 builder.set_edge_interval(seam, Interval::new(start, end));
673 let loop_id = seam_loop_between(builder, circles[index], circles[next], seam, start, end);
674 Ok(add_face(builder, surface, loop_id, Orientation::Forward))
675}
676
677fn slot<T: Copy>(slots: &[Option<T>], index: usize) -> GeomResult<T> {
679 slots[index]
680 .ok_or_else(|| GeomError::Degenerate("a wall references a station on the axis".to_owned()))
681}
682
683fn cap_wall(
690 builder: &mut ExactBRepBuilder,
691 axis_x: Scalar,
692 circles: &[Option<EdgeId>],
693 stations: &[Station],
694 index: usize,
695) -> GeomResult<FaceId> {
696 let next = (index + 1) % stations.len();
697 let (rim, orientation) = if stations[index].on_axis {
698 (next, Orientation::Reversed)
699 } else {
700 (index, Orientation::Forward)
701 };
702 let surface = builder.add_surface(Surface::Plane(Plane {
703 frame: frame_at(axis_x, stations[rim].height),
704 }));
705 let loop_id = circle_loop(
706 builder,
707 slot(circles, rim)?,
708 stations[rim].radius,
709 Orientation::Forward,
710 );
711 Ok(add_face(builder, surface, loop_id, orientation))
712}
713
714fn annulus_wall(
716 builder: &mut ExactBRepBuilder,
717 axis_x: Scalar,
718 circles: &[Option<EdgeId>],
719 stations: &[Station],
720 index: usize,
721) -> GeomResult<FaceId> {
722 let count = stations.len();
723 let next = (index + 1) % count;
724 let here = stations[index];
725 let there = stations[next];
726 let surface = builder.add_surface(Surface::Plane(Plane {
727 frame: frame_at(axis_x, here.height),
728 }));
729
730 let (outer, inner) = if here.radius > there.radius {
733 (index, next)
734 } else {
735 (next, index)
736 };
737 let outer_loop = circle_loop(
738 builder,
739 slot(circles, outer)?,
740 stations[outer].radius,
741 Orientation::Forward,
742 );
743 let inner_loop = circle_loop(
744 builder,
745 slot(circles, inner)?,
746 stations[inner].radius,
747 Orientation::Reversed,
748 );
749 let orientation = if here.radius > there.radius {
750 Orientation::Forward
751 } else {
752 Orientation::Reversed
753 };
754 Ok(builder.topology_mut().add_face(Face {
755 surface: Some(surface),
756 bounds: vec![
757 FaceBound {
758 loop_id: outer_loop,
759 orientation: Orientation::Forward,
760 outer: true,
761 },
762 FaceBound {
763 loop_id: inner_loop,
764 orientation: Orientation::Forward,
765 outer: false,
766 },
767 ],
768 orientation,
769 }))
770}
771fn torus_wall(
777 builder: &mut ExactBRepBuilder,
778 axis_x: Scalar,
779 circles: &[Option<EdgeId>],
780 vertices: &[Option<VertexId>],
781 stations: &[Station],
782 index: usize,
783) -> GeomResult<FaceId> {
784 let count = stations.len();
785 let next = (index + 1) % count;
786 let here = stations[index];
787 let there = stations[next];
788
789 let arc = crate::extrude_arc::arc_geometry(
790 Point2::new(here.radius, here.height),
791 Point2::new(there.radius, there.height),
792 here.bulge,
793 )?;
794 let (centre_radius, centre_height) = (arc.centre.x, arc.centre.y);
795
796 if arc.radius >= centre_radius.abs() - Scalar::EPSILON {
800 return Err(unsupported(
801 "revolved arc whose tube reaches the axis (spindle torus)",
802 ));
803 }
804 if centre_radius <= 0.0 {
805 return Err(unsupported(
806 "revolved arc centred on the far side of the axis",
807 ));
808 }
809
810 let surface = builder.add_surface(Surface::Torus(Torus {
811 frame: frame_at(axis_x, centre_height),
812 major_radius: centre_radius,
813 minor_radius: arc.radius,
814 }));
815
816 let angle_of =
818 |station: Station| (station.height - centre_height).atan2(station.radius - centre_radius);
819 let start = angle_of(here);
820 let end = start + arc.sweep;
823
824 let seam_frame = Frame3 {
830 origin: Point3::new(axis_x + centre_radius, centre_height, 0.0),
831 x: Vec3::X,
832 y: Vec3::Y,
833 z: Vec3::Z,
834 };
835 let seam_curve = builder.add_curve3(Curve3::Circle(Circle3 {
836 frame: seam_frame,
837 radius: arc.radius,
838 }));
839 let seam = builder.topology_mut().add_edge(Edge {
840 start: slot(vertices, index)?,
841 end: slot(vertices, next)?,
842 curve: Some(seam_curve),
843 });
844 builder.set_edge_interval(seam, Interval::new(start, end));
845
846 let loop_id = seam_loop_between(
847 builder,
848 Some(slot(circles, index)?),
849 Some(slot(circles, next)?),
850 seam,
851 start,
852 end,
853 );
854 Ok(add_face(builder, surface, loop_id, Orientation::Forward))
855}
856pub(crate) fn frame_at(axis_x: Scalar, height: Scalar) -> Frame3 {
873 Frame3 {
874 origin: Point3::new(axis_x, height, 0.0),
875 x: Vec3::X,
876 y: Vec3::NEG_Z,
877 z: Vec3::Y,
878 }
879}
880
881fn circle_loop(
883 builder: &mut ExactBRepBuilder,
884 edge: EdgeId,
885 radius: Scalar,
886 orientation: Orientation,
887) -> LoopId {
888 let pcurve = builder.add_curve2(Curve2::Circle(Circle2 {
889 frame: Frame2 {
890 origin: Vec2::ZERO,
891 x: Vec2::X,
892 y: Vec2::Y,
893 },
894 radius,
895 }));
896 let loop_id = builder.topology_mut().add_loop(Loop {
897 edges: vec![EdgeUse {
898 edge,
899 orientation,
900 pcurve: Some(pcurve),
901 }],
902 });
903 let interval = match orientation {
908 Orientation::Forward => Interval::new(0.0, TAU),
909 Orientation::Reversed => Interval::new(TAU, 0.0),
910 };
911 builder.set_pcurve_interval(loop_id, 0, interval);
912 loop_id
913}
914
915fn seam_loop_between(
924 builder: &mut ExactBRepBuilder,
925 lower: Option<EdgeId>,
926 upper: Option<EdgeId>,
927 seam: EdgeId,
928 from: Scalar,
929 to: Scalar,
930) -> LoopId {
931 let rise = to - from;
932 let mut uses = Vec::with_capacity(4);
933 if let Some(lower) = lower {
934 uses.push((
935 lower,
936 Orientation::Forward,
937 Line2 {
938 origin: Vec2::new(0.0, from),
939 direction: Vec2::X,
940 },
941 Interval::new(0.0, TAU),
942 ));
943 }
944 uses.push((
945 seam,
946 Orientation::Forward,
947 Line2 {
948 origin: Vec2::new(TAU, from),
949 direction: Vec2::new(0.0, rise),
950 },
951 Interval::UNIT,
952 ));
953 if let Some(upper) = upper {
954 uses.push((
955 upper,
956 Orientation::Reversed,
957 Line2 {
958 origin: Vec2::new(0.0, to),
959 direction: Vec2::X,
960 },
961 Interval::new(TAU, 0.0),
962 ));
963 }
964 uses.push((
965 seam,
966 Orientation::Reversed,
967 Line2 {
968 origin: Vec2::new(0.0, from),
969 direction: Vec2::new(0.0, rise),
970 },
971 Interval::new(1.0, 0.0),
972 ));
973 let (edges, intervals): (Vec<_>, Vec<_>) = uses
974 .into_iter()
975 .map(|(edge, orientation, line, interval)| {
976 let pcurve = builder.add_curve2(Curve2::Line(line));
977 (
978 EdgeUse {
979 edge,
980 orientation,
981 pcurve: Some(pcurve),
982 },
983 interval,
984 )
985 })
986 .unzip();
987 let loop_id = builder.topology_mut().add_loop(Loop { edges });
988 for (use_index, interval) in intervals.into_iter().enumerate() {
989 builder.set_pcurve_interval(loop_id, use_index, interval);
990 }
991 loop_id
992}
993
994fn add_face(
995 builder: &mut ExactBRepBuilder,
996 surface: axiolid_brep::SurfaceId,
997 loop_id: LoopId,
998 orientation: Orientation,
999) -> FaceId {
1000 builder.topology_mut().add_face(Face {
1001 surface: Some(surface),
1002 bounds: vec![FaceBound {
1003 loop_id,
1004 orientation: Orientation::Forward,
1005 outer: true,
1006 }],
1007 orientation,
1008 })
1009}
1010
1011fn finish(
1023 mut builder: ExactBRepBuilder,
1024 stations: &[Station],
1025 faces: Vec<(FaceId, usize, usize)>,
1026) -> GeomResult<ExactBRep> {
1027 struct Piece {
1028 label: usize,
1029 faces: Vec<FaceId>,
1030 low: Scalar,
1031 high: Scalar,
1032 }
1033 let count = stations.len();
1034 let mut label: Vec<usize> = (0..count).collect();
1035 for index in 0..count {
1036 let next = (index + 1) % count;
1037 if stations[index].on_axis || stations[next].on_axis {
1038 continue;
1039 }
1040 let (a, b) = (label[index], label[next]);
1041 for l in label.iter_mut() {
1042 if *l == b {
1043 *l = a;
1044 }
1045 }
1046 }
1047 let mut pieces: Vec<Piece> = Vec::new();
1048 for (index, station) in stations.iter().enumerate() {
1049 if station.on_axis {
1050 continue;
1051 }
1052 match pieces.iter_mut().find(|piece| piece.label == label[index]) {
1053 Some(piece) => {
1054 piece.low = piece.low.min(station.height);
1055 piece.high = piece.high.max(station.height);
1056 }
1057 None => pieces.push(Piece {
1058 label: label[index],
1059 faces: Vec::new(),
1060 low: station.height,
1061 high: station.height,
1062 }),
1063 }
1064 }
1065 for (face, index, next) in faces {
1066 let representative = if stations[index].on_axis { next } else { index };
1068 if let Some(piece) = pieces
1069 .iter_mut()
1070 .find(|piece| piece.label == label[representative])
1071 {
1072 piece.faces.push(face);
1073 for station in [index, next] {
1077 piece.low = piece.low.min(stations[station].height);
1078 piece.high = piece.high.max(stations[station].height);
1079 }
1080 }
1081 }
1082 let contains = |outer: &Piece, inner: &Piece| outer.low < inner.low && inner.high < outer.high;
1083 let depth: Vec<usize> = pieces
1084 .iter()
1085 .map(|piece| pieces.iter().filter(|other| contains(other, piece)).count())
1086 .collect();
1087 if depth.iter().any(|&d| d > 1) {
1088 return Err(unsupported(
1089 "exact revolution of a section with a solid nested inside a cavity",
1090 ));
1091 }
1092 let shells: Vec<_> = pieces
1093 .iter()
1094 .map(|piece| {
1095 builder.topology_mut().add_shell(Shell {
1096 faces: piece
1097 .faces
1098 .iter()
1099 .map(|&face| (face, Orientation::Forward))
1100 .collect(),
1101 closed: true,
1102 })
1103 })
1104 .collect();
1105 for outer in (0..pieces.len()).filter(|&piece| depth[piece] == 0) {
1106 let voids = (0..pieces.len())
1107 .filter(|&inner| depth[inner] == 1 && contains(&pieces[outer], &pieces[inner]))
1108 .map(|inner| shells[inner])
1109 .collect();
1110 builder.topology_mut().add_solid(Solid {
1111 outer: shells[outer],
1112 voids,
1113 });
1114 }
1115 builder.finish().map_err(|error| {
1116 GeomError::InvalidInput(format!("revolved solid failed validation: {error:?}"))
1117 })
1118}