1use anyhow::Result;
4use kcl_derive_docs::stdlib;
5use kcmc::{
6 each_cmd as mcmd, length_unit::LengthUnit, ok_response::OkModelingCmdResponse, shared::CutType,
7 websocket::OkWebSocketResponseData, ModelingCmd,
8};
9use kittycad_modeling_cmds as kcmc;
10use schemars::JsonSchema;
11use serde::{Deserialize, Serialize};
12use uuid::Uuid;
13
14use super::utils::unique_count;
15use crate::{
16 errors::{KclError, KclErrorDetails},
17 execution::{EdgeCut, ExecState, ExtrudeSurface, FilletSurface, GeoMeta, KclValue, Solid, TagIdentifier},
18 parsing::ast::types::TagNode,
19 settings::types::UnitLength,
20 std::Args,
21};
22
23#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq, Hash)]
25#[ts(export)]
26#[serde(untagged)]
27pub enum EdgeReference {
28 Uuid(uuid::Uuid),
30 Tag(Box<TagIdentifier>),
32}
33
34impl EdgeReference {
35 pub fn get_engine_id(&self, exec_state: &mut ExecState, args: &Args) -> Result<uuid::Uuid, KclError> {
36 match self {
37 EdgeReference::Uuid(uuid) => Ok(*uuid),
38 EdgeReference::Tag(tag) => Ok(args.get_tag_engine_info(exec_state, tag)?.id),
39 }
40 }
41}
42
43pub async fn fillet(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
45 let solid = args.get_unlabeled_kw_arg("solid")?;
46 let radius = args.get_kw_arg("radius")?;
47 let tolerance = args.get_kw_arg_opt("tolerance")?;
48 let tags = args.get_kw_arg("tags")?;
49 let tag = args.get_kw_arg_opt("tag")?;
50 let value = inner_fillet(solid, radius, tags, tolerance, tag, exec_state, args).await?;
51 Ok(KclValue::Solid { value })
52}
53
54#[stdlib {
111 name = "fillet",
112 feature_tree_operation = true,
113 keywords = true,
114 unlabeled_first = true,
115 args = {
116 solid = { docs = "The solid whose edges should be filletted" },
117 radius = { docs = "The radius of the fillet" },
118 tags = { docs = "The paths you want to fillet" },
119 tolerance = { docs = "The tolerance for this fillet" },
120 tag = { docs = "Create a new tag which refers to this fillet"},
121 }
122}]
123async fn inner_fillet(
124 solid: Box<Solid>,
125 radius: f64,
126 tags: Vec<EdgeReference>,
127 tolerance: Option<f64>,
128 tag: Option<TagNode>,
129 exec_state: &mut ExecState,
130 args: Args,
131) -> Result<Box<Solid>, KclError> {
132 let unique_tags = unique_count(tags.clone());
134 if unique_tags != tags.len() {
135 return Err(KclError::Type(KclErrorDetails {
136 message: "Duplicate tags are not allowed.".to_string(),
137 source_ranges: vec![args.source_range],
138 }));
139 }
140
141 let mut solid = solid.clone();
142 for edge_tag in tags {
143 let edge_id = edge_tag.get_engine_id(exec_state, &args)?;
144
145 let id = exec_state.next_uuid();
146 args.batch_end_cmd(
147 id,
148 ModelingCmd::from(mcmd::Solid3dFilletEdge {
149 edge_id,
150 object_id: solid.id,
151 radius: LengthUnit(radius),
152 tolerance: LengthUnit(tolerance.unwrap_or(default_tolerance(&args.ctx.settings.units))),
153 cut_type: CutType::Fillet,
154 face_id: None,
156 }),
157 )
158 .await?;
159
160 solid.edge_cuts.push(EdgeCut::Fillet {
161 id,
162 edge_id,
163 radius,
164 tag: Box::new(tag.clone()),
165 });
166
167 if let Some(ref tag) = tag {
168 solid.value.push(ExtrudeSurface::Fillet(FilletSurface {
169 face_id: id,
170 tag: Some(tag.clone()),
171 geo_meta: GeoMeta {
172 id,
173 metadata: args.source_range.into(),
174 },
175 }));
176 }
177 }
178
179 Ok(solid)
180}
181
182pub async fn get_opposite_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
184 let tag: TagIdentifier = args.get_data()?;
185
186 let edge = inner_get_opposite_edge(tag, exec_state, args.clone()).await?;
187 Ok(KclValue::Uuid {
188 value: edge,
189 meta: vec![args.source_range.into()],
190 })
191}
192
193#[stdlib {
221 name = "getOppositeEdge",
222}]
223async fn inner_get_opposite_edge(tag: TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<Uuid, KclError> {
224 if args.ctx.no_engine_commands().await {
225 return Ok(exec_state.next_uuid());
226 }
227 let face_id = args.get_adjacent_face_to_tag(exec_state, &tag, false).await?;
228
229 let id = exec_state.next_uuid();
230 let tagged_path = args.get_tag_engine_info(exec_state, &tag)?;
231
232 let resp = args
233 .send_modeling_cmd(
234 id,
235 ModelingCmd::from(mcmd::Solid3dGetOppositeEdge {
236 edge_id: tagged_path.id,
237 object_id: tagged_path.sketch,
238 face_id,
239 }),
240 )
241 .await?;
242 let OkWebSocketResponseData::Modeling {
243 modeling_response: OkModelingCmdResponse::Solid3dGetOppositeEdge(opposite_edge),
244 } = &resp
245 else {
246 return Err(KclError::Engine(KclErrorDetails {
247 message: format!("mcmd::Solid3dGetOppositeEdge response was not as expected: {:?}", resp),
248 source_ranges: vec![args.source_range],
249 }));
250 };
251
252 Ok(opposite_edge.edge)
253}
254
255pub async fn get_next_adjacent_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
257 let tag: TagIdentifier = args.get_data()?;
258
259 let edge = inner_get_next_adjacent_edge(tag, exec_state, args.clone()).await?;
260 Ok(KclValue::Uuid {
261 value: edge,
262 meta: vec![args.source_range.into()],
263 })
264}
265
266#[stdlib {
294 name = "getNextAdjacentEdge",
295}]
296async fn inner_get_next_adjacent_edge(
297 tag: TagIdentifier,
298 exec_state: &mut ExecState,
299 args: Args,
300) -> Result<Uuid, KclError> {
301 if args.ctx.no_engine_commands().await {
302 return Ok(exec_state.next_uuid());
303 }
304 let face_id = args.get_adjacent_face_to_tag(exec_state, &tag, false).await?;
305
306 let id = exec_state.next_uuid();
307 let tagged_path = args.get_tag_engine_info(exec_state, &tag)?;
308
309 let resp = args
310 .send_modeling_cmd(
311 id,
312 ModelingCmd::from(mcmd::Solid3dGetNextAdjacentEdge {
313 edge_id: tagged_path.id,
314 object_id: tagged_path.sketch,
315 face_id,
316 }),
317 )
318 .await?;
319 let OkWebSocketResponseData::Modeling {
320 modeling_response: OkModelingCmdResponse::Solid3dGetNextAdjacentEdge(adjacent_edge),
321 } = &resp
322 else {
323 return Err(KclError::Engine(KclErrorDetails {
324 message: format!(
325 "mcmd::Solid3dGetNextAdjacentEdge response was not as expected: {:?}",
326 resp
327 ),
328 source_ranges: vec![args.source_range],
329 }));
330 };
331
332 adjacent_edge.edge.ok_or_else(|| {
333 KclError::Type(KclErrorDetails {
334 message: format!("No edge found next adjacent to tag: `{}`", tag.value),
335 source_ranges: vec![args.source_range],
336 })
337 })
338}
339
340pub async fn get_previous_adjacent_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
342 let tag: TagIdentifier = args.get_data()?;
343
344 let edge = inner_get_previous_adjacent_edge(tag, exec_state, args.clone()).await?;
345 Ok(KclValue::Uuid {
346 value: edge,
347 meta: vec![args.source_range.into()],
348 })
349}
350
351#[stdlib {
379 name = "getPreviousAdjacentEdge",
380}]
381async fn inner_get_previous_adjacent_edge(
382 tag: TagIdentifier,
383 exec_state: &mut ExecState,
384 args: Args,
385) -> Result<Uuid, KclError> {
386 if args.ctx.no_engine_commands().await {
387 return Ok(exec_state.next_uuid());
388 }
389 let face_id = args.get_adjacent_face_to_tag(exec_state, &tag, false).await?;
390
391 let id = exec_state.next_uuid();
392 let tagged_path = args.get_tag_engine_info(exec_state, &tag)?;
393
394 let resp = args
395 .send_modeling_cmd(
396 id,
397 ModelingCmd::from(mcmd::Solid3dGetPrevAdjacentEdge {
398 edge_id: tagged_path.id,
399 object_id: tagged_path.sketch,
400 face_id,
401 }),
402 )
403 .await?;
404 let OkWebSocketResponseData::Modeling {
405 modeling_response: OkModelingCmdResponse::Solid3dGetPrevAdjacentEdge(adjacent_edge),
406 } = &resp
407 else {
408 return Err(KclError::Engine(KclErrorDetails {
409 message: format!(
410 "mcmd::Solid3dGetPrevAdjacentEdge response was not as expected: {:?}",
411 resp
412 ),
413 source_ranges: vec![args.source_range],
414 }));
415 };
416
417 adjacent_edge.edge.ok_or_else(|| {
418 KclError::Type(KclErrorDetails {
419 message: format!("No edge found previous adjacent to tag: `{}`", tag.value),
420 source_ranges: vec![args.source_range],
421 })
422 })
423}
424
425pub(crate) fn default_tolerance(units: &UnitLength) -> f64 {
426 match units {
427 UnitLength::Mm => 0.0000001,
428 UnitLength::Cm => 0.0000001,
429 UnitLength::In => 0.0000001,
430 UnitLength::Ft => 0.0001,
431 UnitLength::Yd => 0.001,
432 UnitLength::M => 0.001,
433 }
434}