Skip to main content

kcl_lib/std/
ids.rs

1//! Functions for handling and converting IDs.
2
3use anyhow::Result;
4use kcmc::{ModelingCmd, each_cmd as mcmd};
5use kittycad_modeling_cmds::{self as kcmc, ok_response::OkModelingCmdResponse, websocket::OkWebSocketResponseData};
6
7use crate::{
8    errors::{KclError, KclErrorDetails},
9    exec::KclValue,
10    execution::{
11        ExecState, ExtrudeSurface, GeoMeta, Geometry, Metadata, ModelingCmdMeta, Solid, TagEngineInfo, TagIdentifier,
12        types::RuntimeType,
13    },
14    parsing::ast::types::TagDeclarator,
15    std::Args,
16};
17
18/// Translates face indices to face IDs.
19pub async fn face_id(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
20    let body = args.get_unlabeled_kw_arg("body", &RuntimeType::solid(), exec_state)?;
21    let face_index: u32 = args.get_kw_arg("index", &RuntimeType::count(), exec_state)?;
22
23    inner_face_id(body, face_index, exec_state, args).await
24}
25
26/// Translates face indices to face IDs.
27async fn inner_face_id(
28    body: Solid,
29    face_index: u32,
30    exec_state: &mut ExecState,
31    args: Args,
32) -> Result<KclValue, KclError> {
33    let no_engine_commands = args.ctx.no_engine_commands().await;
34    // Handle mock execution
35    let face_id = if no_engine_commands {
36        exec_state.next_uuid()
37    } else {
38        // Query engine, unpack response.
39        let face_uuid_response = exec_state
40            .send_modeling_cmd(
41                ModelingCmdMeta::from_args(exec_state, &args),
42                ModelingCmd::from(
43                    mcmd::Solid3dGetFaceUuid::builder()
44                        .object_id(body.id)
45                        .face_index(face_index)
46                        .build(),
47                ),
48            )
49            .await?;
50        let OkWebSocketResponseData::Modeling {
51            modeling_response: OkModelingCmdResponse::Solid3dGetFaceUuid(inner_resp),
52        } = face_uuid_response
53        else {
54            return Err(KclError::new_semantic(KclErrorDetails::new(
55                format!(
56                    "Engine returned invalid response, it should have returned Solid3dGetFaceUuid but it returned {face_uuid_response:?}"
57                ),
58                vec![args.source_range],
59            )));
60        };
61        inner_resp.face_id
62    };
63
64    let new_tag_name = format!("face_id_{}", face_id.to_string().replace('-', "_"));
65    let new_tag_node = TagDeclarator::new(&new_tag_name);
66
67    let mut tagged_surface = body
68        .value
69        .iter()
70        .find(|surface| surface.face_id() == face_id)
71        .cloned()
72        .unwrap_or_else(|| {
73            // Booleans and imported solids can have engine face IDs that we don't track in
74            // `body.value`, but `faceId` should still return a usable tagged face.
75            ExtrudeSurface::ExtrudePlane(crate::execution::ExtrudePlane {
76                face_id,
77                tag: None,
78                geo_meta: GeoMeta {
79                    id: face_id,
80                    metadata: args.source_range.into(),
81                },
82            })
83        });
84    tagged_surface.set_surface_tag(&new_tag_node);
85
86    let new_tag = TagIdentifier {
87        value: new_tag_name,
88        info: vec![(
89            exec_state.stack().current_epoch(),
90            TagEngineInfo {
91                id: tagged_surface.get_id(),
92                geometry: Geometry::Solid(body),
93                path: None,
94                surface: Some(tagged_surface),
95            },
96        )],
97        meta: vec![Metadata {
98            source_range: args.source_range,
99        }],
100    };
101
102    Ok(KclValue::TagIdentifier(Box::new(new_tag)))
103}
104
105/// Translates edge indices to edge IDs.
106pub async fn edge_id(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
107    let body = args.get_unlabeled_kw_arg("body", &RuntimeType::solid(), exec_state)?;
108    let edge_index: u32 = args.get_kw_arg("index", &RuntimeType::count(), exec_state)?;
109
110    inner_edge_id(body, edge_index, exec_state, args).await
111}
112
113/// Translates edge indices to edge IDs.
114async fn inner_edge_id(
115    body: Solid,
116    edge_index: u32,
117    exec_state: &mut ExecState,
118    args: Args,
119) -> Result<KclValue, KclError> {
120    // Handle mock execution
121    let no_engine_commands = args.ctx.no_engine_commands().await;
122    let edge_id = if no_engine_commands {
123        exec_state.next_uuid()
124    } else {
125        let edge_uuid_response = exec_state
126            .send_modeling_cmd(
127                ModelingCmdMeta::from_args(exec_state, &args),
128                ModelingCmd::from(
129                    mcmd::Solid3dGetEdgeUuid::builder()
130                        .object_id(body.id)
131                        .edge_index(edge_index)
132                        .build(),
133                ),
134            )
135            .await?;
136
137        let OkWebSocketResponseData::Modeling {
138            modeling_response: OkModelingCmdResponse::Solid3dGetEdgeUuid(inner_resp),
139        } = edge_uuid_response
140        else {
141            return Err(KclError::new_semantic(KclErrorDetails::new(
142                format!(
143                    "Engine returned invalid response, it should have returned Solid3dGetEdgeUuid but it returned {edge_uuid_response:?}"
144                ),
145                vec![args.source_range],
146            )));
147        };
148        inner_resp.edge_id
149    };
150    Ok(KclValue::Uuid {
151        value: edge_id,
152        meta: vec![Metadata {
153            source_range: args.source_range,
154        }],
155    })
156}