1use kcl_error::SourceRange;
2use kcmc::{ModelingCmd, each_cmd as mcmd};
3use kittycad_modeling_cmds::{
4 self as kcmc,
5 shared::{
6 AnnotationFeatureControl, AnnotationLineEnd, AnnotationMbdControlFrame, AnnotationOptions, AnnotationType,
7 MbdSymbol, Point2d as KPoint2d,
8 },
9};
10
11use crate::{
12 ExecState, KclError,
13 errors::KclErrorDetails,
14 exec::KclValue,
15 execution::{
16 GdtAnnotation, Metadata, ModelingCmdMeta, Plane, StatementKind, TagIdentifier,
17 types::{ArrayLen, RuntimeType},
18 },
19 parsing::ast::types as ast,
20 std::{Args, args::TyF64, sketch::make_sketch_plane_from_orientation},
21};
22
23#[derive(Debug, Clone)]
25pub(crate) struct AnnotationStyle {
26 pub font_point_size: Option<TyF64>,
27 pub font_scale: Option<TyF64>,
28}
29
30pub async fn datum(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
31 let face: TagIdentifier = args.get_kw_arg("face", &RuntimeType::tagged_face(), exec_state)?;
32 let name: String = args.get_kw_arg("name", &RuntimeType::string(), exec_state)?;
33 let frame_position: Option<[TyF64; 2]> =
34 args.get_kw_arg_opt("framePosition", &RuntimeType::point2d(), exec_state)?;
35 let frame_plane: Option<Plane> = args.get_kw_arg_opt("framePlane", &RuntimeType::plane(), exec_state)?;
36 let font_point_size: Option<TyF64> = args.get_kw_arg_opt("fontPointSize", &RuntimeType::count(), exec_state)?;
37 let font_scale: Option<TyF64> = args.get_kw_arg_opt("fontScale", &RuntimeType::count(), exec_state)?;
38
39 let annotation = inner_datum(
40 face,
41 name,
42 frame_position,
43 frame_plane,
44 AnnotationStyle {
45 font_point_size,
46 font_scale,
47 },
48 exec_state,
49 &args,
50 )
51 .await?;
52 Ok(KclValue::GdtAnnotation {
53 value: Box::new(annotation),
54 })
55}
56
57async fn inner_datum(
58 face: TagIdentifier,
59 name: String,
60 frame_position: Option<[TyF64; 2]>,
61 frame_plane: Option<Plane>,
62 style: AnnotationStyle,
63 exec_state: &mut ExecState,
64 args: &Args,
65) -> Result<GdtAnnotation, KclError> {
66 const DATUM_LENGTH_ERROR: &str = "Datum name must be a single character.";
67 if name.len() > 1 {
68 return Err(KclError::new_semantic(KclErrorDetails::new(
69 DATUM_LENGTH_ERROR.to_owned(),
70 vec![args.source_range],
71 )));
72 }
73 let name_char = name.chars().next().ok_or_else(|| {
74 KclError::new_semantic(KclErrorDetails::new(
75 DATUM_LENGTH_ERROR.to_owned(),
76 vec![args.source_range],
77 ))
78 })?;
79 let frame_plane = if let Some(plane) = frame_plane {
80 plane
81 } else {
82 xy_plane(exec_state, args).await?
84 };
85 let frame_plane_id = if frame_plane.value == crate::exec::PlaneType::Uninit {
86 let engine_plane =
88 make_sketch_plane_from_orientation(frame_plane.info.into_plane_data(), exec_state, args).await?;
89 engine_plane.id
90 } else {
91 frame_plane.id
92 };
93 let face_id = args.get_adjacent_face_to_tag(exec_state, &face, false).await?;
94 let meta = vec![Metadata::from(args.source_range)];
95 let annotation_id = exec_state.next_uuid();
96 exec_state
97 .batch_modeling_cmd(
98 ModelingCmdMeta::from_args_id(args, annotation_id),
99 ModelingCmd::from(mcmd::NewAnnotation {
100 options: AnnotationOptions {
101 text: None,
102 line_ends: None,
103 line_width: None,
104 color: None,
105 position: None,
106 dimension: None,
107 feature_control: Some(AnnotationFeatureControl {
108 entity_id: face_id,
109 entity_pos: KPoint2d { x: 0.5, y: 0.5 },
111 leader_type: AnnotationLineEnd::Dot,
112 dimension: None,
113 control_frame: None,
114 defined_datum: Some(name_char),
115 prefix: None,
116 suffix: None,
117 plane_id: frame_plane_id,
118 offset: if let Some(offset) = &frame_position {
119 KPoint2d {
120 x: offset[0].to_mm(),
121 y: offset[1].to_mm(),
122 }
123 } else {
124 KPoint2d { x: 100.0, y: 100.0 }
125 },
126 precision: 0,
127 font_scale: style.font_scale.as_ref().map(|n| n.n as f32).unwrap_or(1.0),
128 font_point_size: style.font_point_size.as_ref().map(|n| n.n.round() as u32).unwrap_or(36),
129 }),
130 feature_tag: None,
131 },
132 clobber: false,
133 annotation_type: AnnotationType::T3D,
134 }),
135 )
136 .await?;
137 Ok(GdtAnnotation {
138 id: annotation_id,
139 meta,
140 })
141}
142
143pub async fn flatness(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
144 let faces: Vec<TagIdentifier> = args.get_kw_arg(
145 "faces",
146 &RuntimeType::Array(Box::new(RuntimeType::tagged_face()), ArrayLen::Minimum(1)),
147 exec_state,
148 )?;
149 let tolerance = args.get_kw_arg("tolerance", &RuntimeType::length(), exec_state)?;
150 let precision = args.get_kw_arg_opt("precision", &RuntimeType::count(), exec_state)?;
151 let frame_position: Option<[TyF64; 2]> =
152 args.get_kw_arg_opt("framePosition", &RuntimeType::point2d(), exec_state)?;
153 let frame_plane: Option<Plane> = args.get_kw_arg_opt("framePlane", &RuntimeType::plane(), exec_state)?;
154 let font_point_size: Option<TyF64> = args.get_kw_arg_opt("fontPointSize", &RuntimeType::count(), exec_state)?;
155 let font_scale: Option<TyF64> = args.get_kw_arg_opt("fontScale", &RuntimeType::count(), exec_state)?;
156
157 let annotations = inner_flatness(
158 faces,
159 tolerance,
160 precision,
161 frame_position,
162 frame_plane,
163 AnnotationStyle {
164 font_point_size,
165 font_scale,
166 },
167 exec_state,
168 &args,
169 )
170 .await?;
171 Ok(annotations.into())
172}
173
174#[allow(clippy::too_many_arguments)]
175async fn inner_flatness(
176 faces: Vec<TagIdentifier>,
177 tolerance: TyF64,
178 precision: Option<TyF64>,
179 frame_position: Option<[TyF64; 2]>,
180 frame_plane: Option<Plane>,
181 style: AnnotationStyle,
182 exec_state: &mut ExecState,
183 args: &Args,
184) -> Result<Vec<GdtAnnotation>, KclError> {
185 let precision = if let Some(precision) = precision {
186 let rounded = precision.n.round();
187 if !(0.0..=9.0).contains(&rounded) {
188 return Err(KclError::new_semantic(KclErrorDetails::new(
189 "Precision must be between 0 and 9".to_owned(),
190 vec![args.source_range],
191 )));
192 }
193 rounded as u32
194 } else {
195 3
197 };
198 let frame_plane = if let Some(plane) = frame_plane {
199 plane
200 } else {
201 xy_plane(exec_state, args).await?
203 };
204 let frame_plane_id = if frame_plane.value == crate::exec::PlaneType::Uninit {
205 let engine_plane =
207 make_sketch_plane_from_orientation(frame_plane.info.into_plane_data(), exec_state, args).await?;
208 engine_plane.id
209 } else {
210 frame_plane.id
211 };
212 let mut annotations = Vec::with_capacity(faces.len());
213 for face in &faces {
214 let face_id = args.get_adjacent_face_to_tag(exec_state, face, false).await?;
215 let meta = vec![Metadata::from(args.source_range)];
216 let annotation_id = exec_state.next_uuid();
217 exec_state
218 .batch_modeling_cmd(
219 ModelingCmdMeta::from_args_id(args, annotation_id),
220 ModelingCmd::from(mcmd::NewAnnotation {
221 options: AnnotationOptions {
222 text: None,
223 line_ends: None,
224 line_width: None,
225 color: None,
226 position: None,
227 dimension: None,
228 feature_control: Some(AnnotationFeatureControl {
229 entity_id: face_id,
230 entity_pos: KPoint2d { x: 0.5, y: 0.5 },
232 leader_type: AnnotationLineEnd::Dot,
233 dimension: None,
234 control_frame: Some(AnnotationMbdControlFrame {
235 symbol: MbdSymbol::Flatness,
236 diameter_symbol: None,
237 tolerance: tolerance.to_mm(),
238 modifier: None,
239 primary_datum: None,
240 secondary_datum: None,
241 tertiary_datum: None,
242 }),
243 defined_datum: None,
244 prefix: None,
245 suffix: None,
246 plane_id: frame_plane_id,
247 offset: if let Some(offset) = &frame_position {
248 KPoint2d {
249 x: offset[0].to_mm(),
250 y: offset[1].to_mm(),
251 }
252 } else {
253 KPoint2d { x: 100.0, y: 100.0 }
254 },
255 precision,
256 font_scale: style.font_scale.as_ref().map(|n| n.n as f32).unwrap_or(1.0),
257 font_point_size: style.font_point_size.as_ref().map(|n| n.n.round() as u32).unwrap_or(36),
258 }),
259 feature_tag: None,
260 },
261 clobber: false,
262 annotation_type: AnnotationType::T3D,
263 }),
264 )
265 .await?;
266 annotations.push(GdtAnnotation {
267 id: annotation_id,
268 meta,
269 });
270 }
271 Ok(annotations)
272}
273
274async fn xy_plane(exec_state: &mut ExecState, args: &Args) -> Result<Plane, KclError> {
277 let plane_ast = plane_ast("XY", args.source_range);
278 let metadata = Metadata::from(args.source_range);
279 let plane_value = args
280 .ctx
281 .execute_expr(&plane_ast, exec_state, &metadata, &[], StatementKind::Expression)
282 .await?
283 .clone();
284 Ok(plane_value
285 .as_plane()
286 .ok_or_else(|| {
287 KclError::new_internal(KclErrorDetails::new(
288 "Expected XY plane to be defined".to_owned(),
289 vec![args.source_range],
290 ))
291 })?
292 .clone())
293}
294
295fn plane_ast(plane_name: &str, range: SourceRange) -> ast::Node<ast::Expr> {
297 ast::Node::new(
298 ast::Expr::Name(Box::new(ast::Node::new(
299 ast::Name {
300 name: ast::Identifier::new(plane_name),
301 path: Vec::new(),
302 abs_path: false,
305 digest: None,
306 },
307 range.start(),
308 range.end(),
309 range.module_id(),
310 ))),
311 range.start(),
312 range.end(),
313 range.module_id(),
314 )
315}