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