1use crate::sweep_topology::{curve_to_plane_parameters, parameter_line};
2use crate::topology::{
3 BrepSolid, CoedgeRecord, EdgeRecord, FaceRecord, LoopRecord, ShellRecord, VertexRecord,
4};
5use crate::{make_line, make_plane, make_revolution, NurbsCurve, NurbsSurface, Vec3, Vec4};
6
7const TOLERANCE: f64 = 1e-5;
8
9fn next_id(counter: &mut u64) -> u64 {
10 let id = *counter;
11 *counter += 1;
12 id
13}
14
15fn rotate_point(point: Vec3, origin: Vec3, axis: Vec3, angle: f64) -> Vec3 {
16 let vector = point.sub(origin);
17 let cosine = angle.cos();
18 let sine = angle.sin();
19 origin
20 .add(vector.scale(cosine))
21 .add(axis.cross(vector).scale(sine))
22 .add(axis.scale(axis.dot(vector) * (1.0 - cosine)))
23}
24
25fn rotate_curve(
26 curve: &NurbsCurve,
27 origin: Vec3,
28 axis: Vec3,
29 angle: f64,
30) -> Result<NurbsCurve, String> {
31 NurbsCurve::new(
32 curve.degree,
33 curve.knots.clone(),
34 curve
35 .control_points
36 .iter()
37 .map(|point| {
38 let euclidean = Vec3::new(point.x / point.w, point.y / point.w, point.z / point.w);
39 Vec4::from_point(rotate_point(euclidean, origin, axis, angle), point.w)
40 })
41 .collect(),
42 )
43}
44
45fn signed_profile_area(
46 curves: &[NurbsCurve],
47 origin: Vec3,
48 radial: Vec3,
49 axis: Vec3,
50) -> Result<f64, String> {
51 let mut area = 0.0;
52 for curve in curves {
53 let [start, end] = curve.domain()?;
54 let mut previous = curve.evaluate(start)?;
55 for index in 1..=64 {
56 let point = curve.evaluate(start + (end - start) * index as f64 / 64.0)?;
57 let a = previous.sub(origin);
58 let b = point.sub(origin);
59 area += 0.5 * (a.dot(radial) * b.dot(axis) - b.dot(radial) * a.dot(axis));
60 previous = point;
61 }
62 }
63 Ok(area)
64}
65
66struct RevolveProfile {
67 curves: Vec<NurbsCurve>,
68 points: Vec<Vec3>,
69 axis_points: Vec<bool>,
70 axis_curves: Vec<bool>,
71 radial_curves: Vec<bool>,
79 radial: Vec3,
80 axis: Vec3,
81 input_indices: Vec<usize>,
87}
88
89fn prepare_profile(
90 input: &[NurbsCurve],
91 axis_point: Vec3,
92 axis_direction: Vec3,
93) -> Result<RevolveProfile, String> {
94 if input.len() < 2 {
95 return Err("profile needs at least 2 curves".into());
96 }
97 let axis = axis_direction.normalized()?;
98 let mut curves: Vec<NurbsCurve> = input
99 .iter()
100 .map(|curve| {
101 NurbsCurve::new(
102 curve.degree,
103 curve.knots.clone(),
104 curve.control_points.clone(),
105 )
106 })
107 .collect::<Result<_, _>>()?;
108 let closure_points = |curves: &[NurbsCurve]| -> Result<Vec<Vec3>, String> {
109 let mut points = Vec::with_capacity(curves.len());
110 for index in 0..curves.len() {
111 let [start, end] = curves[index].domain()?;
112 let next_start = curves[(index + 1) % curves.len()].domain()?[0];
113 let point = curves[index].evaluate(start)?;
114 if curves[index]
115 .evaluate(end)?
116 .sub(curves[(index + 1) % curves.len()].evaluate(next_start)?)
117 .length()
118 > TOLERANCE
119 {
120 return Err(format!("profile not closed at curve {index}"));
121 }
122 points.push(point);
123 }
124 Ok(points)
125 };
126 let mut points = closure_points(&curves)?;
127
128 let radial_distance = |point: Vec3| {
129 let delta = point.sub(axis_point);
130 delta.sub(axis.scale(delta.dot(axis))).length()
131 };
132 let mut radial = None;
133 'outer: for curve in &curves {
134 let [start, end] = curve.domain()?;
135 for index in 0..=24 {
136 let point = curve.evaluate(start + (end - start) * index as f64 / 24.0)?;
137 let delta = point.sub(axis_point);
138 let candidate = delta.sub(axis.scale(delta.dot(axis)));
139 if candidate.length() > TOLERANCE * 100.0 {
140 radial = Some(candidate.normalized()?);
141 break 'outer;
142 }
143 }
144 }
145 let radial = radial.ok_or_else(|| "revolveSolid: profile touches the axis".to_string())?;
146 let curve_on_axis = |curve: &NurbsCurve| -> Result<bool, String> {
147 let [start, end] = curve.domain()?;
148 for index in 0..=8 {
149 if radial_distance(curve.evaluate(start + (end - start) * index as f64 / 8.0)?)
150 > TOLERANCE * 100.0
151 {
152 return Ok(false);
153 }
154 }
155 Ok(true)
156 };
157 for curve in &curves {
158 let [start, end] = curve.domain()?;
159 let on_axis = curve_on_axis(curve)?;
160 for index in 0..=24 {
161 let point = curve.evaluate(start + (end - start) * index as f64 / 24.0)?;
162 let delta = point.sub(axis_point);
163 let in_plane_radial = delta.dot(radial);
164 let off_plane = delta
165 .sub(axis.scale(delta.dot(axis)))
166 .sub(radial.scale(in_plane_radial))
167 .length();
168 if off_plane > TOLERANCE * 100.0 {
169 return Err("revolveSolid: profile not in a half-plane containing the axis".into());
170 }
171 if in_plane_radial < -TOLERANCE * 100.0 {
172 return Err("revolveSolid: profile crosses the revolve axis".into());
173 }
174 if in_plane_radial < TOLERANCE * 100.0 && index != 0 && index != 24 && !on_axis {
175 return Err(
176 "revolveSolid: profile may only touch the axis at boundary vertices or axis edges"
177 .into(),
178 );
179 }
180 }
181 }
182 let mut input_indices: Vec<usize> = (0..curves.len()).collect();
183 if signed_profile_area(&curves, axis_point, radial, axis)? < 0.0 {
184 curves = curves
185 .iter()
186 .rev()
187 .map(NurbsCurve::reversed)
188 .collect::<Result<_, _>>()?;
189 points = closure_points(&curves)?;
190 input_indices.reverse();
191 }
192 let mut axis_points: Vec<bool> = points
193 .iter()
194 .map(|point| radial_distance(*point) <= TOLERANCE * 100.0)
195 .collect();
196 if axis_points.iter().any(|flag| *flag) {
197 let snapped: Vec<Vec3> = points
198 .iter()
199 .enumerate()
200 .map(|(index, point)| {
201 if axis_points[index] {
202 axis_point.add(axis.scale(point.sub(axis_point).dot(axis)))
203 } else {
204 *point
205 }
206 })
207 .collect();
208 for index in 0..curves.len() {
209 let next = (index + 1) % curves.len();
210 if !axis_points[index] && !axis_points[next] {
211 continue;
212 }
213 let mut control_points = curves[index].control_points.clone();
214 if axis_points[index] {
215 let weight = control_points[0].w;
216 control_points[0] = Vec4::from_point(snapped[index], weight);
217 }
218 if axis_points[next] {
219 let last = control_points.len() - 1;
220 let weight = control_points[last].w;
221 control_points[last] = Vec4::from_point(snapped[next], weight);
222 }
223 curves[index] = NurbsCurve::new(
224 curves[index].degree,
225 curves[index].knots.clone(),
226 control_points,
227 )?;
228 }
229 points = closure_points(&curves)?;
230 axis_points = points
231 .iter()
232 .map(|point| radial_distance(*point) <= TOLERANCE * 100.0)
233 .collect();
234 }
235 let axis_curves = curves
236 .iter()
237 .map(curve_on_axis)
238 .collect::<Result<Vec<_>, _>>()?;
239 let radial_curves = curves
244 .iter()
245 .enumerate()
246 .map(|(index, curve)| {
247 if axis_curves[index] {
248 return Ok(false);
249 }
250 Ok(is_straight_radial(curve, axis)?)
251 })
252 .collect::<Result<Vec<_>, String>>()?;
253 Ok(RevolveProfile {
254 curves,
255 points,
256 axis_points,
257 axis_curves,
258 radial_curves,
259 radial,
260 axis,
261 input_indices,
262 })
263}
264
265fn is_straight_radial(curve: &NurbsCurve, axis: Vec3) -> Result<bool, String> {
270 if curve.degree != 1 || curve.control_points.len() != 2 {
271 return Ok(false);
272 }
273 let [start, end] = curve.domain()?;
274 let p0 = curve.evaluate(start)?;
275 let p1 = curve.evaluate(end)?;
276 let direction = p1.sub(p0);
277 let length = direction.length();
278 if length <= TOLERANCE {
279 return Ok(false);
280 }
281 Ok(direction.dot(axis).abs() <= 1e-3 * length)
282}
283
284fn junction_curve(
285 index: usize,
286 surfaces: &[Option<NurbsSurface>],
287 points: &[Vec3],
288) -> Result<NurbsCurve, String> {
289 if let Some(surface) = &surfaces[index] {
290 return surface.iso_curve_v(surface.knots_v[surface.degree_v]);
291 }
292 let previous = (index + surfaces.len() - 1) % surfaces.len();
293 if let Some(surface) = &surfaces[previous] {
294 return surface.iso_curve_v(surface.knots_v[surface.knots_v.len() - 1 - surface.degree_v]);
295 }
296 make_line(points[index], points[index])
297}
298
299#[allow(clippy::too_many_arguments)]
307fn radial_plane_face(
308 profile: &RevolveProfile,
309 index: usize,
310 count: usize,
311 origin: Vec3,
312 revolution: &NurbsSurface,
313 arc_curves: &[NurbsCurve],
314 end_curves: &[NurbsCurve],
315 arc_ids: &[u64],
316 start_edge_ids: &[u64],
317 end_edge_ids: &[u64],
318 counter: &mut u64,
319 name: Option<String>,
320) -> Result<FaceRecord, String> {
321 let axis = profile.axis;
322 let a_start = profile.points[index].sub(origin).dot(axis);
323 let a_end = profile.points[(index + 1) % count].sub(origin).dot(axis);
324 let plane_center = origin.add(axis.scale(0.5 * (a_start + a_end)));
325 let e1 = profile.radial;
326 let e2 = axis.cross(e1);
327
328 let next = (index + 1) % count;
329 let boundary: [(NurbsCurve, u64, bool); 4] = [
330 (arc_curves[index].clone(), arc_ids[index], true),
331 (end_curves[index].clone(), end_edge_ids[index], true),
332 (arc_curves[next].clone(), arc_ids[next], false),
333 (profile.curves[index].clone(), start_edge_ids[index], false),
334 ];
335
336 let mut min1 = f64::INFINITY;
337 let mut max1 = f64::NEG_INFINITY;
338 let mut min2 = f64::INFINITY;
339 let mut max2 = f64::NEG_INFINITY;
340 for (curve, _, _) in &boundary {
341 let [s, e] = curve.domain()?;
342 for k in 0..=16 {
343 let point = curve.evaluate(s + (e - s) * k as f64 / 16.0)?;
344 let d = point.sub(plane_center);
345 let c1 = d.dot(e1);
346 let c2 = d.dot(e2);
347 min1 = min1.min(c1);
348 max1 = max1.max(c1);
349 min2 = min2.min(c2);
350 max2 = max2.max(c2);
351 }
352 }
353 let padding = (max1 - min1).max(max2 - min2) * 0.05 + 1e-6;
354 let width = max1 - min1 + 2.0 * padding;
355 let height = max2 - min2 + 2.0 * padding;
356 let corner = plane_center
357 .add(e1.scale(min1 - padding))
358 .add(e2.scale(min2 - padding));
359 let plane = make_plane(corner, e1, e2, width, height)?;
360
361 let [ru0, ru1] = revolution.domain_u()?;
365 let [rv0, rv1] = revolution.domain_v()?;
366 let rev_normal = revolution.normal((ru0 + ru1) * 0.5, (rv0 + rv1) * 0.5)?;
367 let same_sense = rev_normal.dot(e1.cross(e2)) >= 0.0;
368
369 let mut coedges = Vec::with_capacity(4);
370 for (curve, edge_id, forward) in boundary {
371 let mapped = curve_to_plane_parameters(&curve, corner, e1, e2)?;
372 let pcurve = if forward { mapped } else { mapped.reversed()? };
373 coedges.push(CoedgeRecord {
374 id: next_id(counter),
375 edge_id,
376 forward,
377 pcurve,
378 });
379 }
380 let loop_id = next_id(counter);
381 Ok(FaceRecord {
382 id: next_id(counter),
383 surface: plane,
384 same_sense,
385 loops: vec![LoopRecord {
386 id: loop_id,
387 coedges,
388 }],
389 name,
390 })
391}
392
393pub fn revolve_profile_brep(
394 input: &[NurbsCurve],
395 axis_point: Vec3,
396 axis_direction: Vec3,
397 angle: f64,
398) -> Result<BrepSolid, String> {
399 revolve_profile_brep_named(input, axis_point, axis_direction, angle, &[], &[])
400}
401
402pub fn revolve_profile_brep_named(
408 input: &[NurbsCurve],
409 axis_point: Vec3,
410 axis_direction: Vec3,
411 angle: f64,
412 side_names: &[Option<String>],
413 cap_names: &[Option<String>],
414) -> Result<BrepSolid, String> {
415 if angle <= 1e-9 || angle > std::f64::consts::TAU + 1e-9 {
416 return Err("revolveSolid: angle must be in (0, 2*PI]".into());
417 }
418 if !side_names.is_empty() && side_names.len() != input.len() {
419 return Err(format!(
420 "revolveSolid: {} side names for {} profile curves",
421 side_names.len(),
422 input.len()
423 ));
424 }
425 let full_turn = angle > std::f64::consts::TAU - 1e-9;
426 let profile = prepare_profile(input, axis_point, axis_direction)?;
427 if full_turn {
428 revolve_full(profile, axis_point, side_names)
429 } else {
430 revolve_partial(profile, axis_point, angle, side_names, cap_names)
431 }
432}
433
434fn side_name(
435 profile: &RevolveProfile,
436 curve_index: usize,
437 side_names: &[Option<String>],
438) -> Option<String> {
439 profile
440 .input_indices
441 .get(curve_index)
442 .and_then(|input_index| side_names.get(*input_index))
443 .cloned()
444 .flatten()
445}
446
447fn revolve_full(
448 profile: RevolveProfile,
449 origin: Vec3,
450 side_names: &[Option<String>],
451) -> Result<BrepSolid, String> {
452 let count = profile.curves.len();
453 let surfaces: Vec<Option<NurbsSurface>> = profile
454 .curves
455 .iter()
456 .enumerate()
457 .map(|(index, curve)| {
458 if profile.axis_curves[index] {
459 Ok(None)
460 } else {
461 make_revolution(origin, profile.axis, curve, std::f64::consts::TAU).map(Some)
462 }
463 })
464 .collect::<Result<_, String>>()?;
465 let vertices: Vec<VertexRecord> = profile
466 .points
467 .iter()
468 .enumerate()
469 .map(|(index, point)| VertexRecord {
470 id: index as u64 + 1,
471 point: *point,
472 })
473 .collect();
474 let mut counter = 100_u64;
475 let mut edges = Vec::new();
476 let mut circle_ids = Vec::with_capacity(count);
477 for index in 0..count {
478 let curve = junction_curve(index, &surfaces, &profile.points)?;
479 let [start, end] = curve.domain()?;
480 let id = next_id(&mut counter);
481 circle_ids.push(id);
482 edges.push(EdgeRecord {
483 id,
484 curve,
485 t0: start,
486 t1: end,
487 start_vertex_id: index as u64 + 1,
488 end_vertex_id: index as u64 + 1,
489 degenerate: profile.axis_points[index],
490 name: None,
491 });
492 }
493 let mut faces = Vec::new();
494 for index in 0..count {
495 if profile.axis_curves[index] {
496 continue;
497 }
498 let curve = profile.curves[index].clone();
499 let [start, end] = curve.domain()?;
500 let seam_id = next_id(&mut counter);
501 edges.push(EdgeRecord {
502 id: seam_id,
503 curve,
504 t0: start,
505 t1: end,
506 start_vertex_id: index as u64 + 1,
507 end_vertex_id: ((index + 1) % count) as u64 + 1,
508 degenerate: false,
509 name: None,
510 });
511 let coedges = vec![
512 CoedgeRecord {
513 id: next_id(&mut counter),
514 edge_id: circle_ids[index],
515 forward: true,
516 pcurve: parameter_line(0.0, start, 1.0, start)?,
517 },
518 CoedgeRecord {
519 id: next_id(&mut counter),
520 edge_id: seam_id,
521 forward: true,
522 pcurve: parameter_line(1.0, start, 1.0, end)?,
523 },
524 CoedgeRecord {
525 id: next_id(&mut counter),
526 edge_id: circle_ids[(index + 1) % count],
527 forward: false,
528 pcurve: parameter_line(1.0, end, 0.0, end)?,
529 },
530 CoedgeRecord {
531 id: next_id(&mut counter),
532 edge_id: seam_id,
533 forward: false,
534 pcurve: parameter_line(0.0, end, 0.0, start)?,
535 },
536 ];
537 let loop_id = next_id(&mut counter);
538 faces.push(FaceRecord {
539 id: next_id(&mut counter),
540 surface: surfaces[index].clone().unwrap(),
541 same_sense: true,
542 loops: vec![LoopRecord {
543 id: loop_id,
544 coedges,
545 }],
546 name: side_name(&profile, index, side_names),
547 });
548 }
549 let solid = BrepSolid {
550 id: next_id(&mut counter),
551 vertices,
552 edges,
553 shells: vec![ShellRecord {
554 id: next_id(&mut counter),
555 faces,
556 }],
557 genus: if profile.axis_points.iter().any(|flag| *flag) {
558 0
559 } else {
560 1
561 },
562 };
563 let issues = solid.validate();
564 if issues.is_empty() {
565 Ok(solid)
566 } else {
567 Err(format!("Rust full revolution is invalid: {issues:?}"))
568 }
569}
570
571fn revolve_partial(
572 profile: RevolveProfile,
573 origin: Vec3,
574 angle: f64,
575 side_names: &[Option<String>],
576 cap_names: &[Option<String>],
577) -> Result<BrepSolid, String> {
578 let count = profile.curves.len();
579 let end_curves: Vec<NurbsCurve> = profile
580 .curves
581 .iter()
582 .map(|curve| rotate_curve(curve, origin, profile.axis, angle))
583 .collect::<Result<_, _>>()?;
584 let surfaces: Vec<Option<NurbsSurface>> = profile
585 .curves
586 .iter()
587 .enumerate()
588 .map(|(index, curve)| {
589 if profile.axis_curves[index] {
590 Ok(None)
591 } else {
592 make_revolution(origin, profile.axis, curve, angle).map(Some)
593 }
594 })
595 .collect::<Result<_, String>>()?;
596 let mut vertices = Vec::new();
597 let mut start_vertex_ids = Vec::with_capacity(count);
598 let mut end_vertex_ids = Vec::with_capacity(count);
599 let mut counter = 100_u64;
600 for point in &profile.points {
601 let id = next_id(&mut counter);
602 start_vertex_ids.push(id);
603 vertices.push(VertexRecord { id, point: *point });
604 }
605 for index in 0..count {
606 if profile.axis_points[index] {
607 end_vertex_ids.push(start_vertex_ids[index]);
608 } else {
609 let id = next_id(&mut counter);
610 end_vertex_ids.push(id);
611 vertices.push(VertexRecord {
612 id,
613 point: rotate_point(profile.points[index], origin, profile.axis, angle),
614 });
615 }
616 }
617
618 let mut edges = Vec::new();
619 let mut arc_ids = Vec::with_capacity(count);
620 let mut arc_curves = Vec::with_capacity(count);
621 for index in 0..count {
622 let curve = junction_curve(index, &surfaces, &profile.points)?;
623 let [start, end] = curve.domain()?;
624 let id = next_id(&mut counter);
625 arc_ids.push(id);
626 arc_curves.push(curve.clone());
627 edges.push(EdgeRecord {
628 id,
629 curve,
630 t0: start,
631 t1: end,
632 start_vertex_id: start_vertex_ids[index],
633 end_vertex_id: end_vertex_ids[index],
634 degenerate: profile.axis_points[index],
635 name: None,
636 });
637 }
638 let mut start_edge_ids = Vec::with_capacity(count);
639 let mut end_edge_ids = Vec::with_capacity(count);
640 for index in 0..count {
641 let [start, end] = profile.curves[index].domain()?;
642 let start_id = next_id(&mut counter);
643 start_edge_ids.push(start_id);
644 edges.push(EdgeRecord {
645 id: start_id,
646 curve: profile.curves[index].clone(),
647 t0: start,
648 t1: end,
649 start_vertex_id: start_vertex_ids[index],
650 end_vertex_id: start_vertex_ids[(index + 1) % count],
651 degenerate: false,
652 name: None,
653 });
654 if profile.axis_curves[index] {
655 end_edge_ids.push(start_id);
656 } else {
657 let end_id = next_id(&mut counter);
658 end_edge_ids.push(end_id);
659 edges.push(EdgeRecord {
660 id: end_id,
661 curve: end_curves[index].clone(),
662 t0: start,
663 t1: end,
664 start_vertex_id: end_vertex_ids[index],
665 end_vertex_id: end_vertex_ids[(index + 1) % count],
666 degenerate: false,
667 name: None,
668 });
669 }
670 }
671
672 let mut faces = Vec::new();
673 for index in 0..count {
674 if profile.axis_curves[index] {
675 continue;
676 }
677 if profile.radial_curves[index] {
678 faces.push(radial_plane_face(
679 &profile,
680 index,
681 count,
682 origin,
683 surfaces[index].as_ref().unwrap(),
684 &arc_curves,
685 &end_curves,
686 &arc_ids,
687 &start_edge_ids,
688 &end_edge_ids,
689 &mut counter,
690 side_name(&profile, index, side_names),
691 )?);
692 continue;
693 }
694 let [start, end] = profile.curves[index].domain()?;
695 let coedges = vec![
696 CoedgeRecord {
697 id: next_id(&mut counter),
698 edge_id: arc_ids[index],
699 forward: true,
700 pcurve: parameter_line(0.0, start, 1.0, start)?,
701 },
702 CoedgeRecord {
703 id: next_id(&mut counter),
704 edge_id: end_edge_ids[index],
705 forward: true,
706 pcurve: parameter_line(1.0, start, 1.0, end)?,
707 },
708 CoedgeRecord {
709 id: next_id(&mut counter),
710 edge_id: arc_ids[(index + 1) % count],
711 forward: false,
712 pcurve: parameter_line(1.0, end, 0.0, end)?,
713 },
714 CoedgeRecord {
715 id: next_id(&mut counter),
716 edge_id: start_edge_ids[index],
717 forward: false,
718 pcurve: parameter_line(0.0, end, 0.0, start)?,
719 },
720 ];
721 let loop_id = next_id(&mut counter);
722 faces.push(FaceRecord {
723 id: next_id(&mut counter),
724 surface: surfaces[index].clone().unwrap(),
725 same_sense: true,
726 loops: vec![LoopRecord {
727 id: loop_id,
728 coedges,
729 }],
730 name: side_name(&profile, index, side_names),
731 });
732 }
733
734 let mut min_radial = f64::INFINITY;
735 let mut max_radial = f64::NEG_INFINITY;
736 let mut min_axis = f64::INFINITY;
737 let mut max_axis = f64::NEG_INFINITY;
738 for curve in &profile.curves {
739 let [start, end] = curve.domain()?;
740 for index in 0..=24 {
741 let delta = curve
742 .evaluate(start + (end - start) * index as f64 / 24.0)?
743 .sub(origin);
744 let radial = delta.dot(profile.radial);
745 let axial = delta.dot(profile.axis);
746 min_radial = min_radial.min(radial);
747 max_radial = max_radial.max(radial);
748 min_axis = min_axis.min(axial);
749 max_axis = max_axis.max(axial);
750 }
751 }
752 let padding = (max_radial - min_radial).max(max_axis - min_axis) * 0.05 + 1e-6;
753 let width = max_radial - min_radial + 2.0 * padding;
754 let height = max_axis - min_axis + 2.0 * padding;
755 let start_origin = origin
756 .add(profile.radial.scale(min_radial - padding))
757 .add(profile.axis.scale(min_axis - padding));
758 let mut start_coedges = Vec::with_capacity(count);
759 for index in 0..count {
760 start_coedges.push(CoedgeRecord {
761 id: next_id(&mut counter),
762 edge_id: start_edge_ids[index],
763 forward: true,
764 pcurve: curve_to_plane_parameters(
765 &profile.curves[index],
766 start_origin,
767 profile.radial,
768 profile.axis,
769 )?,
770 });
771 }
772 let start_loop_id = next_id(&mut counter);
773 faces.push(FaceRecord {
774 id: next_id(&mut counter),
775 surface: make_plane(start_origin, profile.radial, profile.axis, width, height)?,
776 same_sense: true,
777 loops: vec![LoopRecord {
778 id: start_loop_id,
779 coedges: start_coedges,
780 }],
781 name: cap_names.first().cloned().flatten(),
782 });
783
784 let end_radial = rotate_point(origin.add(profile.radial), origin, profile.axis, angle)
785 .sub(origin)
786 .normalized()?;
787 let end_origin = origin
788 .add(end_radial.scale(min_radial - padding))
789 .add(profile.axis.scale(min_axis - padding));
790 let mut end_coedges = Vec::with_capacity(count);
791 for index in (0..count).rev() {
792 end_coedges.push(CoedgeRecord {
793 id: next_id(&mut counter),
794 edge_id: end_edge_ids[index],
795 forward: false,
796 pcurve: curve_to_plane_parameters(
797 &end_curves[index],
798 end_origin,
799 profile.axis,
800 end_radial,
801 )?
802 .reversed()?,
803 });
804 }
805 let end_loop_id = next_id(&mut counter);
806 faces.push(FaceRecord {
807 id: next_id(&mut counter),
808 surface: make_plane(end_origin, profile.axis, end_radial, height, width)?,
809 same_sense: true,
810 loops: vec![LoopRecord {
811 id: end_loop_id,
812 coedges: end_coedges,
813 }],
814 name: cap_names.get(1).cloned().flatten(),
815 });
816
817 let solid = BrepSolid {
818 id: next_id(&mut counter),
819 vertices,
820 edges,
821 shells: vec![ShellRecord {
822 id: next_id(&mut counter),
823 faces,
824 }],
825 genus: 0,
826 };
827 let issues = solid.validate();
828 if issues.is_empty() {
829 Ok(solid)
830 } else {
831 Err(format!("Rust partial revolution is invalid: {issues:?}"))
832 }
833}
834
835