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