1use std::{cell::RefCell, collections::HashMap};
5
6use geop_core_editor::{
7 registry::{Drawer, StructureItem},
8 view::{FaceMesh, PartView, RESOLUTION, ViewEdge, ViewFace, ViewVertex},
9};
10use geop_core_geometry::search::PROJECT_ITERATIONS;
11use geop_core_math::{geop_error::GeopResult, scalars::Scalar, vector::Vector3};
12use geop_core_program::reader::Reader;
13use geop_core_rasterize::Rasterizer;
14use geop_core_topology::{CoedgeId, EdgeId, Model, SolidId};
15
16use crate::{NamedModel, RefId, read_model};
17
18fn solid_of_edge(model: &Model, edge: EdgeId) -> Option<SolidId> {
20 let coedge = *model.coedges_of_edge(edge).first()?;
21 model
22 .solid_of_face(model.get_coedge(coedge).ok()?.face)
23 .ok()?
24}
25
26impl NamedModel {
27 pub fn draw(
31 &self,
32 target: &str,
33 rasterizer: &mut Rasterizer,
34 view: &mut PartView,
35 ) -> GeopResult<()> {
36 let model = self.topology();
37 let raster = rasterizer.rasterize(model, RESOLUTION)?;
38 let name = |id: RefId| self.name_of(id).unwrap_or_default().to_string();
39
40 let mut corner_of = HashMap::new();
43 let mut faces_of_edge = HashMap::new();
44 let mut faces_at_corner: HashMap<_, Vec<String>> = HashMap::new();
45 for (&id, edge) in &model.edges {
46 if let Some(solid) = solid_of_edge(model, id) {
47 corner_of.insert(edge.start_vertex, solid);
48 corner_of.insert(edge.end_vertex, solid);
49 }
50 let mut faces: Vec<String> = (model.coedges_of_edge(id).into_iter())
51 .filter_map(|c| model.get_coedge(c).ok())
52 .map(|c| name(c.face.into()))
53 .collect();
54 faces.sort();
55 faces.dedup();
56 for v in [edge.start_vertex, edge.end_vertex] {
57 let at = faces_at_corner.entry(v).or_default();
58 at.extend(faces.iter().cloned());
59 at.sort();
60 at.dedup();
61 }
62 faces_of_edge.insert(id, faces);
63 }
64 let features = self.feature_faces();
65 view.model = target.to_string();
66 view.vertices = (raster.vertices.iter())
68 .map(|(&id, p)| ViewVertex {
69 name: name(id.into()),
70 solid: corner_of.get(&id).map(|&s| name(s.into())),
71 faces: faces_at_corner.get(&id).cloned().unwrap_or_default(),
72 at: *p,
73 })
74 .collect();
75 view.edges = (raster.edges.iter())
76 .map(|(&id, polyline)| ViewEdge {
77 ends: (model.get_edge(id))
78 .map(|e| [e.start_vertex, e.end_vertex].map(|v| name(v.into())))
79 .unwrap_or_default(),
80 solid: solid_of_edge(model, id).map(|s| name(s.into())),
81 faces: faces_of_edge.get(&id).cloned().unwrap_or_default(),
82 name: name(id.into()),
83 polyline: polyline.clone(),
84 within_a_surface: within_a_surface(model, id),
85 })
86 .collect();
87 view.faces = (raster.faces.iter())
88 .map(|(&id, tris)| {
89 let face = name(id.into());
90 ViewFace {
91 feature: features.of(&face).map(str::to_string),
92 also: self.names.aliases_of(&face),
93 name: face,
94 solid: (model.solid_of_face(id).ok().flatten()).map(|s| name(s.into())),
95 mesh: FaceMesh::new(
96 tris.iter().map(|t| [t.a, t.b, t.c]).collect(),
97 (tris.iter())
98 .map(|t| t.vertex_normals.unwrap_or([t.normal; 3]))
99 .collect(),
100 ),
101 }
102 })
103 .collect();
104 view.solids = model.solids.keys().map(|&s| name(s.into())).collect();
105 Ok(())
106 }
107}
108
109#[derive(Default)]
114pub struct ModelDrawer {
115 rasterizer: RefCell<Rasterizer>,
116}
117
118impl Drawer for ModelDrawer {
119 fn draw(&self, reader: &Reader, view: &mut PartView) -> GeopResult<()> {
120 read_model(reader)?.draw("model", &mut self.rasterizer.borrow_mut(), view)
121 }
122
123 fn structure(&self, reader: &Reader) -> GeopResult<Vec<StructureItem>> {
124 let model = read_model(reader)?;
125 let solids =
126 (model.solid_names().into_iter()).map(|n| StructureItem::drawn("solid", "Solids", &n));
127 let faces = (model.sheet_face_names().into_iter())
128 .map(|n| StructureItem::drawn("face", "Faces", &n));
129 Ok(solids.chain(faces).collect())
130 }
131
132 fn kept(&self) -> bool {
134 true
135 }
136}
137
138const CREASE: f64 = 0.0175;
147const TURN: f64 = 0.25;
148
149fn within_a_surface(model: &Model, edge: EdgeId) -> bool {
156 let holds = || -> GeopResult<bool> {
157 let coedges = model.coedges_of_edge(edge);
158 let [a, b] = coedges.as_slice() else {
159 return Ok(false);
160 };
161 let curve = &model.get_edge(edge)?.curve;
162 let (t0, t1) = curve.domain();
163 let size = |coedge: CoedgeId| -> GeopResult<f64> {
164 let boxed = model
165 .get_face(model.get_coedge(coedge)?.face)?
166 .surface
167 .aabb();
168 Ok(boxed
169 .iter()
170 .map(|x| x.width().to_f64().powi(2))
171 .sum::<f64>()
172 .sqrt())
173 };
174 let across_faces = size(*a)?.max(size(*b)?);
175 let side =
178 |coedge: CoedgeId, point: Vector3, along: &Vector3| -> GeopResult<(Vector3, f64)> {
179 let coedge = model.get_coedge(coedge)?;
180 let surface = &model.get_face(coedge.face)?.surface;
181 let seed = coedge.pcurve.evaluate(coedge.pcurve.mid_parameter()?)?;
182 let (u, v) = surface.project(point, seed[0], seed[1], PROJECT_ITERATIONS)?;
183 let normal = surface.normal(u, v)?;
184 let across = normal.prod_cross(along);
185 Ok((normal, surface.normal_curvature(u, v, &across)?.to_f64()))
186 };
187 for fraction in [0.25, 0.5, 0.75] {
188 let t = Scalar::interpolate(t0, t1, Scalar::from_f64(fraction)).sharpen();
189 let point = curve.evaluate(t)?;
190 let along = curve.tangent(t)?.normalize()?;
191 let ((n, k), (m, l)) = (side(*a, point, &along)?, side(*b, point, &along)?);
192 let turned = n.prod_cross(&m).norm().to_f64();
193 if turned > CREASE {
194 return Ok(false);
195 }
196 let l = if n.prod_dot(&m).to_f64() < 0.0 { -l } else { l };
198 if (k - l).abs() * across_faces > TURN {
199 return Ok(false);
200 }
201 }
202 Ok(true)
203 };
204 holds().unwrap_or(false)
205}
206
207#[cfg(test)]
208mod tests {
209 use geop_core_editor::view::drawn;
210 use geop_core_editor::{
211 editing::{Pointer, Reach},
212 selectable::selected,
213 };
214 use geop_core_math::{primitives::Ray, scalars::Scalar, vector::Vector3};
215 use geop_core_program::{element::ElementReference, part::Part};
216
217 use crate::{
218 Body, Line, NamedModel, model,
219 testing::{given_part, unit_cube},
220 };
221
222 use super::*;
223
224 fn from_above(x: f64, y: f64) -> Pointer {
226 let v = |p: [f64; 3]| Vector3::from_array(p.map(Scalar::from_f64));
227 Pointer {
228 cycle: 0,
229 ray: Ray::try_new(v([x, y, 10.0]), v([0.0, 0.0, -1.0])).unwrap(),
230 reach: Reach::Tube {
231 radius: Scalar::from_f64(0.01),
232 },
233 }
234 }
235
236 fn accepts(kinds: &[&str]) -> Vec<String> {
237 kinds.iter().map(|k| k.to_string()).collect()
238 }
239
240 #[test]
244 fn a_pick_hits_the_smallest_element_under_the_pointer() {
245 let mut view = PartView::new();
246 unit_cube()
247 .draw("model", &mut Rasterizer::default(), &mut view)
248 .unwrap();
249 let view = view.finish();
250 assert_eq!(
251 (view.vertices.len(), view.edges.len(), view.faces.len()),
252 (8, 12, 6)
253 );
254 let all = accepts(&[drawn::VERTEX, drawn::EDGE, drawn::FACE]);
255 let hit =
256 |x, y, accepts: &[String]| view.pick(&from_above(x, y), accepts, &[]).unwrap().element;
257 assert_eq!(hit(1.0, 1.0, &all).kind, drawn::VERTEX);
258 assert_eq!(hit(0.5, 1.0, &all).kind, drawn::EDGE);
259 let top = hit(0.5, 0.5, &all);
260 assert_eq!(
261 (top.kind.as_str(), top.target.to_string().as_str()),
262 (drawn::FACE, "/:model")
263 );
264 assert_eq!(hit(1.0, 1.0, &accepts(&[drawn::FACE])).kind, drawn::FACE);
265 assert!(view.pick(&from_above(5.0, 5.0), &all, &[]).is_none());
266 }
267
268 #[test]
271 fn a_pick_selects_what_a_field_asks_for() {
272 let mut view = PartView::new();
273 unit_cube()
274 .draw("model", &mut Rasterizer::default(), &mut view)
275 .unwrap();
276 let view = view.finish();
277 let part: Part = given_part(&unit_cube()).unwrap();
278 let reader = part.current();
279 let edge = view
280 .pick(&from_above(0.5, 1.0), &accepts(&[drawn::EDGE]), &[])
281 .unwrap();
282 let lines: Vec<ElementReference<NamedModel, Line>> =
283 selected(&reader, std::slice::from_ref(&edge.element));
284 assert_eq!(lines[0].name(), edge.element.name);
285 let face = view
286 .pick(&from_above(0.5, 0.5), &accepts(&[drawn::FACE]), &[])
287 .unwrap();
288 let bodies: Vec<ElementReference<NamedModel, Body>> = selected(&reader, &[face.element]);
289 assert_eq!(bodies, [ElementReference::new(model(), "s0")]);
290 }
291
292 #[test]
296 fn faces_and_solids() {
297 use geop_core_editor::{registry::Registry, selectable};
298
299 use crate::Solid;
300 let part: Part = given_part(&unit_cube()).unwrap();
301 let registry = crate::register(Registry::default()).unwrap();
302 let view = (registry.draw(&part.current(), &Default::default(), None)).unwrap();
303 let faces = accepts(&[drawn::FACE]);
304 let hit = view.pick(&from_above(0.5, 0.5), &faces, &[]).unwrap();
305 assert_eq!(hit.element.kind, drawn::FACE);
306 assert!(hit.point[2].could_be_equal(Scalar::ONE), "{hit:?}");
307 let solid = selectable::pick::<ElementReference<NamedModel, Solid>>(
308 &view,
309 &part.current(),
310 &from_above(0.5, 0.5),
311 );
312 assert!(solid.is_some());
313 assert!(view.pick(&from_above(5.0, 5.0), &faces, &[]).is_none());
314 }
315}