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