Skip to main content

geop_ops_model/
sketch3d.rs

1//! The 3-D sketch target: [`PlacedSketch3d`], a 3-D sketch as a part holds
2//! it — the value of its target ([`sketch3d`]) — and what it is to the
3//! steps that build on it: a path as a whole ([`Path`]), picked by an edge
4//! of the wire it is built as. And what of the part a 3-D sketch is given:
5//! a point ([`PointSource`]), a curve ([`CurveSource`]), a line to run
6//! along ([`LineSource`]). The step that draws one is `geop_ops_sketch3d`'s.
7
8use crate::{
9    Chain, Curve, Line, NamedModel, Path, Point,
10    datums::Datums,
11    model,
12    sketch::{PlacedSketch, chain_of_pieces},
13};
14use geop_core_editor::{one_of, selectable::SelectableElement, view::drawn};
15use geop_core_geometry::{nurb_curve::NurbCurve3D, shape::Axis};
16use geop_core_math::{
17    geop_error::{GeopError, GeopResult, WithContext},
18    vector::Vector3,
19    with_context,
20};
21use geop_core_program::{
22    element::{ElementReference, HasElements, Kind, PickedElement, whole},
23    reader::Reader,
24    target::{Target, TargetReference},
25};
26
27/// A 3-D sketch as a program holds it.
28pub type Sketch3d = geop_core_sketch::space::Sketch3d;
29/// A 3-D sketch's constraint as a program holds it.
30pub type Constraint3d = geop_core_sketch::space::Constraint3d;
31
32one_of! {
33    /// A point of the part a 3-D sketch's fixed point is at: a vertex, a
34    /// datum point or a frame's origin, a sketch's point.
35    pub enum PointSource -> Vector3 {
36        Vertex(ElementReference<NamedModel, Point>),
37        Datum(ElementReference<Datums, Point>),
38        Sketch(ElementReference<PlacedSketch, Point>),
39    }
40}
41
42one_of! {
43    /// A curve of the part a 3-D sketch's reference curve is: an edge, a
44    /// sketch's curve.
45    pub enum CurveSource -> Chain<NurbCurve3D> {
46        Edge(ElementReference<NamedModel, Curve>),
47        Sketch(ElementReference<PlacedSketch, Curve>),
48    }
49}
50
51one_of! {
52    /// A line of the part a 3-D sketch's direction runs along: a straight
53    /// edge, a datum axis or a frame's, a sketch's line.
54    pub enum LineSource -> Axis {
55        Edge(ElementReference<NamedModel, Line>),
56        Datum(ElementReference<Datums, Line>),
57        Sketch(ElementReference<PlacedSketch, Line>),
58    }
59}
60
61/// A 3-D sketch as a part holds it: its name, the id of the step that drew
62/// it; the sketch, what it is given of the part brought up to date and
63/// solved; and the names of the edges of the wire it is built as — what a
64/// pick of one of them takes the whole sketch for, where a path is asked
65/// for.
66#[derive(Clone, Debug, PartialEq)]
67pub struct PlacedSketch3d {
68    pub name: String,
69    pub sketch: Sketch3d,
70    pub edges: Vec<String>,
71}
72
73/// What the target of a 3-D sketch is named, before the sketch's name.
74const PREFIX: &str = "sketch3d:";
75
76/// The 3-D sketch `name`: what the step of that id draws.
77pub fn sketch3d(name: &str) -> TargetReference<PlacedSketch3d> {
78    TargetReference::new(format!("{PREFIX}{name}"))
79}
80
81/// The 3-D sketch `name` as a path.
82pub fn sketch3d_path(name: &str) -> ElementReference<PlacedSketch3d, Path> {
83    ElementReference::whole(sketch3d(name))
84}
85
86impl PlacedSketch3d {
87    /// Its one chain of curves, open or closed, as a path runs along it:
88    /// named after the sketch and its elements.
89    pub fn path(&self) -> GeopResult<Chain<NurbCurve3D>> {
90        let ctx = with_context!("the curves of the 3-D sketch {:?}", self.name);
91        let chains = self.sketch.chains().with_context(ctx)?;
92        let [chain] = chains.as_slice() else {
93            return Err(GeopError::new(format!(
94                "it has {} chains of curves: a path is one chain of curves, or one loop",
95                chains.len()
96            )))
97            .with_context(ctx);
98        };
99        let geometry = self.sketch.enclose().with_context(ctx)?;
100        let pieces = chain.to_nurbs(&self.sketch, &geometry).with_context(ctx)?;
101        Ok(chain_of_pieces(&self.name, pieces, chain.closed))
102    }
103}
104
105/// Produced once, by the step that draws it.
106impl Target for PlacedSketch3d {}
107
108/// Read whole.
109impl Kind for PlacedSketch3d {
110    const NAME: &'static str = "sketch3d";
111}
112
113impl HasElements<PlacedSketch3d> for PlacedSketch3d {
114    type Element<'a> = &'a PlacedSketch3d;
115
116    fn element(&self, name: &str) -> GeopResult<&PlacedSketch3d> {
117        whole(self, name)
118    }
119
120    fn names_target(name: &str) -> bool {
121        name.starts_with(PREFIX)
122    }
123}
124
125impl HasElements<Path> for PlacedSketch3d {
126    type Element<'a> = Chain<NurbCurve3D>;
127
128    fn element(&self, name: &str) -> GeopResult<Chain<NurbCurve3D>> {
129        whole(self, name)?.path()
130    }
131
132    fn names_target(name: &str) -> bool {
133        name.starts_with(PREFIX)
134    }
135}
136
137/// A 3-D sketch is a path as a whole, picked by any edge of its wire, and
138/// lit as all of them.
139impl SelectableElement<PlacedSketch3d> for Path {
140    const PICKED_FROM: &'static [&'static str] = &[drawn::EDGE];
141
142    fn select(
143        reader: &Reader,
144        hit: &PickedElement,
145    ) -> Option<ElementReference<PlacedSketch3d, Self>> {
146        (hit.kind == drawn::EDGE && hit.target == *model().location()).then_some(())?;
147        reader
148            .targets_of::<PlacedSketch3d>()
149            .into_iter()
150            .find(|target| {
151                let whole =
152                    ElementReference::<PlacedSketch3d, PlacedSketch3d>::whole(target.clone());
153                (reader.retrieve_element(&whole))
154                    .is_ok_and(|placed| placed.edges.contains(&hit.name))
155            })
156            .map(ElementReference::whole)
157    }
158
159    fn drawn(reader: &Reader, path: &ElementReference<PlacedSketch3d, Self>) -> Vec<PickedElement> {
160        let whole =
161            ElementReference::<PlacedSketch3d, PlacedSketch3d>::whole(path.target().clone());
162        let edges = (reader.retrieve_element(&whole).ok()).map(|placed| placed.edges.clone());
163        (edges.unwrap_or_default().into_iter())
164            .map(|edge| PickedElement {
165                target: model().location().clone(),
166                kind: drawn::EDGE.to_string(),
167                name: edge,
168            })
169            .collect()
170    }
171}