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::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
28pub 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
36async 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 let face_id = if no_engine_commands {
46 exec_state.next_uuid()
47 } else {
48 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 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
115pub 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
137async fn inner_edge_id(
139 body: Solid,
140 edge_index: u32,
141 exec_state: &mut ExecState,
142 args: Args,
143) -> Result<KclValue, KclError> {
144 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
182async 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 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}