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 crate::{
9 errors::{KclError, KclErrorDetails},
10 execution::{
11 kcl_value::RuntimeType, ChamferSurface, EdgeCut, ExecState, ExtrudeSurface, GeoMeta, KclValue, PrimitiveType,
12 Solid,
13 },
14 parsing::ast::types::TagNode,
15 std::{fillet::EdgeReference, Args},
16};
17
18pub(crate) const DEFAULT_TOLERANCE: f64 = 0.0000001;
19
20pub async fn chamfer(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
22 let solid = args.get_unlabeled_kw_arg_typed("solid", &RuntimeType::Primitive(PrimitiveType::Solid), exec_state)?;
23 let length = args.get_kw_arg("length")?;
24 let tags = args.kw_arg_array_and_source::<EdgeReference>("tags")?;
25 let tag = args.get_kw_arg_opt("tag")?;
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
33#[stdlib {
97 name = "chamfer",
98 feature_tree_operation = true,
99 keywords = true,
100 unlabeled_first = true,
101 args = {
102 solid = { docs = "The solid whose edges should be chamfered" },
103 length = { docs = "The length of the chamfer" },
104 tags = { docs = "The paths you want to chamfer" },
105 tag = { docs = "Create a new tag which refers to this chamfer"},
106 }
107}]
108async fn inner_chamfer(
109 solid: Box<Solid>,
110 length: f64,
111 tags: Vec<EdgeReference>,
112 tag: Option<TagNode>,
113 exec_state: &mut ExecState,
114 args: Args,
115) -> Result<Box<Solid>, KclError> {
116 if tag.is_some() && tags.len() > 1 {
119 return Err(KclError::Type(KclErrorDetails {
120 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(),
121 source_ranges: vec![args.source_range],
122 }));
123 }
124
125 let mut solid = solid.clone();
126 for edge_tag in tags {
127 let edge_id = match edge_tag {
128 EdgeReference::Uuid(uuid) => uuid,
129 EdgeReference::Tag(edge_tag) => args.get_tag_engine_info(exec_state, &edge_tag)?.id,
130 };
131
132 let id = exec_state.next_uuid();
133 args.batch_end_cmd(
134 id,
135 ModelingCmd::from(mcmd::Solid3dFilletEdge {
136 edge_id,
137 object_id: solid.id,
138 radius: LengthUnit(length),
139 tolerance: LengthUnit(DEFAULT_TOLERANCE), cut_type: CutType::Chamfer,
141 face_id: None,
143 }),
144 )
145 .await?;
146
147 solid.edge_cuts.push(EdgeCut::Chamfer {
148 id,
149 edge_id,
150 length,
151 tag: Box::new(tag.clone()),
152 });
153
154 if let Some(ref tag) = tag {
155 solid.value.push(ExtrudeSurface::Chamfer(ChamferSurface {
156 face_id: id,
157 tag: Some(tag.clone()),
158 geo_meta: GeoMeta {
159 id,
160 metadata: args.source_range.into(),
161 },
162 }));
163 }
164 }
165
166 Ok(solid)
167}