Skip to main content

kcl_lib/std/
ids.rs

1//! Functions for handling and converting IDs.
2
3use anyhow::Result;
4use kcmc::ModelingCmd;
5use kcmc::each_cmd as mcmd;
6use kittycad_modeling_cmds::ok_response::OkModelingCmdResponse;
7use kittycad_modeling_cmds::shared::Point3d;
8use kittycad_modeling_cmds::websocket::OkWebSocketResponseData;
9use kittycad_modeling_cmds::{self as kcmc};
10
11use crate::errors::KclError;
12use crate::errors::KclErrorDetails;
13use crate::exec::KclValue;
14use crate::execution::ExecState;
15use crate::execution::ExtrudeSurface;
16use crate::execution::GeoMeta;
17use crate::execution::Geometry;
18use crate::execution::Metadata;
19use crate::execution::ModelingCmdMeta;
20use crate::execution::Solid;
21use crate::execution::TagEngineInfo;
22use crate::execution::TagIdentifier;
23use crate::execution::types::RuntimeType;
24use crate::parsing::ast::types::TagDeclarator;
25use crate::std::Args;
26use crate::std::args::TyF64;
27
28/// Translates face indices to face IDs.
29pub async fn face_id(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
30    let body = args.get_unlabeled_kw_arg("body", &RuntimeType::solid(), exec_state)?;
31    let face_index: u32 = args.get_kw_arg("index", &RuntimeType::count(), exec_state)?;
32
33    inner_face_id(body, face_index, exec_state, args).await
34}
35
36/// Translates face indices to face IDs.
37async fn inner_face_id(
38    body: Solid,
39    face_index: u32,
40    exec_state: &mut ExecState,
41    args: Args,
42) -> Result<KclValue, KclError> {
43    let no_engine_commands = args.ctx.no_engine_commands().await;
44    // Handle mock execution
45    let face_id = if no_engine_commands {
46        exec_state.next_uuid()
47    } else {
48        // Query engine, unpack response.
49        let face_uuid_response = exec_state
50            .send_modeling_cmd(
51                ModelingCmdMeta::from_args(exec_state, &args),
52                ModelingCmd::from(
53                    mcmd::Solid3dGetFaceUuid::builder()
54                        .object_id(body.id)
55                        .face_index(face_index)
56                        .build(),
57                ),
58            )
59            .await?;
60        let OkWebSocketResponseData::Modeling {
61            modeling_response: OkModelingCmdResponse::Solid3dGetFaceUuid(inner_resp),
62        } = face_uuid_response
63        else {
64            return Err(KclError::new_semantic(KclErrorDetails::new(
65                format!(
66                    "Engine returned invalid response, it should have returned Solid3dGetFaceUuid but it returned {face_uuid_response:?}"
67                ),
68                vec![args.source_range],
69            )));
70        };
71        inner_resp.face_id
72    };
73
74    let new_tag_name = format!("face_id_{}", face_id.to_string().replace('-', "_"));
75    let new_tag_node = TagDeclarator::new(&new_tag_name);
76
77    let mut tagged_surface = body
78        .value
79        .iter()
80        .find(|surface| surface.face_id() == face_id)
81        .cloned()
82        .unwrap_or_else(|| {
83            // Booleans and imported solids can have engine face IDs that we don't track in
84            // `body.value`, but `faceId` should still return a usable tagged face.
85            ExtrudeSurface::ExtrudePlane(crate::execution::ExtrudePlane {
86                face_id,
87                tag: None,
88                geo_meta: GeoMeta {
89                    id: face_id,
90                    metadata: args.source_range.into(),
91                },
92            })
93        });
94    tagged_surface.set_surface_tag(&new_tag_node);
95
96    let new_tag = TagIdentifier {
97        value: new_tag_name,
98        info: vec![(
99            exec_state.stack().current_epoch(),
100            TagEngineInfo {
101                id: tagged_surface.get_id(),
102                geometry: Geometry::Solid(body),
103                path: None,
104                surface: Some(tagged_surface),
105            },
106        )],
107        meta: vec![Metadata {
108            source_range: args.source_range,
109        }],
110    };
111
112    Ok(KclValue::TagIdentifier(Box::new(new_tag)))
113}
114
115/// Translates edge indices to edge IDs.
116pub async fn edge_id(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
117    let body = args.get_unlabeled_kw_arg("body", &RuntimeType::solid(), exec_state)?;
118    let edge_index: Option<u32> = args.get_kw_arg_opt("index", &RuntimeType::count(), exec_state)?;
119    let closest_to: Option<[TyF64; 3]> = args.get_kw_arg_opt("closestTo", &RuntimeType::point3d(), exec_state)?;
120    let closest_to = closest_to
121        .map(|point| [point[0].to_mm(), point[1].to_mm(), point[2].to_mm()])
122        .map(|[x, y, z]| Point3d { x, y, z });
123    match (edge_index, closest_to) {
124        (None, None) => Err(KclError::new_semantic(KclErrorDetails::new(
125            "Must use either `index` or `closestTo`".to_string(),
126            vec![args.source_range],
127        ))),
128        (None, Some(closest_to)) => inner_edge_id_by_point(body, closest_to, exec_state, args).await,
129        (Some(edge_index), None) => inner_edge_id(body, edge_index, exec_state, args).await,
130        (Some(_), Some(_)) => Err(KclError::new_semantic(KclErrorDetails::new(
131            "Cannot use both `index` and `closestTo`".to_string(),
132            vec![args.source_range],
133        ))),
134    }
135}
136
137/// Translates edge indices to edge IDs.
138async fn inner_edge_id(
139    body: Solid,
140    edge_index: u32,
141    exec_state: &mut ExecState,
142    args: Args,
143) -> Result<KclValue, KclError> {
144    // Handle mock execution
145    let no_engine_commands = args.ctx.no_engine_commands().await;
146    let edge_id = if no_engine_commands {
147        exec_state.next_uuid()
148    } else {
149        let edge_uuid_response = exec_state
150            .send_modeling_cmd(
151                ModelingCmdMeta::from_args(exec_state, &args),
152                ModelingCmd::from(
153                    mcmd::Solid3dGetEdgeUuid::builder()
154                        .object_id(body.id)
155                        .edge_index(edge_index)
156                        .build(),
157                ),
158            )
159            .await?;
160
161        let OkWebSocketResponseData::Modeling {
162            modeling_response: OkModelingCmdResponse::Solid3dGetEdgeUuid(inner_resp),
163        } = edge_uuid_response
164        else {
165            return Err(KclError::new_semantic(KclErrorDetails::new(
166                format!(
167                    "Engine returned invalid response, it should have returned Solid3dGetEdgeUuid but it returned {edge_uuid_response:?}"
168                ),
169                vec![args.source_range],
170            )));
171        };
172        inner_resp.edge_id
173    };
174    Ok(KclValue::Uuid {
175        value: edge_id,
176        meta: vec![Metadata {
177            source_range: args.source_range,
178        }],
179    })
180}
181
182/// Finds ID of edge closest to this point.
183async fn inner_edge_id_by_point(
184    body: Solid,
185    closest_point: Point3d<f64>,
186    exec_state: &mut ExecState,
187    args: Args,
188) -> Result<KclValue, KclError> {
189    // Handle mock execution
190    let no_engine_commands = args.ctx.no_engine_commands().await;
191    let edge_id = if no_engine_commands {
192        exec_state.next_uuid()
193    } else {
194        let edge_uuid_response = exec_state
195            .send_modeling_cmd(
196                ModelingCmdMeta::from_args(exec_state, &args),
197                ModelingCmd::from(
198                    mcmd::ClosestEdge::builder()
199                        .object_id(body.id)
200                        .closest_to(closest_point)
201                        .build(),
202                ),
203            )
204            .await?;
205
206        let OkWebSocketResponseData::Modeling {
207            modeling_response: OkModelingCmdResponse::ClosestEdge(inner_resp),
208        } = edge_uuid_response
209        else {
210            return Err(KclError::new_semantic(KclErrorDetails::new(
211                format!(
212                    "Engine returned invalid response, it should have returned ClosestEdge but it returned {edge_uuid_response:?}"
213                ),
214                vec![args.source_range],
215            )));
216        };
217        let Some(edge_id) = inner_resp.edge_id else {
218            return Err(KclError::new_semantic(KclErrorDetails::new(
219                "Engine didn't find any edges near this point".to_string(),
220                vec![args.source_range],
221            )));
222        };
223        edge_id
224    };
225    Ok(KclValue::Uuid {
226        value: edge_id,
227        meta: vec![Metadata {
228            source_range: args.source_range,
229        }],
230    })
231}