Skip to main content

kcl_lib/std/
faces.rs

1//! Standard library face helpers.
2
3use uuid::Uuid;
4
5use super::sketch::FaceTag;
6use crate::errors::KclError;
7use crate::errors::KclErrorDetails;
8use crate::execution::ExecState;
9use crate::execution::Face;
10use crate::execution::FaceParentSolid;
11use crate::execution::KclValue;
12use crate::execution::Segment;
13use crate::execution::Solid;
14use crate::execution::types::RuntimeType;
15use crate::std::Args;
16
17const SEGMENT_MUST_HAVE_TAG_ERROR: &str =
18    "Face specifier must have a tag. For sketch block segments, assign the segment to a variable.";
19
20/// Specifies a face of a `Solid` either by a tag or by a segment.
21pub(crate) enum FaceSpecifier {
22    FaceTag(FaceTag),
23    Segment(Box<Segment>),
24}
25
26impl FaceSpecifier {
27    /// Gets the tag name of the face specifier, if it has one. This may return
28    /// the fake tag names "start" or "end" when the face specifier is `START`
29    /// or `END`.
30    fn tag_name(&self) -> Option<String> {
31        match self {
32            FaceSpecifier::FaceTag(face_tag) => Some(face_tag.to_string()),
33            FaceSpecifier::Segment(segment) => segment.tag.as_ref().map(|tag| tag.to_string()),
34        }
35    }
36
37    pub(super) async fn face_id(
38        &self,
39        solid: &Solid,
40        exec_state: &mut ExecState,
41        args: &Args,
42        must_be_planar: bool,
43    ) -> Result<Uuid, KclError> {
44        match self {
45            FaceSpecifier::FaceTag(face_tag) => face_tag.get_face_id(solid, exec_state, args, must_be_planar).await,
46            FaceSpecifier::Segment(segment) => {
47                let tag_id = segment.tag.as_ref().ok_or_else(|| {
48                    KclError::new_semantic(KclErrorDetails::new(
49                        SEGMENT_MUST_HAVE_TAG_ERROR.to_owned(),
50                        vec![args.source_range],
51                    ))
52                })?;
53                args.get_adjacent_face_to_tag(exec_state, tag_id, must_be_planar).await
54            }
55        }
56    }
57}
58
59/// Face of a given solid.
60pub async fn face_of(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
61    let solid = args.get_unlabeled_kw_arg("solid", &RuntimeType::solid(), exec_state)?;
62    let face = args.get_kw_arg("face", &RuntimeType::tagged_face_or_segment(), exec_state)?;
63
64    let face = make_face(solid, face, exec_state, &args).await?;
65
66    Ok(KclValue::Face { value: face })
67}
68
69pub(super) async fn make_face(
70    solid: Box<Solid>,
71    face: FaceSpecifier,
72    exec_state: &mut ExecState,
73    args: &Args,
74) -> Result<Box<Face>, KclError> {
75    let Some(tag_name) = face.tag_name() else {
76        return Err(KclError::new_semantic(KclErrorDetails::new(
77            SEGMENT_MUST_HAVE_TAG_ERROR.to_owned(),
78            vec![args.source_range],
79        )));
80    };
81    let extrude_plane_id = face.face_id(&solid, exec_state, args, true).await?;
82    let sketch = solid.sketch().ok_or_else(|| {
83        KclError::new_type(crate::errors::KclErrorDetails::new(
84            "This solid was created without a sketch, so its face axes are unavailable.".to_owned(),
85            vec![args.source_range],
86        ))
87    })?;
88
89    let object_id = exec_state.next_object_id();
90    #[cfg(feature = "artifact-graph")]
91    {
92        use crate::front::SourceRef;
93
94        let face_object = crate::front::Object {
95            id: object_id,
96            kind: crate::front::ObjectKind::Face(crate::front::Face { id: object_id }),
97            label: Default::default(),
98            comments: Default::default(),
99            artifact_id: extrude_plane_id.into(),
100            source: SourceRef::new(args.source_range, args.node_path.clone()),
101        };
102        exec_state.add_scene_object(face_object, args.source_range);
103    }
104
105    Ok(Box::new(Face {
106        id: extrude_plane_id,
107        artifact_id: extrude_plane_id.into(),
108        object_id,
109        value: tag_name,
110        // TODO: get this from the extrude plane data.
111        x_axis: sketch.on.x_axis(),
112        y_axis: sketch.on.y_axis(),
113        units: solid.units,
114        parent_solid: FaceParentSolid::from(&*solid),
115        meta: vec![args.source_range.into()],
116    }))
117}