Another project
0

Configure Feed

Select the types of activity you want to include in your feed.

at main 24 kB View raw
1use std::collections::{HashMap, HashSet}; 2 3use bone_types::{ 4 EdgeLabel, EdgeRole, FaceLabel, FaceRole, FeatureId, LoopIndex, Parameter, Plane3, Point2, 5 PositiveLength, SideKind, SketchEntityId, Tolerance, VertexLabel, VertexRole, 6}; 7use core::f64::consts::TAU; 8use truck_modeling::{Edge, EdgeID, FaceID, Solid, Vector3, Vertex, VertexID, Wire, builder}; 9use uom::si::length::millimeter; 10 11use crate::curve2::{Curve2, Curve2Kind}; 12use crate::extrude::{ExtrudeDirection, ExtrudeEndCondition, ExtrudeFeature, ExtrudeSense}; 13use crate::intersect::{IntersectionSet, intersect_curves}; 14 15use super::build::{SolidLabeling, assemble}; 16use super::edges::{EdgeCurve3, lift_curve2}; 17use super::profile::{ExtrudeProfile, ProfileLoop}; 18use super::{BrepError, BrepSolid, ProfileDefect, TruckGap}; 19 20const PROFILE_TOLERANCE_MM: f64 = 1.0e-6; 21const PROFILE_TOLERANCE: Tolerance = Tolerance::new(PROFILE_TOLERANCE_MM); 22const AREA_TOLERANCE_MM2: f64 = 1.0e-9; 23const EXTRUDE_MIN_DEPTH_MM: f64 = 1.0e-6; 24const MAX_ARC_SEGMENTS: u32 = 64; 25const MAX_SUBARC_RAD: f64 = TAU / 3.0; 26const AREA_SAMPLES: u32 = 32; 27 28pub fn evaluate_extrude( 29 extrude: FeatureId, 30 profile: &ExtrudeProfile, 31 feature: &ExtrudeFeature, 32) -> Result<BrepSolid, BrepError> { 33 let plan = sweep_plan(feature)?; 34 let resolved = resolve_profile(profile)?; 35 let parent_curves = parent_curves(profile); 36 let (solid, labeling) = build_solid( 37 profile.plane(), 38 &resolved, 39 plan, 40 extrude, 41 closed_curves(profile), 42 &parent_curves, 43 )?; 44 assemble(solid, &labeling) 45} 46 47fn parent_curves(profile: &ExtrudeProfile) -> HashMap<SketchEntityId, Curve2Kind> { 48 profile 49 .loops() 50 .iter() 51 .flat_map(|profile_loop| match profile_loop { 52 ProfileLoop::Closed { 53 curve, 54 curve_entity, 55 } => vec![(*curve_entity, *curve)], 56 ProfileLoop::Open(edges) => edges 57 .iter() 58 .map(|edge| (edge.curve_entity(), edge.curve())) 59 .collect(), 60 }) 61 .collect() 62} 63 64fn closed_curves(profile: &ExtrudeProfile) -> HashSet<SketchEntityId> { 65 profile 66 .loops() 67 .iter() 68 .filter_map(|profile_loop| match profile_loop { 69 ProfileLoop::Closed { curve_entity, .. } => Some(*curve_entity), 70 ProfileLoop::Open(_) => None, 71 }) 72 .collect() 73} 74 75fn invalid(reason: ProfileDefect) -> BrepError { 76 BrepError::InvalidProfile { reason } 77} 78 79#[derive(Copy, Clone)] 80struct SweepPlan { 81 depth_mm: f64, 82 base_offset_mm: f64, 83} 84 85fn sweep_plan(feature: &ExtrudeFeature) -> Result<SweepPlan, BrepError> { 86 if feature.draft.is_some() { 87 return Err(unsupported(TruckGap::Draft)); 88 } 89 if feature.thin_wall.is_some() { 90 return Err(unsupported(TruckGap::ThinWall)); 91 } 92 match feature.direction { 93 ExtrudeDirection::Normal { 94 sense: ExtrudeSense::Forward, 95 } => {} 96 ExtrudeDirection::Normal { 97 sense: ExtrudeSense::Reverse, 98 } => return Err(unsupported(TruckGap::ReverseNormal)), 99 ExtrudeDirection::AlongAxis(_) => return Err(unsupported(TruckGap::AxisDirection)), 100 ExtrudeDirection::BetweenReferences { .. } => { 101 return Err(unsupported(TruckGap::ReferenceDirection)); 102 } 103 } 104 match feature.end_condition { 105 ExtrudeEndCondition::Blind { depth } => Ok(SweepPlan { 106 depth_mm: depth_mm(depth)?, 107 base_offset_mm: 0.0, 108 }), 109 ExtrudeEndCondition::MidPlane { depth } => { 110 let total = depth_mm(depth)?; 111 Ok(SweepPlan { 112 depth_mm: total, 113 base_offset_mm: -total / 2.0, 114 }) 115 } 116 ExtrudeEndCondition::ThroughAll => Err(unsupported(TruckGap::ThroughAll)), 117 ExtrudeEndCondition::UpToNext => Err(unsupported(TruckGap::UpToNext)), 118 ExtrudeEndCondition::UpToVertex { .. } => Err(unsupported(TruckGap::UpToVertex)), 119 ExtrudeEndCondition::UpToSurface { .. } => Err(unsupported(TruckGap::UpToSurface)), 120 ExtrudeEndCondition::OffsetFromSurface { .. } => { 121 Err(unsupported(TruckGap::OffsetFromSurface)) 122 } 123 ExtrudeEndCondition::UpToBody { .. } => Err(unsupported(TruckGap::UpToBody)), 124 } 125} 126 127fn unsupported(detail: TruckGap) -> BrepError { 128 BrepError::TruckUnsupported { detail } 129} 130 131fn depth_mm(depth: PositiveLength) -> Result<f64, BrepError> { 132 let value = depth.get().get::<millimeter>(); 133 if value <= EXTRUDE_MIN_DEPTH_MM { 134 Err(BrepError::EmptyExtrudeDepth) 135 } else { 136 Ok(value) 137 } 138} 139 140#[derive(Copy, Clone)] 141struct Segment { 142 curve: Curve2Kind, 143 curve_entity: SketchEntityId, 144 corner: Option<SketchEntityId>, 145} 146 147struct LoopInfo { 148 segments: Vec<Segment>, 149 polygon: Vec<(f64, f64)>, 150 area: f64, 151} 152 153struct ResolvedLoop { 154 segments: Vec<Segment>, 155 reversed: bool, 156 loop_index: LoopIndex, 157} 158 159fn resolve_profile(profile: &ExtrudeProfile) -> Result<Vec<ResolvedLoop>, BrepError> { 160 let loops = profile.loops(); 161 if loops.is_empty() { 162 return Err(invalid(ProfileDefect::ZeroArea)); 163 } 164 let infos: Vec<LoopInfo> = loops.iter().map(analyze_loop).collect::<Result<_, _>>()?; 165 166 let outer = (0..infos.len()) 167 .max_by(|&a, &b| { 168 infos[a] 169 .area 170 .abs() 171 .total_cmp(&infos[b].area.abs()) 172 .then_with(|| min_entity(&infos[b].segments).cmp(&min_entity(&infos[a].segments))) 173 }) 174 .unwrap_or(0); 175 176 validate_arrangement(&infos, outer)?; 177 178 let mut holes: Vec<usize> = (0..infos.len()).filter(|&i| i != outer).collect(); 179 holes.sort_by_key(|&i| min_entity(&infos[i].segments)); 180 181 let outer_resolved = ResolvedLoop { 182 segments: infos[outer].segments.clone(), 183 reversed: infos[outer].area < 0.0, 184 loop_index: LoopIndex::OUTER, 185 }; 186 let hole_resolved = holes.iter().enumerate().map(|(rank, &index)| ResolvedLoop { 187 segments: infos[index].segments.clone(), 188 reversed: infos[index].area > 0.0, 189 loop_index: LoopIndex::new(u16::try_from(rank + 1).unwrap_or(u16::MAX)), 190 }); 191 Ok(core::iter::once(outer_resolved) 192 .chain(hole_resolved) 193 .collect()) 194} 195 196fn analyze_loop(profile_loop: &ProfileLoop) -> Result<LoopInfo, BrepError> { 197 let segments = match profile_loop { 198 ProfileLoop::Closed { 199 curve, 200 curve_entity, 201 } => { 202 if !curve_closed(*curve) { 203 return Err(invalid(ProfileDefect::OpenLoop)); 204 } 205 vec![Segment { 206 curve: *curve, 207 curve_entity: *curve_entity, 208 corner: None, 209 }] 210 } 211 ProfileLoop::Open(edges) => { 212 if edges.is_empty() || edges.iter().any(|edge| curve_closed(edge.curve())) { 213 return Err(invalid(ProfileDefect::OpenLoop)); 214 } 215 let segments: Vec<Segment> = edges 216 .iter() 217 .map(|edge| Segment { 218 curve: edge.curve(), 219 curve_entity: edge.curve_entity(), 220 corner: Some(edge.corner()), 221 }) 222 .collect(); 223 if !loop_closed(&segments) { 224 return Err(invalid(ProfileDefect::OpenLoop)); 225 } 226 if self_intersects(&segments) { 227 return Err(invalid(ProfileDefect::SelfIntersectingLoop)); 228 } 229 segments 230 } 231 }; 232 let polygon = loop_polygon(&segments); 233 let area = signed_area(&polygon); 234 if area.abs() < AREA_TOLERANCE_MM2 { 235 return Err(invalid(ProfileDefect::ZeroArea)); 236 } 237 Ok(LoopInfo { 238 segments, 239 polygon, 240 area, 241 }) 242} 243 244fn validate_arrangement(infos: &[LoopInfo], outer: usize) -> Result<(), BrepError> { 245 let outer_info = &infos[outer]; 246 let holes: Vec<usize> = (0..infos.len()).filter(|&i| i != outer).collect(); 247 248 holes.iter().try_for_each(|&i| { 249 let contained = !loops_cross(&infos[i].segments, &outer_info.segments) 250 && any_point_inside(&infos[i], outer_info); 251 contained 252 .then_some(()) 253 .ok_or(invalid(ProfileDefect::UncontainedLoop)) 254 })?; 255 256 holes.iter().enumerate().try_for_each(|(rank, &a)| { 257 holes[rank + 1..].iter().try_for_each(|&b| { 258 let overlaps = loops_cross(&infos[a].segments, &infos[b].segments) 259 || any_point_inside(&infos[a], &infos[b]) 260 || any_point_inside(&infos[b], &infos[a]); 261 (!overlaps) 262 .then_some(()) 263 .ok_or(invalid(ProfileDefect::OverlappingLoops)) 264 }) 265 }) 266} 267 268fn any_point_inside(inner: &LoopInfo, container: &LoopInfo) -> bool { 269 inner 270 .polygon 271 .iter() 272 .any(|&point| point_in_polygon(point, &container.polygon)) 273} 274 275fn loops_cross(a: &[Segment], b: &[Segment]) -> bool { 276 a.iter().any(|sa| { 277 b.iter().any(|sb| { 278 !matches!( 279 intersect_curves(&sa.curve, &sb.curve, PROFILE_TOLERANCE), 280 IntersectionSet::Empty 281 ) 282 }) 283 }) 284} 285 286fn self_intersects(segments: &[Segment]) -> bool { 287 let count = segments.len(); 288 (0..count).any(|i| { 289 let last = if i == 0 { count - 1 } else { count }; 290 ((i + 2)..last).any(|j| { 291 !matches!( 292 intersect_curves(&segments[i].curve, &segments[j].curve, PROFILE_TOLERANCE), 293 IntersectionSet::Empty 294 ) 295 }) 296 }) 297} 298 299fn curve_closed(curve: Curve2Kind) -> bool { 300 let start = curve.evaluate(Parameter::new(0.0)); 301 let end = curve.evaluate(Parameter::new(1.0)); 302 (end - start).norm_mm() < PROFILE_TOLERANCE_MM 303} 304 305fn min_entity(segments: &[Segment]) -> Option<SketchEntityId> { 306 segments.iter().map(|seg| seg.curve_entity).min() 307} 308 309fn loop_closed(segments: &[Segment]) -> bool { 310 let count = segments.len(); 311 (0..count).all(|index| { 312 let end = segments[index].curve.evaluate(Parameter::new(1.0)); 313 let next = segments[(index + 1) % count] 314 .curve 315 .evaluate(Parameter::new(0.0)); 316 (end - next).norm_mm() < PROFILE_TOLERANCE_MM 317 }) 318} 319 320fn loop_polygon(segments: &[Segment]) -> Vec<(f64, f64)> { 321 segments 322 .iter() 323 .flat_map(|seg| { 324 let curve = seg.curve; 325 let samples = samples_for(curve); 326 (0..samples).map(move |i| { 327 let t = f64::from(i) / f64::from(samples); 328 curve.evaluate(Parameter::new(t)).coords_mm() 329 }) 330 }) 331 .collect() 332} 333 334fn signed_area(points: &[(f64, f64)]) -> f64 { 335 let count = points.len(); 336 points 337 .iter() 338 .enumerate() 339 .map(|(index, &(x0, y0))| { 340 let (x1, y1) = points[(index + 1) % count]; 341 x0 * y1 - x1 * y0 342 }) 343 .sum::<f64>() 344 / 2.0 345} 346 347fn point_in_polygon(point: (f64, f64), polygon: &[(f64, f64)]) -> bool { 348 let (px, py) = point; 349 let count = polygon.len(); 350 (0..count).fold(false, |inside, index| { 351 let (xi, yi) = polygon[index]; 352 let (xj, yj) = polygon[(index + count - 1) % count]; 353 let crosses = (yi > py) != (yj > py) && px < (xj - xi) * (py - yi) / (yj - yi) + xi; 354 inside ^ crosses 355 }) 356} 357 358fn samples_for(curve: Curve2Kind) -> u32 { 359 match curve { 360 Curve2Kind::Line(_) => 1, 361 Curve2Kind::Arc(_) | Curve2Kind::Circle(_) => AREA_SAMPLES, 362 } 363} 364 365struct Step { 366 start: Point2, 367 transit: Option<Point2>, 368 curve_entity: SketchEntityId, 369 vertex_entry: (SideKind, SketchEntityId), 370} 371 372fn loop_steps(segments: &[Segment], reversed: bool) -> Vec<Step> { 373 let count = segments.len(); 374 let order: Vec<usize> = if reversed { 375 (0..count).rev().collect() 376 } else { 377 (0..count).collect() 378 }; 379 order 380 .into_iter() 381 .flat_map(|index| { 382 let segment = segments[index]; 383 let first_entry = if reversed { 384 junction_entry(segments, (index + 1) % count) 385 } else { 386 junction_entry(segments, index) 387 }; 388 sub_edges(segment.curve, reversed) 389 .into_iter() 390 .enumerate() 391 .map(move |(position, sub)| Step { 392 start: sub.0, 393 transit: sub.2, 394 curve_entity: segment.curve_entity, 395 vertex_entry: if position == 0 { 396 first_entry 397 } else { 398 (SideKind::Seam, segment.curve_entity) 399 }, 400 }) 401 }) 402 .collect() 403} 404 405fn junction_entry(segments: &[Segment], index: usize) -> (SideKind, SketchEntityId) { 406 let segment = segments[index]; 407 segment 408 .corner 409 .map_or((SideKind::Seam, segment.curve_entity), |corner| { 410 (SideKind::Corner, corner) 411 }) 412} 413 414fn sub_edges(curve: Curve2Kind, reversed: bool) -> Vec<(Point2, Point2, Option<Point2>)> { 415 match curve { 416 Curve2Kind::Line(line) => { 417 let (start, end) = if reversed { 418 (line.end(), line.start()) 419 } else { 420 (line.start(), line.end()) 421 }; 422 vec![(start, end, None)] 423 } 424 Curve2Kind::Arc(arc) => sub_arcs(curve, arc.sweep_rad().abs(), reversed), 425 Curve2Kind::Circle(_) => sub_arcs(curve, TAU, reversed), 426 } 427} 428 429fn sub_arcs( 430 curve: Curve2Kind, 431 sweep_abs_rad: f64, 432 reversed: bool, 433) -> Vec<(Point2, Point2, Option<Point2>)> { 434 let segments = arc_segments(sweep_abs_rad); 435 let knots: Vec<f64> = (0..=segments) 436 .map(|i| { 437 let forward = f64::from(i) / f64::from(segments); 438 if reversed { 1.0 - forward } else { forward } 439 }) 440 .collect(); 441 knots 442 .windows(2) 443 .map(|pair| { 444 let (t0, t1) = (pair[0], pair[1]); 445 let start = curve.evaluate(Parameter::new(t0)); 446 let end = curve.evaluate(Parameter::new(t1)); 447 let transit = curve.evaluate(Parameter::new(f64::midpoint(t0, t1))); 448 (start, end, Some(transit)) 449 }) 450 .collect() 451} 452 453fn arc_segments(sweep_abs_rad: f64) -> u32 { 454 (1..=MAX_ARC_SEGMENTS) 455 .find(|&segments| sweep_abs_rad / f64::from(segments) <= MAX_SUBARC_RAD) 456 .unwrap_or(MAX_ARC_SEGMENTS) 457} 458 459#[derive(Default)] 460struct Bookkeeping { 461 edge_entry: HashMap<EdgeID, (LoopIndex, SketchEntityId)>, 462 vertex_entry: HashMap<VertexID, (SideKind, SketchEntityId)>, 463} 464 465fn build_solid( 466 plane: Plane3, 467 resolved: &[ResolvedLoop], 468 plan: SweepPlan, 469 extrude: FeatureId, 470 closed: HashSet<SketchEntityId>, 471 parent_curves: &HashMap<SketchEntityId, Curve2Kind>, 472) -> Result<(Solid, SolidLabeling), BrepError> { 473 let mut book = Bookkeeping::default(); 474 let wires: Vec<Wire> = resolved 475 .iter() 476 .map(|resolved_loop| { 477 let steps = loop_steps(&resolved_loop.segments, resolved_loop.reversed); 478 build_loop( 479 plane, 480 plan.base_offset_mm, 481 &steps, 482 resolved_loop.loop_index, 483 &mut book, 484 ) 485 }) 486 .collect(); 487 488 let face = builder::try_attach_plane(&wires) 489 .map_err(|_| invalid(ProfileDefect::SelfIntersectingLoop))?; 490 let (nx, ny, nz) = plane.normal().components(); 491 let sweep = Vector3::new(nx * plan.depth_mm, ny * plan.depth_mm, nz * plan.depth_mm); 492 let solid = builder::tsweep(&face, sweep); 493 let labeling = label_solid(&solid, &book, extrude, closed, plane, plan, parent_curves); 494 Ok((solid, labeling)) 495} 496 497fn build_loop( 498 plane: Plane3, 499 offset_mm: f64, 500 steps: &[Step], 501 loop_index: LoopIndex, 502 book: &mut Bookkeeping, 503) -> Wire { 504 let count = steps.len(); 505 let vertices: Vec<Vertex> = steps 506 .iter() 507 .map(|step| builder::vertex(lift(plane, offset_mm, step.start))) 508 .collect(); 509 vertices.iter().zip(steps).for_each(|(vertex, step)| { 510 book.vertex_entry.insert(vertex.id(), step.vertex_entry); 511 }); 512 let edges: Vec<Edge> = (0..count) 513 .map(|index| { 514 let from = &vertices[index]; 515 let to = &vertices[(index + 1) % count]; 516 let step = &steps[index]; 517 let edge = match step.transit { 518 None => builder::line(from, to), 519 Some(transit) => builder::circle_arc(from, to, lift(plane, offset_mm, transit)), 520 }; 521 book.edge_entry 522 .insert(edge.id(), (loop_index, step.curve_entity)); 523 edge 524 }) 525 .collect(); 526 edges.into() 527} 528 529fn lift(plane: Plane3, offset_mm: f64, point: Point2) -> truck_modeling::Point3 { 530 let (u, v) = point.coords_mm(); 531 let (wx, wy, wz) = plane.point_at_local(u, v, offset_mm).coords_mm(); 532 truck_modeling::Point3::new(wx, wy, wz) 533} 534 535struct Labels { 536 faces: HashMap<FaceID, FaceLabel>, 537 edges: HashMap<EdgeID, EdgeLabel>, 538 vertices: HashMap<VertexID, VertexLabel>, 539 edge_curves: HashMap<EdgeID, EdgeCurve3>, 540 feature: FeatureId, 541 closed: HashSet<SketchEntityId>, 542} 543 544impl Labels { 545 fn new(feature: FeatureId, closed: HashSet<SketchEntityId>) -> Self { 546 Self { 547 faces: HashMap::new(), 548 edges: HashMap::new(), 549 vertices: HashMap::new(), 550 edge_curves: HashMap::new(), 551 feature, 552 closed, 553 } 554 } 555 556 fn face(&mut self, id: FaceID, role: FaceRole) { 557 self.faces.insert( 558 id, 559 FaceLabel { 560 feature: self.feature, 561 role, 562 }, 563 ); 564 } 565 566 fn edge(&mut self, id: EdgeID, role: EdgeRole) { 567 self.edges.insert( 568 id, 569 EdgeLabel { 570 feature: self.feature, 571 role, 572 }, 573 ); 574 } 575 576 fn vertex(&mut self, id: VertexID, role: VertexRole) { 577 self.vertices.insert( 578 id, 579 VertexLabel { 580 feature: self.feature, 581 role, 582 }, 583 ); 584 } 585 586 fn edge_curve(&mut self, id: EdgeID, curve: EdgeCurve3) { 587 self.edge_curves.insert(id, curve); 588 } 589 590 fn into_labeling(self) -> SolidLabeling { 591 SolidLabeling { 592 faces: self.faces, 593 edges: self.edges, 594 vertices: self.vertices, 595 closed_curves: self.closed, 596 edge_curves: self.edge_curves, 597 } 598 } 599} 600 601#[derive(Copy, Clone)] 602struct SideFace { 603 id: FaceID, 604 profile_edge: EdgeID, 605 front: VertexID, 606 back: VertexID, 607} 608 609fn label_solid( 610 solid: &Solid, 611 book: &Bookkeeping, 612 extrude: FeatureId, 613 closed: HashSet<SketchEntityId>, 614 plane: Plane3, 615 plan: SweepPlan, 616 parent_curves: &HashMap<SketchEntityId, Curve2Kind>, 617) -> SolidLabeling { 618 let mut labels = solid 619 .boundaries() 620 .iter() 621 .flat_map(|shell| shell.face_iter()) 622 .fold(Labels::new(extrude, closed), |mut labels, face| { 623 let face_edges: Vec<(EdgeID, VertexID, VertexID)> = face 624 .boundaries() 625 .iter() 626 .flat_map(|wire| { 627 wire.edge_iter() 628 .map(|edge| (edge.id(), edge.front().id(), edge.back().id())) 629 }) 630 .collect(); 631 let profile: Vec<(EdgeID, VertexID, VertexID)> = face_edges 632 .iter() 633 .copied() 634 .filter(|(edge_id, _, _)| book.edge_entry.contains_key(edge_id)) 635 .collect(); 636 637 match profile.first() { 638 None => labels.face(face.id(), FaceRole::EndCap), 639 Some(_) if profile.len() == face_edges.len() => { 640 labels.face(face.id(), FaceRole::StartCap); 641 } 642 Some(&(profile_edge, front, back)) => label_side_face( 643 &mut labels, 644 SideFace { 645 id: face.id(), 646 profile_edge, 647 front, 648 back, 649 }, 650 &face_edges, 651 book, 652 ), 653 } 654 labels 655 }); 656 record_cap_curves(&mut labels, plane, plan, parent_curves); 657 labels.into_labeling() 658} 659 660fn record_cap_curves( 661 labels: &mut Labels, 662 plane: Plane3, 663 plan: SweepPlan, 664 parent_curves: &HashMap<SketchEntityId, Curve2Kind>, 665) { 666 let entries: Vec<(EdgeID, EdgeRole)> = labels 667 .edges 668 .iter() 669 .map(|(id, label)| (*id, label.role)) 670 .collect(); 671 entries.into_iter().for_each(|(edge_id, role)| { 672 let (offset_mm, curve_entity) = match role { 673 EdgeRole::StartCapEdge { from } => (plan.base_offset_mm, from), 674 EdgeRole::EndCapEdge { from } => (plan.base_offset_mm + plan.depth_mm, from), 675 _ => return, 676 }; 677 let Some(parent) = parent_curves.get(&curve_entity) else { 678 return; 679 }; 680 let Some(lifted) = lift_curve2(*parent, plane, offset_mm, PROFILE_TOLERANCE) else { 681 return; 682 }; 683 labels.edge_curve(edge_id, lifted); 684 }); 685} 686 687fn label_side_face( 688 labels: &mut Labels, 689 side: SideFace, 690 face_edges: &[(EdgeID, VertexID, VertexID)], 691 book: &Bookkeeping, 692) { 693 let (loop_index, curve_entity) = book.edge_entry[&side.profile_edge]; 694 let (front_kind, front_entity) = book.vertex_entry[&side.front]; 695 let (back_kind, back_entity) = book.vertex_entry[&side.back]; 696 697 labels.face( 698 side.id, 699 FaceRole::Side { 700 loop_index, 701 from: curve_entity, 702 }, 703 ); 704 labels.edge( 705 side.profile_edge, 706 EdgeRole::StartCapEdge { from: curve_entity }, 707 ); 708 labels.vertex( 709 side.front, 710 VertexRole::StartCapVertex { 711 from: front_entity, 712 side: front_kind, 713 }, 714 ); 715 labels.vertex( 716 side.back, 717 VertexRole::StartCapVertex { 718 from: back_entity, 719 side: back_kind, 720 }, 721 ); 722 723 face_edges 724 .iter() 725 .filter(|(edge_id, _, _)| *edge_id != side.profile_edge) 726 .for_each(|&(edge_id, edge_front, edge_back)| { 727 let touches_front = edge_front == side.front || edge_back == side.front; 728 let touches_back = edge_front == side.back || edge_back == side.back; 729 match (touches_front, touches_back) { 730 (true, false) => { 731 labels.edge( 732 edge_id, 733 EdgeRole::SideEdge { 734 from: front_entity, 735 side: front_kind, 736 }, 737 ); 738 let top = if edge_front == side.front { 739 edge_back 740 } else { 741 edge_front 742 }; 743 labels.vertex( 744 top, 745 VertexRole::EndCapVertex { 746 from: front_entity, 747 side: front_kind, 748 }, 749 ); 750 } 751 (false, true) => { 752 labels.edge( 753 edge_id, 754 EdgeRole::SideEdge { 755 from: back_entity, 756 side: back_kind, 757 }, 758 ); 759 let top = if edge_front == side.back { 760 edge_back 761 } else { 762 edge_front 763 }; 764 labels.vertex( 765 top, 766 VertexRole::EndCapVertex { 767 from: back_entity, 768 side: back_kind, 769 }, 770 ); 771 } 772 _ => labels.edge(edge_id, EdgeRole::EndCapEdge { from: curve_entity }), 773 } 774 }); 775}