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