1use 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
30pub 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
38async 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 let face_id = if no_engine_commands {
48 exec_state.next_uuid()
49 } else {
50 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 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
117pub 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| {
124 [
125 point[0].unwrap_to_mm(),
126 point[1].unwrap_to_mm(),
127 point[2].unwrap_to_mm(),
128 ]
129 })
130 .map(|[x, y, z]| Point3d { x, y, z });
131 match (edge_index, closest_to) {
132 (None, None) => Err(KclError::new_semantic(KclErrorDetails::new(
133 "Must use either `index` or `closestTo`".to_string(),
134 vec![args.source_range],
135 ))),
136 (None, Some(closest_to)) => inner_edge_id_by_point(body, closest_to, exec_state, args).await,
137 (Some(edge_index), None) => inner_edge_id(body, edge_index, exec_state, args).await,
138 (Some(_), Some(_)) => Err(KclError::new_semantic(KclErrorDetails::new(
139 "Cannot use both `index` and `closestTo`".to_string(),
140 vec![args.source_range],
141 ))),
142 }
143}
144
145async fn inner_edge_id(
147 body: Solid,
148 edge_index: u32,
149 exec_state: &mut ExecState,
150 args: Args,
151) -> Result<KclValue, KclError> {
152 let no_engine_commands = args.ctx.no_engine_commands().await;
154 let edge_id = if no_engine_commands {
155 exec_state.next_uuid()
156 } else {
157 let edge_uuid_response = exec_state
158 .send_modeling_cmd(
159 ModelingCmdMeta::from_args(exec_state, &args),
160 ModelingCmd::from(
161 mcmd::Solid3dGetEdgeUuid::builder()
162 .object_id(body.id)
163 .edge_index(edge_index)
164 .build(),
165 ),
166 )
167 .await?;
168
169 let OkWebSocketResponseData::Modeling {
170 modeling_response: OkModelingCmdResponse::Solid3dGetEdgeUuid(inner_resp),
171 } = edge_uuid_response
172 else {
173 return Err(KclError::new_semantic(KclErrorDetails::new(
174 format!(
175 "Engine returned invalid response, it should have returned Solid3dGetEdgeUuid but it returned {edge_uuid_response:?}"
176 ),
177 vec![args.source_range],
178 )));
179 };
180 let edge_id = inner_resp.edge_id;
181
182 if let Ok(meta) = edge::get_refactor_meta_for_edge(
183 exec_state,
184 edge_id,
185 &args,
186 args.source_range,
187 EdgeRefactorStdlibFn::EdgeId,
188 )
189 .await
190 {
191 exec_state.record_edge_refactor_meta(meta);
192 }
193
194 edge_id
195 };
196 Ok(KclValue::Uuid {
197 value: edge_id,
198 meta: vec![Metadata {
199 source_range: args.source_range,
200 }],
201 })
202}
203
204async fn inner_edge_id_by_point(
206 body: Solid,
207 closest_point: Point3d<f64>,
208 exec_state: &mut ExecState,
209 args: Args,
210) -> Result<KclValue, KclError> {
211 let no_engine_commands = args.ctx.no_engine_commands().await;
213 let edge_id = if no_engine_commands {
214 exec_state.next_uuid()
215 } else {
216 let edge_uuid_response = exec_state
217 .send_modeling_cmd(
218 ModelingCmdMeta::from_args(exec_state, &args),
219 ModelingCmd::from(
220 mcmd::ClosestEdge::builder()
221 .object_id(body.id)
222 .closest_to(closest_point)
223 .build(),
224 ),
225 )
226 .await?;
227
228 let OkWebSocketResponseData::Modeling {
229 modeling_response: OkModelingCmdResponse::ClosestEdge(inner_resp),
230 } = edge_uuid_response
231 else {
232 return Err(KclError::new_semantic(KclErrorDetails::new(
233 format!(
234 "Engine returned invalid response, it should have returned ClosestEdge but it returned {edge_uuid_response:?}"
235 ),
236 vec![args.source_range],
237 )));
238 };
239 let Some(edge_id) = inner_resp.edge_id else {
240 return Err(KclError::new_semantic(KclErrorDetails::new(
241 "Engine didn't find any edges near this point".to_string(),
242 vec![args.source_range],
243 )));
244 };
245
246 if let Ok(meta) = edge::get_refactor_meta_for_edge(
247 exec_state,
248 edge_id,
249 &args,
250 args.source_range,
251 EdgeRefactorStdlibFn::EdgeId,
252 )
253 .await
254 {
255 exec_state.record_edge_refactor_meta(meta);
256 }
257
258 edge_id
259 };
260 Ok(KclValue::Uuid {
261 value: edge_id,
262 meta: vec![Metadata {
263 source_range: args.source_range,
264 }],
265 })
266}