Another project
0

Configure Feed

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

feat(render): brep face/edge/vertex picks use PickId

Lewis: May this revision serve well! <lu5a@proton.me>

author
Lewis
date (May 30, 2026, 11:45 PM +0300) commit 412575ad parent 03a451ab change-id rqynwpom
+279 -16
+1 -2
crates/bone-app/src/main.rs
··· 479 479 } 480 480 481 481 fn handle_viewport_click(state: &mut RenderState, cursor: PhysicalPosition<f64>, additive: bool) { 482 - let picked = pick_at(state, cursor); 483 - let item = picked.map(selection::picked_to_item); 482 + let item = pick_at(state, cursor).and_then(selection::picked_to_item); 484 483 state.selection = std::mem::take(&mut state.selection).picked(item, additive); 485 484 if additive || !state.mode.is_sketch() { 486 485 return;
+5 -4
crates/bone-app/src/selection.rs
··· 44 44 } 45 45 46 46 #[must_use] 47 - pub fn picked_to_item(picked: PickedItem) -> SketchItemId { 47 + pub fn picked_to_item(picked: PickedItem) -> Option<SketchItemId> { 48 48 match picked { 49 49 PickedItem::Point(id) 50 50 | PickedItem::Line(id) 51 51 | PickedItem::Arc(id) 52 - | PickedItem::Circle(id) => SketchItemId::Entity(id), 53 - PickedItem::Relation(id) => SketchItemId::Relation(id), 54 - PickedItem::Dimension(id) => SketchItemId::Dimension(id), 52 + | PickedItem::Circle(id) => Some(SketchItemId::Entity(id)), 53 + PickedItem::Relation(id) => Some(SketchItemId::Relation(id)), 54 + PickedItem::Dimension(id) => Some(SketchItemId::Dimension(id)), 55 + PickedItem::BrepFace(_) | PickedItem::BrepEdge(_) | PickedItem::BrepVertex(_) => None, 55 56 } 56 57 } 57 58
+273 -10
crates/bone-render/src/pick.rs
··· 1 - use bone_types::{SketchDimensionId, SketchEntityId, SketchRelationId}; 1 + use bone_types::{ 2 + BrepEdgeId, BrepFaceId, BrepVertexId, SketchDimensionId, SketchEntityId, SketchRelationId, 3 + }; 2 4 use slotmap::Key; 3 5 use std::collections::HashMap; 4 6 use std::collections::hash_map::Entry; ··· 16 18 Circle = 3, 17 19 Relation = 4, 18 20 Dimension = 5, 21 + BrepFace = 6, 22 + BrepEdge = 7, 23 + BrepVertex = 8, 19 24 } 20 25 21 26 impl EntityKindTag { ··· 33 38 3 => Some(Self::Circle), 34 39 4 => Some(Self::Relation), 35 40 5 => Some(Self::Dimension), 41 + 6 => Some(Self::BrepFace), 42 + 7 => Some(Self::BrepEdge), 43 + 8 => Some(Self::BrepVertex), 36 44 _ => None, 37 45 } 38 46 } 47 + 48 + const fn pick_priority(self) -> PickPriority { 49 + PickPriority(match self { 50 + Self::Point => 0, 51 + Self::Line => 1, 52 + Self::Arc => 2, 53 + Self::Circle => 3, 54 + Self::Relation => 4, 55 + Self::Dimension => 5, 56 + Self::BrepVertex => 6, 57 + Self::BrepEdge => 7, 58 + Self::BrepFace => 8, 59 + }) 60 + } 39 61 } 40 62 41 63 impl core::fmt::Display for EntityKindTag { ··· 47 69 Self::Circle => "circle", 48 70 Self::Relation => "relation", 49 71 Self::Dimension => "dimension", 72 + Self::BrepFace => "brep_face", 73 + Self::BrepEdge => "brep_edge", 74 + Self::BrepVertex => "brep_vertex", 50 75 }; 51 76 f.write_str(name) 52 77 } 53 78 } 54 79 80 + #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] 81 + struct PickPriority(u8); 82 + 55 83 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] 56 84 pub enum PickedItem { 57 85 Point(SketchEntityId), ··· 60 88 Circle(SketchEntityId), 61 89 Relation(SketchRelationId), 62 90 Dimension(SketchDimensionId), 91 + BrepFace(BrepFaceId), 92 + BrepEdge(BrepEdgeId), 93 + BrepVertex(BrepVertexId), 63 94 } 64 95 65 96 impl PickedItem { ··· 72 103 Self::Circle(_) => EntityKindTag::Circle, 73 104 Self::Relation(_) => EntityKindTag::Relation, 74 105 Self::Dimension(_) => EntityKindTag::Dimension, 106 + Self::BrepFace(_) => EntityKindTag::BrepFace, 107 + Self::BrepEdge(_) => EntityKindTag::BrepEdge, 108 + Self::BrepVertex(_) => EntityKindTag::BrepVertex, 75 109 } 76 110 } 77 111 } ··· 141 175 Self::from_keyed(EntityKindTag::Dimension, id) 142 176 } 143 177 178 + pub fn brep_face(id: BrepFaceId) -> Result<Self, PickIdError> { 179 + Self::from_keyed(EntityKindTag::BrepFace, id) 180 + } 181 + 182 + pub fn brep_edge(id: BrepEdgeId) -> Result<Self, PickIdError> { 183 + Self::from_keyed(EntityKindTag::BrepEdge, id) 184 + } 185 + 186 + pub fn brep_vertex(id: BrepVertexId) -> Result<Self, PickIdError> { 187 + Self::from_keyed(EntityKindTag::BrepVertex, id) 188 + } 189 + 144 190 #[must_use] 145 191 pub fn unpack(self, index: &PickIndex) -> Option<PickedItem> { 146 192 let tag = self.tag()?; ··· 160 206 .get(&slot) 161 207 .copied() 162 208 .map(PickedItem::Dimension), 209 + EntityKindTag::BrepFace => index.faces.get(&slot).copied().map(PickedItem::BrepFace), 210 + EntityKindTag::BrepEdge => index.edges.get(&slot).copied().map(PickedItem::BrepEdge), 211 + EntityKindTag::BrepVertex => index 212 + .vertices 213 + .get(&slot) 214 + .copied() 215 + .map(PickedItem::BrepVertex), 163 216 } 164 217 } 165 218 ··· 508 561 candidates 509 562 .iter() 510 563 .copied() 511 - .filter(|(_, _, pid)| *pid != PickId::NONE) 512 - .min_by_key(|(px, py, pid)| { 564 + .filter_map(|(px, py, pid)| pid.tag().map(|tag| (px, py, pid, tag))) 565 + .min_by_key(|(px, py, _, tag)| { 513 566 let dx = px - qx; 514 567 let dy = py - qy; 515 - let sq_dist = dx * dx + dy * dy; 516 - let tag_priority = pid.tag().map_or(u8::MAX, EntityKindTag::bits); 517 - (sq_dist, tag_priority) 568 + (dx * dx + dy * dy, tag.pick_priority()) 518 569 }) 519 - .map(|(_, _, pid)| pid) 570 + .map(|(_, _, pid, _)| pid) 520 571 } 521 572 522 573 impl core::fmt::Debug for Picker<'_> { ··· 532 583 entities: HashMap<u32, (SketchEntityId, EntityKindTag)>, 533 584 relations: HashMap<u32, SketchRelationId>, 534 585 dimensions: HashMap<u32, SketchDimensionId>, 586 + faces: HashMap<u32, BrepFaceId>, 587 + edges: HashMap<u32, BrepEdgeId>, 588 + vertices: HashMap<u32, BrepVertexId>, 535 589 } 536 590 537 591 impl PickIndex { ··· 549 603 entities: try_collect_keyed(entities, |(id, _)| slot_index(*id))?, 550 604 relations: try_collect_keyed(relations, |id| slot_index(*id))?, 551 605 dimensions: try_collect_keyed(dimensions, |id| slot_index(*id))?, 606 + ..Self::default() 607 + }) 608 + } 609 + 610 + pub fn build_solid<FI, EI, VI>(faces: FI, edges: EI, vertices: VI) -> Result<Self, PickIdError> 611 + where 612 + FI: IntoIterator<Item = BrepFaceId>, 613 + EI: IntoIterator<Item = BrepEdgeId>, 614 + VI: IntoIterator<Item = BrepVertexId>, 615 + { 616 + Ok(Self { 617 + faces: try_collect_keyed(faces, |id| slot_index(*id))?, 618 + edges: try_collect_keyed(edges, |id| slot_index(*id))?, 619 + vertices: try_collect_keyed(vertices, |id| slot_index(*id))?, 620 + ..Self::default() 552 621 }) 553 622 } 554 623 ··· 706 775 PickedItem::Dimension(k) => { 707 776 slots.2.remove(k); 708 777 } 778 + PickedItem::BrepFace(_) | PickedItem::BrepEdge(_) | PickedItem::BrepVertex(_) => { 779 + unreachable!("forget receives only sketch items") 780 + } 709 781 } 710 782 } 711 783 712 784 #[test] 713 785 fn tag_bits_roundtrip() { 714 - (0u8..6).for_each(|bits| { 786 + (0u8..9).for_each(|bits| { 715 787 let Some(tag) = EntityKindTag::from_bits(bits) else { 716 788 panic!("tag {bits} should decode"); 717 789 }; ··· 721 793 722 794 #[test] 723 795 fn invalid_tag_bits_return_none() { 724 - (6u8..16).for_each(|bits| { 796 + (9u8..16).for_each(|bits| { 725 797 assert!(EntityKindTag::from_bits(bits).is_none()); 726 798 }); 727 799 } ··· 874 946 } 875 947 876 948 #[test] 877 - fn invalid_tag_bits_unpack_to_none(bits in 6u8..16, idx in 0u32..=PickId::INDEX_MASK) { 949 + fn invalid_tag_bits_unpack_to_none(bits in 9u8..16, idx in 0u32..=PickId::INDEX_MASK) { 878 950 let raw = (u32::from(bits) << PickId::TAG_SHIFT) | (idx & PickId::INDEX_MASK); 879 951 let pid = PickId::from_raw(raw); 880 952 prop_assert!(pid.unpack(&PickIndex::default()).is_none()); 881 953 } 882 954 } 883 955 956 + #[derive(Copy, Clone, Debug)] 957 + enum BrepKind { 958 + Face, 959 + Edge, 960 + Vertex, 961 + } 962 + 963 + fn arb_brep_kind() -> impl Strategy<Value = BrepKind> { 964 + prop_oneof![ 965 + Just(BrepKind::Face), 966 + Just(BrepKind::Edge), 967 + Just(BrepKind::Vertex), 968 + ] 969 + } 970 + 971 + type BrepSlots = ( 972 + SlotMap<BrepFaceId, ()>, 973 + SlotMap<BrepEdgeId, ()>, 974 + SlotMap<BrepVertexId, ()>, 975 + ); 976 + 977 + fn populate_brep( 978 + n: usize, 979 + ) -> ( 980 + Vec<BrepFaceId>, 981 + Vec<BrepEdgeId>, 982 + Vec<BrepVertexId>, 983 + BrepSlots, 984 + ) { 985 + let mut faces = SlotMap::with_key(); 986 + let mut edges = SlotMap::with_key(); 987 + let mut vertices = SlotMap::with_key(); 988 + let f: Vec<_> = (0..n).map(|_| faces.insert(())).collect(); 989 + let e: Vec<_> = (0..n).map(|_| edges.insert(())).collect(); 990 + let v: Vec<_> = (0..n).map(|_| vertices.insert(())).collect(); 991 + (f, e, v, (faces, edges, vertices)) 992 + } 993 + 994 + fn build_brep_index(slots: &BrepSlots) -> PickIndex { 995 + ok(PickIndex::build_solid( 996 + slots.0.keys(), 997 + slots.1.keys(), 998 + slots.2.keys(), 999 + )) 1000 + } 1001 + 1002 + fn build_brep_case( 1003 + kind: BrepKind, 1004 + f: &[BrepFaceId], 1005 + e: &[BrepEdgeId], 1006 + v: &[BrepVertexId], 1007 + target: usize, 1008 + ) -> (PickId, PickedItem) { 1009 + match kind { 1010 + BrepKind::Face => ( 1011 + ok(PickId::brep_face(f[target])), 1012 + PickedItem::BrepFace(f[target]), 1013 + ), 1014 + BrepKind::Edge => ( 1015 + ok(PickId::brep_edge(e[target])), 1016 + PickedItem::BrepEdge(e[target]), 1017 + ), 1018 + BrepKind::Vertex => ( 1019 + ok(PickId::brep_vertex(v[target])), 1020 + PickedItem::BrepVertex(v[target]), 1021 + ), 1022 + } 1023 + } 1024 + 1025 + #[test] 1026 + fn brep_tag_and_index_split_cleanly() { 1027 + let mut faces: SlotMap<BrepFaceId, ()> = SlotMap::with_key(); 1028 + let k = faces.insert(()); 1029 + let pid = ok(PickId::brep_face(k)); 1030 + assert_eq!(pid.tag(), Some(EntityKindTag::BrepFace)); 1031 + assert_eq!(pid.raw() & PickId::TAG_MASK, 6u32 << PickId::TAG_SHIFT); 1032 + assert_eq!(pid.index(), pid.raw() & PickId::INDEX_MASK); 1033 + assert!(format!("{pid}").starts_with("brep_face#")); 1034 + } 1035 + 1036 + #[test] 1037 + fn brep_families_resolve_by_tag_at_shared_slot() { 1038 + let (f, e, v, slots) = populate_brep(1); 1039 + let index = build_brep_index(&slots); 1040 + assert_eq!( 1041 + ok(PickId::brep_face(f[0])).unpack(&index), 1042 + Some(PickedItem::BrepFace(f[0])) 1043 + ); 1044 + assert_eq!( 1045 + ok(PickId::brep_edge(e[0])).unpack(&index), 1046 + Some(PickedItem::BrepEdge(e[0])) 1047 + ); 1048 + assert_eq!( 1049 + ok(PickId::brep_vertex(v[0])).unpack(&index), 1050 + Some(PickedItem::BrepVertex(v[0])) 1051 + ); 1052 + } 1053 + 1054 + #[test] 1055 + fn brep_slot_overflow_is_rejected_by_build_solid() { 1056 + let bad_slot: u32 = PickId::INDEX_MASK + 1; 1057 + let key = BrepFaceId::from(KeyData::from_ffi((1u64 << 32) | u64::from(bad_slot))); 1058 + let result = PickIndex::build_solid( 1059 + [key], 1060 + std::iter::empty::<BrepEdgeId>(), 1061 + std::iter::empty::<BrepVertexId>(), 1062 + ); 1063 + assert!(matches!(result, Err(PickIdError::SlotIndexOverflow { .. }))); 1064 + } 1065 + 1066 + proptest! { 1067 + #[test] 1068 + fn brep_unpack_roundtrip( 1069 + kind in arb_brep_kind(), 1070 + n in 1usize..32, 1071 + target in 0usize..32, 1072 + ) { 1073 + let target = target % n; 1074 + let (f, e, v, slots) = populate_brep(n); 1075 + let (pid, expected) = build_brep_case(kind, &f, &e, &v, target); 1076 + let index = build_brep_index(&slots); 1077 + prop_assert_eq!(pid.unpack(&index), Some(expected)); 1078 + prop_assert_eq!(pid.tag(), Some(expected.tag())); 1079 + } 1080 + 1081 + #[test] 1082 + fn brep_removed_slot_unpacks_to_none( 1083 + kind in arb_brep_kind(), 1084 + n in 1usize..32, 1085 + target in 0usize..32, 1086 + ) { 1087 + let target = target % n; 1088 + let (f, e, v, mut slots) = populate_brep(n); 1089 + let (pid, _) = build_brep_case(kind, &f, &e, &v, target); 1090 + match kind { 1091 + BrepKind::Face => slots.0.remove(f[target]), 1092 + BrepKind::Edge => slots.1.remove(e[target]), 1093 + BrepKind::Vertex => slots.2.remove(v[target]), 1094 + }; 1095 + let index = build_brep_index(&slots); 1096 + prop_assert!(pid.unpack(&index).is_none()); 1097 + } 1098 + } 1099 + 1100 + #[test] 1101 + fn pick_priority_prefers_smaller_brep_entities() { 1102 + assert!(EntityKindTag::BrepVertex.pick_priority() < EntityKindTag::BrepEdge.pick_priority()); 1103 + assert!(EntityKindTag::BrepEdge.pick_priority() < EntityKindTag::BrepFace.pick_priority()); 1104 + } 1105 + 1106 + #[test] 1107 + fn nearest_pick_prefers_vertex_over_face_at_equal_distance() { 1108 + let mut faces: SlotMap<BrepFaceId, ()> = SlotMap::with_key(); 1109 + let mut verts: SlotMap<BrepVertexId, ()> = SlotMap::with_key(); 1110 + let face_pid = ok(PickId::brep_face(faces.insert(()))); 1111 + let vert_pid = ok(PickId::brep_vertex(verts.insert(()))); 1112 + let q = query_at(10, 10); 1113 + assert_eq!( 1114 + nearest_pick(q, &[(9, 10, face_pid), (11, 10, vert_pid)]), 1115 + Some(vert_pid) 1116 + ); 1117 + assert_eq!( 1118 + nearest_pick(q, &[(9, 10, vert_pid), (11, 10, face_pid)]), 1119 + Some(vert_pid) 1120 + ); 1121 + } 1122 + 1123 + #[test] 1124 + fn nearest_pick_prefers_edge_over_face_at_equal_distance() { 1125 + let mut faces: SlotMap<BrepFaceId, ()> = SlotMap::with_key(); 1126 + let mut edges: SlotMap<BrepEdgeId, ()> = SlotMap::with_key(); 1127 + let face_pid = ok(PickId::brep_face(faces.insert(()))); 1128 + let edge_pid = ok(PickId::brep_edge(edges.insert(()))); 1129 + let q = query_at(10, 10); 1130 + assert_eq!( 1131 + nearest_pick(q, &[(9, 10, face_pid), (11, 10, edge_pid)]), 1132 + Some(edge_pid) 1133 + ); 1134 + } 1135 + 884 1136 fn pid_for(slot: u32, tag: EntityKindTag) -> PickId { 885 1137 debug_assert!(slot <= PickId::INDEX_MASK); 886 1138 PickId::from_raw((u32::from(tag.bits()) << PickId::TAG_SHIFT) | slot) ··· 956 1208 (22, 22, real), 957 1209 (25, 25, PickId::NONE), 958 1210 ]; 1211 + assert_eq!(nearest_pick(q, &region), Some(real)); 1212 + } 1213 + 1214 + #[test] 1215 + fn nearest_pick_ignores_invalid_tag() { 1216 + let q = query_at(10, 10); 1217 + let invalid = PickId::from_raw((10u32 << PickId::TAG_SHIFT) | 3); 1218 + assert_eq!(invalid.tag(), None); 1219 + assert_ne!(invalid, PickId::NONE); 1220 + let real = pid_for(7, EntityKindTag::Line); 1221 + let region = vec![(10, 10, invalid), (12, 11, real)]; 959 1222 assert_eq!(nearest_pick(q, &region), Some(real)); 960 1223 } 961 1224