1use crate::{
7 NamedModel, frame_along,
8 placed::locate,
9 selection::{Selected, Shape},
10};
11use geop_core_editor::editing::{Style, Visual};
12use geop_core_editor::selectable::Selectable;
13use geop_core_editor::view::{FramePick, drawn};
14use geop_core_geometry::{
15 nurb_curve::NurbCurve3D,
16 nurb_surface::NurbSurface3D,
17 search::{MAX_NODES, min_subdivision_size},
18 shape::Axis,
19};
20use geop_core_math::{
21 geop_error::{GeopError, GeopResult, WithContext},
22 primitives::CoordinateSystem,
23 scalars::Scalar,
24 vector::Vector3,
25 with_context,
26};
27use geop_core_program::{element::PickedElement, reader::Reader};
28use geop_core_topology::{CoedgeGeometry, EdgeId, FaceId};
29use serde::{Deserialize, Serialize};
30
31#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
38pub struct MateEntity {
39 pub on: Selected,
40 #[serde(default, skip_serializing_if = "Option::is_none")]
41 pub at: Option<Selected>,
42}
43
44impl MateEntity {
45 pub fn on(on: Selected) -> Self {
47 Self { on, at: None }
48 }
49
50 pub fn of(part: &str, on: Selected) -> Self {
52 Self::on(on.behind(part))
53 }
54
55 pub fn at(self, at: Selected) -> Self {
57 let at = at.local().behind(self.part());
58 Self {
59 at: Some(at),
60 ..self
61 }
62 }
63
64 pub fn part(&self) -> &str {
66 self.on.instance()
67 }
68
69 pub fn label(&self) -> String {
71 let on = self.on.local().picked().label();
72 match self.part() {
73 "" => on,
74 part => format!("{part}: {on}"),
75 }
76 }
77
78 pub fn shape(&self, reader: &Reader) -> GeopResult<Shape> {
82 Ok(self.on.shape_in_instance(reader)?.element)
83 }
84
85 pub fn surface(&self, reader: &Reader) -> GeopResult<Option<NurbSurface3D>> {
93 let Selected::Face(face) = self.on.local() else {
94 return Ok(None);
95 };
96 let ctx = with_context!("the surface of {}", self.label());
97 let inside = match self.part() {
98 "" => reader.clone(),
99 part => reader.placed_part(part).with_context(ctx)?,
100 };
101 let found = inside.retrieve_element(&face).with_context(ctx)?;
102 wall(found.model, found.id).map(Some).with_context(ctx)
103 }
104
105 pub fn curve(&self, reader: &Reader) -> GeopResult<Option<NurbCurve3D>> {
112 let ctx = with_context!("the curve of {}", self.label());
113 let Selected::Edge(edge) = self.on.local() else {
114 return Ok(self.shape(reader).with_context(ctx)?.curve);
115 };
116 let inside = match self.part() {
117 "" => reader.clone(),
118 part => reader.placed_part(part).with_context(ctx)?,
119 };
120 let found = inside.retrieve_element(&edge).with_context(ctx)?;
121 run(found.model, found.id).map(Some).with_context(ctx)
122 }
123
124 pub fn frame(&self, reader: &Reader) -> GeopResult<CoordinateSystem> {
143 let ctx = with_context!("the frame on {}", self.label());
144 let inside = match self.part() {
145 "" => reader.clone(),
146 part => reader.placed_part(part).with_context(ctx)?,
147 };
148 let at = self.at.as_ref().map(Selected::local);
149 frame_on(&inside, &self.on.local(), at.as_ref()).with_context(ctx)
150 }
151
152 pub fn frame_in_world(&self, reader: &Reader) -> GeopResult<CoordinateSystem> {
155 let frame = self.frame(reader)?;
156 match self.part() {
157 "" => Ok(frame),
158 part => (locate(reader, part)?.world.motion())
159 .apply_frame(&frame)
160 .with_context(with_context!("the frame on {}", self.label())),
161 }
162 }
163}
164
165fn wall(named: &NamedModel, face: FaceId) -> GeopResult<NurbSurface3D> {
171 let model = named.topology();
172 let mut wall = model.get_face(face)?.surface.clone();
173 let mut used = vec![face];
174 let mut along = None;
175 loop {
176 let mut next: Vec<FaceId> = (used.iter())
177 .flat_map(|&f| model.face_edges(f).collect::<Vec<_>>())
178 .filter_map(|edge| model.faces_of_edge(edge))
179 .flatten()
180 .filter(|f| !used.contains(f))
181 .collect();
182 next.sort();
183 next.dedup();
184 let joined = next.iter().find_map(|&f| {
185 let surface = &model.get_face(f).ok()?.surface;
186 let ways = along.map_or(vec![true, false], |along| vec![along]);
187 ways.into_iter().find_map(|way| {
188 let after = (runs_on(&wall, surface, way))
189 .then(|| wall.joined(surface, way).ok())
190 .flatten();
191 let before = || {
192 (runs_on(surface, &wall, way))
193 .then(|| surface.joined(&wall, way).ok())
194 .flatten()
195 };
196 Some((f, way, after.or_else(before)?))
197 })
198 });
199 let Some((f, way, joined)) = joined else {
200 return Ok(wall);
201 };
202 (wall, along) = (joined, Some(way));
203 used.push(f);
204 }
205}
206
207fn run(named: &NamedModel, edge: EdgeId) -> GeopResult<NurbCurve3D> {
212 let model = named.topology();
213 let first = model.get_edge(edge)?;
214 let mut run = first.curve.clone();
215 let mut ends = [first.start_vertex, first.end_vertex];
216 let mut used = vec![edge];
217 loop {
218 let mut grown = false;
219 for at_end in [true, false] {
220 let vertex = ends[usize::from(at_end)];
221 let mut next = model.edges_at_vertex(vertex);
222 next.retain(|e| !used.contains(e));
223 next.sort();
224 for e in next {
225 let found = model.get_edge(e)?;
226 let leaves = found.start_vertex == vertex;
228 let curve = match leaves == at_end {
229 true => found.curve.clone(),
230 false => found.curve.reverse(),
231 };
232 let far = if leaves {
233 found.end_vertex
234 } else {
235 found.start_vertex
236 };
237 let joined = match at_end {
238 true => tangent(&run, &curve).then(|| run.joined(&curve).ok()),
239 false => tangent(&curve, &run).then(|| curve.joined(&run).ok()),
240 };
241 if let Some(Some(joined)) = joined {
242 run = joined;
243 ends[usize::from(at_end)] = far;
244 used.push(e);
245 grown = true;
246 break;
247 }
248 }
249 }
250 if !grown || ends[0] == ends[1] {
252 return Ok(run);
253 }
254 }
255}
256
257fn tangent(curve: &NurbCurve3D, next: &NurbCurve3D) -> bool {
261 let point = |p: &geop_core_math::vector::Vector<4>| {
262 Vector3::from_array([0, 1, 2].map(|k| p[k].div(p[3]).unwrap_or(Scalar::ZERO)))
263 };
264 let (a, b) = (&curve.control_points, &next.control_points);
265 if a.len() < 2 || b.len() < 2 {
266 return false;
267 }
268 let (end, before) = (point(&a[a.len() - 1]), point(&a[a.len() - 2]));
269 let (start, after) = (point(&b[0]), point(&b[1]));
270 let (into, out) = (end.sub(&before), after.sub(&start));
271 end.could_be_equal(&start)
272 && into.prod_cross(&out).norm_sq().could_be_equal(Scalar::ZERO)
273 && into.prod_dot(&out).definitely_greater(Scalar::ZERO)
274}
275
276fn runs_on(surface: &NurbSurface3D, next: &NurbSurface3D, along_u: bool) -> bool {
281 let point = |s: &NurbSurface3D, i: usize, j: usize| {
282 let p = match along_u {
283 true => s.control_points[i * s.num_v + j],
284 false => s.control_points[j * s.num_v + i],
285 };
286 let w = p[3];
287 Vector3::from_array([0, 1, 2].map(|k| p[k].div(w).unwrap_or(Scalar::ZERO)))
288 };
289 let count = |s: &NurbSurface3D| match along_u {
290 true => (s.num_u, s.num_v),
291 false => (s.num_v, s.num_u),
292 };
293 let ((n, m), (n2, m2)) = (count(surface), count(next));
294 if m != m2 || n < 2 || n2 < 2 {
295 return false;
296 }
297 (0..m).all(|j| {
298 let (end, before) = (point(surface, n - 1, j), point(surface, n - 2, j));
299 let (start, after) = (point(next, 0, j), point(next, 1, j));
300 let (into, out) = (end.sub(&before), after.sub(&start));
301 end.could_be_equal(&start)
302 && into.prod_cross(&out).norm_sq().could_be_equal(Scalar::ZERO)
303 && into.prod_dot(&out).definitely_greater(Scalar::ZERO)
304 })
305}
306
307fn frame_on(reader: &Reader, on: &Selected, at: Option<&Selected>) -> GeopResult<CoordinateSystem> {
310 let shape = on.shape(reader)?;
311 if let Some(frame) = shape.frame {
312 return Ok(frame);
313 }
314 if let Some(arc) = &shape.arc {
315 return frame_along(arc.circle.center.sharpen(), &arc.circle.normal);
316 }
317 let anchor = at.map(|at| middle_of(reader, at)).transpose()?;
318 let near = || match anchor {
321 Some(anchor) => Ok(anchor),
322 None => middle_of(reader, on),
323 };
324 let along = |axis: &Axis| -> GeopResult<CoordinateSystem> {
325 frame_along(axis.project(&near()?).sharpen(), &axis.direction)
326 };
327 if let Some(plane) = &shape.plane {
328 let near = match (anchor, on) {
329 (Some(anchor), _) => anchor,
330 (None, Selected::Face(_)) => middle_of(reader, on)?,
331 (None, _) => *plane.origin(),
332 };
333 let off = near.sub(plane.origin()).prod_dot(plane.w());
334 let origin = near.sub(&plane.w().prod_scalar(off)).sharpen();
335 return CoordinateSystem::try_new(origin, *plane.u(), *plane.v(), *plane.w());
336 }
337 if let Some(axis) = &shape.round {
338 return along(axis);
339 }
340 if let Some(axis) = &shape.line {
341 return along(axis);
342 }
343 if let Some(point) = shape.point {
344 return Ok(CoordinateSystem::world_at(point));
345 }
346 Err(GeopError::new(
347 "it gives no frame: pick a face, an edge, a point or a datum",
348 ))
349}
350
351fn middle_of(reader: &Reader, element: &Selected) -> GeopResult<Vector3> {
357 let ctx = with_context!("the middle of {}", element.picked().label());
358 if let Selected::Face(face) = element {
359 let found = reader.retrieve_element(face).with_context(ctx)?;
360 return face_middle(found.model, found.id).with_context(ctx);
361 }
362 let shape = element.shape(reader).with_context(ctx)?;
363 if let Some(arc) = &shape.arc {
364 return Ok(arc.circle.center.sharpen());
365 }
366 if let Some(curve) = &shape.curve {
367 let t = curve.mid_parameter().with_context(ctx)?;
368 return Ok(curve.evaluate(t).with_context(ctx)?.sharpen());
369 }
370 (shape.point.map(|p| p.sharpen()))
371 .ok_or_else(|| GeopError::new("it has no middle"))
372 .with_context(ctx)
373}
374
375fn face_middle(named: &NamedModel, face: FaceId) -> GeopResult<Vector3> {
379 let model = named.topology();
380 let mut bounds: Option<[Scalar; 3]> = None;
381 for coedge in model.iterate_face_coedges(face) {
382 let spans = match model.get_coedge(coedge)?.geometry {
383 CoedgeGeometry::Edge(edge) => model
384 .get_edge(edge)?
385 .curve
386 .bounds(MAX_NODES, min_subdivision_size())?,
387 CoedgeGeometry::Vertex(vertex) => model.get_vertex(vertex)?.point.to_array(),
388 };
389 bounds = Some(match bounds {
390 Some(b) => std::array::from_fn(|k| b[k].union(spans[k])),
391 None => spans,
392 });
393 }
394 let bounds =
395 bounds.ok_or_else(|| GeopError::new(format!("face {face} has no edge to measure")))?;
396 Ok(Vector3::from_array(bounds.map(|axis| axis.midpoint())))
397}
398
399impl Selectable for MateEntity {
404 fn picked_from() -> Vec<&'static str> {
405 let mut kinds = Selected::picked_from();
406 kinds.push(drawn::FRAME);
407 kinds
408 }
409
410 fn from_picked(picked: &PickedElement) -> Option<Self> {
411 if let Some(frame) = FramePick::of(picked) {
412 let at = match &frame.at {
413 Some(at) => Some(Selected::from_picked(at)?),
414 None => None,
415 };
416 return Some(MateEntity {
417 on: Selected::from_picked(&frame.on)?,
418 at,
419 });
420 }
421 Selected::from_picked(picked).map(MateEntity::on)
422 }
423
424 fn picked(&self) -> PickedElement {
425 let on = self.on.picked();
426 match &self.at {
427 None => on,
428 Some(at) => FramePick {
429 on,
430 at: Some(at.picked()),
431 }
432 .picked(),
433 }
434 }
435
436 fn select(reader: &Reader, hit: &PickedElement) -> Option<Self> {
437 let _ = reader;
438 Self::from_picked(hit)
441 }
442
443 fn drawn(&self, reader: &Reader) -> Vec<PickedElement> {
444 let _ = reader;
445 let mut drawn = vec![drawn_of(&self.on)];
446 drawn.extend(self.at.iter().map(drawn_of));
447 drawn
448 }
449
450 fn visuals(&self, reader: &Reader) -> Vec<Visual> {
453 let Ok(frame) = self.frame_in_world(reader) else {
454 return Vec::new();
455 };
456 let shape = geop_core_editor::editing::Shape::Triad {
457 at: *frame.origin(),
458 axes: [*frame.u(), *frame.v(), *frame.w()],
459 };
460 vec![Visual::new("frame", shape, Style::Selected)]
461 }
462}
463
464fn drawn_of(element: &Selected) -> PickedElement {
466 let picked = element.picked();
467 let kind = match element {
468 Selected::Vertex(_) => drawn::VERTEX,
469 Selected::Edge(_) => drawn::EDGE,
470 Selected::Face(_) => drawn::FACE,
471 Selected::Datum(_) => drawn::DATUM,
472 Selected::SketchPoint(_) => drawn::SKETCH_POINT,
473 Selected::SketchCurve(_) => drawn::SKETCH_CURVE,
474 };
475 PickedElement {
476 kind: kind.to_string(),
477 ..picked
478 }
479}