1use anyhow::Result;
4use kcl_derive_docs::stdlib;
5use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, shared::CutType, ModelingCmd};
6use kittycad_modeling_cmds as kcmc;
7
8use super::utils::unique_count;
9use crate::{
10 errors::{KclError, KclErrorDetails},
11 execution::{ChamferSurface, EdgeCut, ExecState, ExtrudeSurface, GeoMeta, KclValue, Solid},
12 parsing::ast::types::TagNode,
13 std::{fillet::EdgeReference, Args},
14};
15
16pub(crate) const DEFAULT_TOLERANCE: f64 = 0.0000001;
17
18pub async fn chamfer(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
20 let solid = args.get_unlabeled_kw_arg("solid")?;
21 let length = args.get_kw_arg("length")?;
22 let tags = args.get_kw_arg("tags")?;
23 let tag = args.get_kw_arg_opt("tag")?;
24
25 let value = inner_chamfer(solid, length, tags, tag, exec_state, args).await?;
26 Ok(KclValue::Solid { value })
27}
28
29#[stdlib {
93 name = "chamfer",
94 feature_tree_operation = true,
95 keywords = true,
96 unlabeled_first = true,
97 args = {
98 solid = { docs = "The solid whose edges should be chamfered" },
99 length = { docs = "The length of the chamfer" },
100 tags = { docs = "The paths you want to chamfer" },
101 tag = { docs = "Create a new tag which refers to this chamfer"},
102 }
103}]
104async fn inner_chamfer(
105 solid: Box<Solid>,
106 length: f64,
107 tags: Vec<EdgeReference>,
108 tag: Option<TagNode>,
109 exec_state: &mut ExecState,
110 args: Args,
111) -> Result<Box<Solid>, KclError> {
112 let unique_tags = unique_count(tags.clone());
114 if unique_tags != tags.len() {
115 return Err(KclError::Type(KclErrorDetails {
116 message: "Duplicate tags are not allowed.".to_string(),
117 source_ranges: vec![args.source_range],
118 }));
119 }
120
121 if tag.is_some() && tags.len() > 1 {
124 return Err(KclError::Type(KclErrorDetails {
125 message: "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(),
126 source_ranges: vec![args.source_range],
127 }));
128 }
129
130 let mut solid = solid.clone();
131 for edge_tag in tags {
132 let edge_id = match edge_tag {
133 EdgeReference::Uuid(uuid) => uuid,
134 EdgeReference::Tag(edge_tag) => args.get_tag_engine_info(exec_state, &edge_tag)?.id,
135 };
136
137 let id = exec_state.next_uuid();
138 args.batch_end_cmd(
139 id,
140 ModelingCmd::from(mcmd::Solid3dFilletEdge {
141 edge_id,
142 object_id: solid.id,
143 radius: LengthUnit(length),
144 tolerance: LengthUnit(DEFAULT_TOLERANCE), cut_type: CutType::Chamfer,
146 face_id: None,
148 }),
149 )
150 .await?;
151
152 solid.edge_cuts.push(EdgeCut::Chamfer {
153 id,
154 edge_id,
155 length,
156 tag: Box::new(tag.clone()),
157 });
158
159 if let Some(ref tag) = tag {
160 solid.value.push(ExtrudeSurface::Chamfer(ChamferSurface {
161 face_id: id,
162 tag: Some(tag.clone()),
163 geo_meta: GeoMeta {
164 id,
165 metadata: args.source_range.into(),
166 },
167 }));
168 }
169 }
170
171 Ok(solid)
172}