kcl_lib/std/
chamfer.rs

1//! Standard library chamfers.
2
3use anyhow::Result;
4use kcmc::{ModelingCmd, each_cmd as mcmd, length_unit::LengthUnit, shared::CutType};
5use kittycad_modeling_cmds as kcmc;
6
7use super::args::TyF64;
8use crate::{
9    errors::{KclError, KclErrorDetails},
10    execution::{
11        ChamferSurface, EdgeCut, ExecState, ExtrudeSurface, GeoMeta, KclValue, ModelingCmdMeta, Solid,
12        types::RuntimeType,
13    },
14    parsing::ast::types::TagNode,
15    std::{Args, fillet::EdgeReference},
16};
17
18pub(crate) const DEFAULT_TOLERANCE: f64 = 0.0000001;
19
20/// Create chamfers on tagged paths.
21pub async fn chamfer(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
22    let solid = args.get_unlabeled_kw_arg("solid", &RuntimeType::solid(), exec_state)?;
23    let length: TyF64 = args.get_kw_arg("length", &RuntimeType::length(), exec_state)?;
24    let tags = args.kw_arg_edge_array_and_source("tags")?;
25    let tag = args.get_kw_arg_opt("tag", &RuntimeType::tag_decl(), exec_state)?;
26
27    super::fillet::validate_unique(&tags)?;
28    let tags: Vec<EdgeReference> = tags.into_iter().map(|item| item.0).collect();
29    let value = inner_chamfer(solid, length, tags, tag, exec_state, args).await?;
30    Ok(KclValue::Solid { value })
31}
32
33async fn inner_chamfer(
34    solid: Box<Solid>,
35    length: TyF64,
36    tags: Vec<EdgeReference>,
37    tag: Option<TagNode>,
38    exec_state: &mut ExecState,
39    args: Args,
40) -> Result<Box<Solid>, KclError> {
41    // If you try and tag multiple edges with a tagged chamfer, we want to return an
42    // error to the user that they can only tag one edge at a time.
43    if tag.is_some() && tags.len() > 1 {
44        return Err(KclError::new_type(KclErrorDetails::new(
45            "You can only tag one edge at a time with a tagged chamfer. Either delete the tag for the chamfer fn if you don't need it OR separate into individual chamfer functions for each tag.".to_string(),
46            vec![args.source_range],
47        )));
48    }
49
50    let mut solid = solid.clone();
51    for edge_tag in tags {
52        let edge_id = match edge_tag {
53            EdgeReference::Uuid(uuid) => uuid,
54            EdgeReference::Tag(edge_tag) => args.get_tag_engine_info(exec_state, &edge_tag)?.id,
55        };
56
57        let id = exec_state.next_uuid();
58        exec_state
59            .batch_end_cmd(
60                ModelingCmdMeta::from_args_id(&args, id),
61                ModelingCmd::from(mcmd::Solid3dFilletEdge {
62                    edge_id: None,
63                    edge_ids: vec![edge_id],
64                    extra_face_ids: vec![],
65                    strategy: Default::default(),
66                    object_id: solid.id,
67                    radius: LengthUnit(length.to_mm()),
68                    tolerance: LengthUnit(DEFAULT_TOLERANCE), // We can let the user set this in the future.
69                    cut_type: CutType::Chamfer,
70                }),
71            )
72            .await?;
73
74        solid.edge_cuts.push(EdgeCut::Chamfer {
75            id,
76            edge_id,
77            length: length.clone(),
78            tag: Box::new(tag.clone()),
79        });
80
81        if let Some(ref tag) = tag {
82            solid.value.push(ExtrudeSurface::Chamfer(ChamferSurface {
83                face_id: id,
84                tag: Some(tag.clone()),
85                geo_meta: GeoMeta {
86                    id,
87                    metadata: args.source_range.into(),
88                },
89            }));
90        }
91    }
92
93    Ok(solid)
94}