1#[cfg(test)]
20mod draw_tests;
21pub mod fields;
22pub mod figures;
23
24use crate::{
25 Chain, Circle, Curve, Line, NamedModel, Path, Plane, Point, Profile, Round, datums::Datums,
26};
27use geop_core_editor::{
28 one_of,
29 registry::{Drawer, StructureItem},
30 selectable::SelectableElement,
31 view::{PartView, ViewCurve, ViewSketch, ViewSketchPoint, drawn},
32};
33use geop_core_geometry::{
34 nurb_curve::{NurbCurve, NurbCurve3D},
35 shape::{Arc, Axis},
36};
37use geop_core_math::{
38 geop_error::{GeopError, GeopResult, WithContext},
39 primitives::CoordinateSystem,
40 vector::Vector3,
41 with_context,
42};
43use geop_core_program::{
44 element::{ElementReference, HasElements, Kind, PickedElement, whole},
45 reader::Reader,
46 target::{Target, TargetReference},
47};
48use geop_core_sketch::{
49 CurveId, Enclosure, PointId, Shape,
50 profile::{Piece, ProfileLoop, curve_nurbs, curve_polylines},
51};
52use std::collections::BTreeSet;
53
54pub type Sketch = geop_core_sketch::Sketch;
56pub type Constraint = geop_core_sketch::Constraint;
58pub type CurveKind = geop_core_sketch::CurveKind;
60
61one_of! {
62 pub enum SketchPlane -> CoordinateSystem {
65 Face(ElementReference<NamedModel, Plane>),
66 Datum(ElementReference<Datums, Plane>),
67 Sketch(ElementReference<PlacedSketch, Plane>),
68 }
69}
70
71pub struct SketchDrawer;
73
74impl Drawer for SketchDrawer {
75 fn draw(&self, reader: &Reader, view: &mut PartView) -> GeopResult<()> {
76 for target in reader.targets_of::<PlacedSketch>() {
77 let name = target.name().to_string();
78 let reference = ElementReference::<_, PlacedSketch>::whole(target);
79 reader.retrieve_element(&reference)?.draw(&name, view)?;
80 }
81 Ok(())
82 }
83
84 fn structure(&self, reader: &Reader) -> GeopResult<Vec<StructureItem>> {
85 (reader.targets_of::<PlacedSketch>().into_iter())
86 .map(|target| {
87 let reference = ElementReference::<_, PlacedSketch>::whole(target);
88 let placed = reader.retrieve_element(&reference)?;
89 Ok(StructureItem::drawn("sketch", "Sketches", &placed.name))
90 })
91 .collect()
92 }
93}
94
95#[derive(Clone, Debug, PartialEq)]
104pub struct PlacedSketch {
105 pub name: String,
106 pub plane: CoordinateSystem,
107 pub sketch: Sketch,
108}
109
110const PREFIX: &str = "sketch:";
112
113pub fn sketch(name: &str) -> TargetReference<PlacedSketch> {
115 TargetReference::new(format!("{PREFIX}{name}"))
116}
117
118pub type SketchRef = ElementReference<PlacedSketch, PlacedSketch>;
120
121pub fn whole_sketch(name: &str) -> ElementReference<PlacedSketch, PlacedSketch> {
123 ElementReference::whole(sketch(name))
124}
125
126pub fn sketch_element<K: Kind>(
129 name: &str,
130 element: impl ToString,
131) -> ElementReference<PlacedSketch, K>
132where
133 PlacedSketch: HasElements<K>,
134{
135 ElementReference::new(sketch(name), element.to_string())
136}
137
138pub fn piece_names<C>(
142 sketch: &str,
143 pieces: &[Piece<C>],
144 closed: bool,
145) -> (Vec<String>, Vec<String>) {
146 let curve_names = pieces
147 .iter()
148 .map(|p| format!("{sketch},{}", p.name()))
149 .collect();
150 let mut joint_names: Vec<String> = pieces
151 .iter()
152 .map(|p| format!("{sketch},{}", p.start))
153 .collect();
154 if !closed && let Some(last) = pieces.last() {
155 joint_names.push(format!("{sketch},{}", last.end));
156 }
157 (curve_names, joint_names)
158}
159
160pub fn chain_of_pieces<const D: usize>(
163 sketch: &str,
164 pieces: Vec<Piece<NurbCurve<D>>>,
165 closed: bool,
166) -> Chain<NurbCurve<D>> {
167 let (curve_names, joint_names) = piece_names(sketch, &pieces, closed);
168 Chain {
169 name: sketch.to_string(),
170 curves: pieces.into_iter().map(|p| p.curve).collect(),
171 curve_names,
172 joint_names,
173 }
174}
175
176pub fn shape_profiles(
181 name: &str,
182 sketch: &Sketch,
183 geometry: &Enclosure,
184 shape: Shape,
185) -> GeopResult<Vec<Profile>> {
186 match shape {
187 Shape::Regions(regions) => (regions.iter())
188 .flat_map(|region| std::iter::once(®ion.outer).chain(®ion.holes))
189 .map(|lp| Ok(chain_of_pieces(name, lp.to_nurbs(sketch, geometry)?, true)))
190 .collect(),
191 Shape::Chain(chain) => Ok(vec![chain_of_pieces(
192 name,
193 chain.to_nurbs(sketch, geometry)?,
194 false,
195 )]),
196 }
197}
198
199impl PlacedSketch {
200 pub fn curve_in_space(&self, curve: CurveId, geometry: &Enclosure) -> GeopResult<NurbCurve3D> {
204 let pieces = curve_nurbs(&self.sketch, geometry, curve)?;
205 let plane = &self.plane;
206 NurbCurve::join(&pieces)?.embed(plane.origin(), plane.u(), plane.v())
207 }
208
209 pub fn path(&self) -> GeopResult<Chain<NurbCurve3D>> {
213 let ctx = with_context!("the curves of the sketch {:?}", self.name);
214 let sketch = &self.sketch;
215 let geometry = sketch.enclose().with_context(ctx)?;
216 let (lp, closed) = match sketch.shape().with_context(ctx)? {
217 Shape::Chain(chain) => (chain, false),
218 Shape::Regions(mut regions) if regions.len() == 1 && regions[0].holes.is_empty() => {
219 (regions.remove(0).outer, true)
220 }
221 Shape::Regions(_) => {
222 return Err(GeopError::new(
223 "it has more than one loop: a path or a rail is one chain of curves, or one loop",
224 ))
225 .with_context(ctx);
226 }
227 };
228 let pieces = lp.to_nurbs(sketch, &geometry).with_context(ctx)?;
229 let plane = &self.plane;
230 let (curve_names, joint_names) = piece_names(&self.name, &pieces, closed);
231 Ok(Chain {
232 name: self.name.clone(),
233 curves: (pieces.iter())
234 .map(|p| p.curve.embed(plane.origin(), plane.u(), plane.v()))
235 .collect::<GeopResult<_>>()
236 .with_context(ctx)?,
237 curve_names,
238 joint_names,
239 })
240 }
241
242 pub fn runs(&self) -> GeopResult<Vec<Profile>> {
248 let name = &self.name;
249 let ctx = with_context!("the runs of curves of the sketch {name:?}");
250 let geometry = self.sketch.enclose().with_context(ctx)?;
251 let mut taken = BTreeSet::new();
252 let mut profiles = Vec::new();
253 for run in self.sketch.runs().with_context(ctx)? {
254 let lp = ProfileLoop { edges: run.edges };
255 let pieces = lp.to_nurbs(&self.sketch, &geometry).with_context(ctx)?;
256 let first = pieces[0].name();
257 let mut profile = chain_of_pieces(name, pieces, run.closed);
258 for joint in &mut profile.joint_names {
259 if !taken.insert(joint.clone()) {
260 *joint = format!("{joint},{first}");
261 }
262 }
263 profiles.push(profile);
264 }
265 Ok(profiles)
266 }
267
268 pub fn curve_chain(&self, curve: CurveId) -> GeopResult<Chain<NurbCurve3D>> {
272 let name = &self.name;
273 let ctx = with_context!("curve {curve} of the sketch {name:?}");
274 let geometry = self.sketch.enclose().with_context(ctx)?;
275 let class = self.sketch.point_classes();
276 let in_space = self.curve_in_space(curve, &geometry).with_context(ctx)?;
277 let joint_name = |p: PointId| match class.get(&p) {
278 Some(class) => Ok(format!("{name},{class}")),
279 None => Err(GeopError::new(format!("point {p} has no class"))),
280 };
281 let joint_names = match self.sketch.curve(curve)?.endpoints() {
282 Some((s, e)) => vec![joint_name(s)?, joint_name(e)?],
283 None => vec![format!("{name},{curve},seam")],
284 };
285 Ok(Chain {
286 name: format!("{name},{curve}"),
287 curves: vec![in_space],
288 curve_names: vec![format!("{name},{curve}")],
289 joint_names,
290 })
291 }
292}
293
294impl PlacedSketch {
295 pub fn draw(&self, target: &str, view: &mut PartView) -> GeopResult<()> {
299 let s = &self.sketch;
300 let regions = (s.regions())
303 .and_then(|regions| {
304 (regions.iter())
305 .map(|r| {
306 (std::iter::once(&r.outer).chain(&r.holes))
307 .map(|l| l.polyline(s))
308 .collect()
309 })
310 .collect()
311 })
312 .unwrap_or_default();
313 let mut curves = Vec::new();
315 for (&id, c) in s
316 .curves
317 .iter()
318 .filter(|(_, c)| !(c.fixed && c.construction))
319 {
320 curves.extend(
321 curve_polylines(s, id)?
322 .into_iter()
323 .map(|polyline| ViewCurve {
324 id: id.to_string(),
325 construction: c.construction,
326 polyline,
327 }),
328 );
329 }
330 let points = (s.positions().iter())
331 .filter(|(id, _)| s.points.get(id).is_some_and(|p| !p.fixed))
332 .map(|(id, &at)| ViewSketchPoint {
333 id: id.to_string(),
334 at,
335 })
336 .collect();
337 view.sketches.push(ViewSketch {
338 name: self.name.clone(),
339 target: target.to_string(),
340 plane: self.plane.clone(),
341 regions,
342 curves,
343 points,
344 });
345 Ok(())
346 }
347
348 fn point_in_space(&self, name: &str) -> GeopResult<Vector3> {
350 let id: PointId = name.parse()?;
351 self.sketch.point(id)?;
352 let geometry = self.sketch.enclose()?;
353 let at = (geometry.points.get(&id))
354 .ok_or_else(|| GeopError::new(format!("point {id} is not solved")))?;
355 Ok(self.plane.uv_to_xyz(at))
356 }
357
358 fn curve_named(&self, name: &str) -> GeopResult<(CurveId, &CurveKind)> {
360 let id: CurveId = name.parse()?;
361 Ok((id, &self.sketch.curve(id)?.kind))
362 }
363
364 fn arc(&self, name: &str) -> GeopResult<Arc> {
366 let (id, kind) = self.curve_named(name)?;
367 if !matches!(kind, CurveKind::Arc { .. } | CurveKind::Circle { .. }) {
368 return Err(GeopError::new(format!("curve {id} is no circle or arc")));
369 }
370 let geometry = self.sketch.enclose()?;
371 (self.curve_in_space(id, &geometry)?.as_arc()?)
372 .ok_or_else(|| GeopError::new(format!("curve {id} is not circular")))
373 }
374}
375
376impl Target for PlacedSketch {}
378
379impl Kind for PlacedSketch {
381 const NAME: &'static str = drawn::SKETCH;
382}
383
384macro_rules! sketch_elements {
387 ($($kind:ty => $element:ty, |$sketch:ident, $name:ident| $resolve:expr;)*) => {$(
388 impl HasElements<$kind> for PlacedSketch {
389 type Element<'a> = $element;
390
391 fn element(&self, name: &str) -> GeopResult<$element> {
392 let ($sketch, $name) = (self, name);
393 ($resolve).with_context(&|e: GeopError| {
394 e.with_context(format!("{} {name:?} of the sketch {:?}", <$kind>::NAME, self.name))
395 })
396 }
397
398 fn names_target(name: &str) -> bool {
399 name.starts_with(PREFIX)
400 }
401 }
402 )*};
403}
404
405impl HasElements<PlacedSketch> for PlacedSketch {
406 type Element<'a> = &'a PlacedSketch;
407
408 fn element(&self, name: &str) -> GeopResult<&PlacedSketch> {
409 whole(self, name)
410 }
411
412 fn names_target(name: &str) -> bool {
413 name.starts_with(PREFIX)
414 }
415}
416
417sketch_elements! {
418 Plane => CoordinateSystem, |sketch, name| {
419 whole(sketch, name).map(|sketch| sketch.plane.clone())
420 };
421 Path => Chain<NurbCurve3D>, |sketch, name| whole(sketch, name)?.path();
422 Point => Vector3, |sketch, name| sketch.point_in_space(name);
423 Line => Axis, |sketch, name| {
424 match sketch.curve_named(name)? {
425 (_, &CurveKind::Line { start, end }) => {
426 let (a, b) = (sketch.point_in_space(&start.to_string())?, sketch.point_in_space(&end.to_string())?);
427 Axis::try_new(a, b.sub(&a))
428 }
429 (id, _) => Err(GeopError::new(format!("curve {id} is no line"))),
430 }
431 };
432 Circle => Arc, |sketch, name| sketch.arc(name);
433 Round => Axis, |sketch, name| sketch.arc(name).map(|arc| arc.circle.axis());
434 Curve => Chain<NurbCurve3D>, |sketch, name| {
435 let (id, _) = sketch.curve_named(name)?;
436 sketch.curve_chain(id)
437 };
438}
439
440macro_rules! picked_from_its_drawing {
444 ($($kind:ty => $from:expr;)*) => {$(
445 impl SelectableElement<PlacedSketch> for $kind {
446 const PICKED_FROM: &'static [&'static str] = &[$from];
447
448 fn select(
449 reader: &Reader,
450 hit: &PickedElement,
451 ) -> Option<ElementReference<PlacedSketch, Self>> {
452 let is_sketch = <PlacedSketch as HasElements<$kind>>::names_target(hit.target.name());
453 (is_sketch && hit.kind == $from).then_some(())?;
454 let reference = ElementReference::new(TargetReference::located(hit.target.clone()), &hit.name);
455 reader.retrieve_element(&reference).ok()?;
456 Some(reference)
457 }
458
459 fn drawn(
460 _: &Reader,
461 element: &ElementReference<PlacedSketch, Self>,
462 ) -> Vec<PickedElement> {
463 vec![PickedElement {
464 target: element.target().location().clone(),
465 kind: $from.to_string(),
466 name: element.name().to_string(),
467 }]
468 }
469 }
470 )*};
471}
472
473picked_from_its_drawing! {
474 PlacedSketch => drawn::SKETCH;
475 Plane => drawn::SKETCH;
476 Path => drawn::SKETCH;
477 Point => drawn::SKETCH_POINT;
478 Line => drawn::SKETCH_CURVE;
479 Circle => drawn::SKETCH_CURVE;
480 Round => drawn::SKETCH_CURVE;
481 Curve => drawn::SKETCH_CURVE;
482}