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 ControlFlowKind, GdtAnnotation, Metadata, ModelingCmdMeta, Plane, StatementKind, TagIdentifier,
17 types::{ArrayLen, RuntimeType},
18 },
19 parsing::ast::types as ast,
20 std::{Args, args::TyF64, sketch::ensure_sketch_plane_in_engine},
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 leader_scale: Option<TyF64> = args.get_kw_arg_opt("leaderScale", &RuntimeType::count(), exec_state)?;
37 let font_point_size: Option<TyF64> = args.get_kw_arg_opt("fontPointSize", &RuntimeType::count(), exec_state)?;
38 let font_scale: Option<TyF64> = args.get_kw_arg_opt("fontScale", &RuntimeType::count(), exec_state)?;
39
40 let annotation = inner_datum(
41 face,
42 name,
43 frame_position,
44 frame_plane,
45 leader_scale,
46 AnnotationStyle {
47 font_point_size,
48 font_scale,
49 },
50 exec_state,
51 &args,
52 )
53 .await?;
54 Ok(KclValue::GdtAnnotation {
55 value: Box::new(annotation),
56 })
57}
58
59#[allow(clippy::too_many_arguments)]
60async fn inner_datum(
61 face: TagIdentifier,
62 name: String,
63 frame_position: Option<[TyF64; 2]>,
64 frame_plane: Option<Plane>,
65 leader_scale: Option<TyF64>,
66 style: AnnotationStyle,
67 exec_state: &mut ExecState,
68 args: &Args,
69) -> Result<GdtAnnotation, KclError> {
70 const DATUM_LENGTH_ERROR: &str = "Datum name must be a single character.";
71 if name.len() > 1 {
72 return Err(KclError::new_semantic(KclErrorDetails::new(
73 DATUM_LENGTH_ERROR.to_owned(),
74 vec![args.source_range],
75 )));
76 }
77 let name_char = name.chars().next().ok_or_else(|| {
78 KclError::new_semantic(KclErrorDetails::new(
79 DATUM_LENGTH_ERROR.to_owned(),
80 vec![args.source_range],
81 ))
82 })?;
83 let mut frame_plane = if let Some(plane) = frame_plane {
84 plane
85 } else {
86 xy_plane(exec_state, args).await?
88 };
89 ensure_sketch_plane_in_engine(&mut frame_plane, exec_state, args).await?;
90 let face_id = args.get_adjacent_face_to_tag(exec_state, &face, false).await?;
91 let meta = vec![Metadata::from(args.source_range)];
92 let annotation_id = exec_state.next_uuid();
93 let feature_control = AnnotationFeatureControl::builder()
94 .entity_id(face_id)
95 .entity_pos(KPoint2d { x: 0.5, y: 0.5 })
97 .leader_type(AnnotationLineEnd::Dot)
98 .defined_datum(name_char)
99 .plane_id(frame_plane.id)
100 .offset(if let Some(offset) = &frame_position {
101 KPoint2d {
102 x: offset[0].to_mm(),
103 y: offset[1].to_mm(),
104 }
105 } else {
106 KPoint2d { x: 100.0, y: 100.0 }
107 })
108 .precision(0)
109 .font_scale(style.font_scale.as_ref().map(|n| n.n as f32).unwrap_or(1.0))
110 .font_point_size(style.font_point_size.as_ref().map(|n| n.n.round() as u32).unwrap_or(36))
111 .leader_scale(leader_scale.as_ref().map(|n| n.n as f32).unwrap_or(1.0))
112 .build();
113 exec_state
114 .batch_modeling_cmd(
115 ModelingCmdMeta::from_args_id(exec_state, args, annotation_id),
116 ModelingCmd::from(
117 mcmd::NewAnnotation::builder()
118 .options(AnnotationOptions::builder().feature_control(feature_control).build())
119 .clobber(false)
120 .annotation_type(AnnotationType::T3D)
121 .build(),
122 ),
123 )
124 .await?;
125 Ok(GdtAnnotation {
126 id: annotation_id,
127 meta,
128 })
129}
130
131pub async fn flatness(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
132 let faces: Vec<TagIdentifier> = args.get_kw_arg(
133 "faces",
134 &RuntimeType::Array(Box::new(RuntimeType::tagged_face()), ArrayLen::Minimum(1)),
135 exec_state,
136 )?;
137 let tolerance = args.get_kw_arg("tolerance", &RuntimeType::length(), exec_state)?;
138 let precision = args.get_kw_arg_opt("precision", &RuntimeType::count(), exec_state)?;
139 let frame_position: Option<[TyF64; 2]> =
140 args.get_kw_arg_opt("framePosition", &RuntimeType::point2d(), exec_state)?;
141 let frame_plane: Option<Plane> = args.get_kw_arg_opt("framePlane", &RuntimeType::plane(), exec_state)?;
142 let leader_scale: Option<TyF64> = args.get_kw_arg_opt("leaderScale", &RuntimeType::count(), exec_state)?;
143 let font_point_size: Option<TyF64> = args.get_kw_arg_opt("fontPointSize", &RuntimeType::count(), exec_state)?;
144 let font_scale: Option<TyF64> = args.get_kw_arg_opt("fontScale", &RuntimeType::count(), exec_state)?;
145
146 let annotations = inner_flatness(
147 faces,
148 tolerance,
149 precision,
150 frame_position,
151 frame_plane,
152 leader_scale,
153 AnnotationStyle {
154 font_point_size,
155 font_scale,
156 },
157 exec_state,
158 &args,
159 )
160 .await?;
161 Ok(annotations.into())
162}
163
164#[allow(clippy::too_many_arguments)]
165async fn inner_flatness(
166 faces: Vec<TagIdentifier>,
167 tolerance: TyF64,
168 precision: Option<TyF64>,
169 frame_position: Option<[TyF64; 2]>,
170 frame_plane: Option<Plane>,
171 leader_scale: Option<TyF64>,
172 style: AnnotationStyle,
173 exec_state: &mut ExecState,
174 args: &Args,
175) -> Result<Vec<GdtAnnotation>, KclError> {
176 let precision = if let Some(precision) = precision {
177 let rounded = precision.n.round();
178 if !(0.0..=9.0).contains(&rounded) {
179 return Err(KclError::new_semantic(KclErrorDetails::new(
180 "Precision must be between 0 and 9".to_owned(),
181 vec![args.source_range],
182 )));
183 }
184 rounded as u32
185 } else {
186 3
188 };
189 let mut frame_plane = if let Some(plane) = frame_plane {
190 plane
191 } else {
192 xy_plane(exec_state, args).await?
194 };
195 ensure_sketch_plane_in_engine(&mut frame_plane, exec_state, args).await?;
196 let mut annotations = Vec::with_capacity(faces.len());
197 for face in &faces {
198 let face_id = args.get_adjacent_face_to_tag(exec_state, face, false).await?;
199 let meta = vec![Metadata::from(args.source_range)];
200 let annotation_id = exec_state.next_uuid();
201 let feature_control = AnnotationFeatureControl::builder()
202 .entity_id(face_id)
203 .entity_pos(KPoint2d { x: 0.5, y: 0.5 })
205 .leader_type(AnnotationLineEnd::Dot)
206 .control_frame(
207 AnnotationMbdControlFrame::builder()
208 .symbol(MbdSymbol::Flatness)
209 .tolerance(tolerance.to_mm())
210 .build(),
211 )
212 .plane_id(frame_plane.id)
213 .offset(if let Some(offset) = &frame_position {
214 KPoint2d {
215 x: offset[0].to_mm(),
216 y: offset[1].to_mm(),
217 }
218 } else {
219 KPoint2d { x: 100.0, y: 100.0 }
220 })
221 .precision(precision)
222 .font_scale(style.font_scale.as_ref().map(|n| n.n as f32).unwrap_or(1.0))
223 .font_point_size(style.font_point_size.as_ref().map(|n| n.n.round() as u32).unwrap_or(36))
224 .leader_scale(leader_scale.as_ref().map(|n| n.n as f32).unwrap_or(1.0))
225 .build();
226 let options = AnnotationOptions::builder().feature_control(feature_control).build();
227 exec_state
228 .batch_modeling_cmd(
229 ModelingCmdMeta::from_args_id(exec_state, args, annotation_id),
230 ModelingCmd::from(
231 mcmd::NewAnnotation::builder()
232 .options(options)
233 .clobber(false)
234 .annotation_type(AnnotationType::T3D)
235 .build(),
236 ),
237 )
238 .await?;
239 annotations.push(GdtAnnotation {
240 id: annotation_id,
241 meta,
242 });
243 }
244 Ok(annotations)
245}
246
247async fn xy_plane(exec_state: &mut ExecState, args: &Args) -> Result<Plane, KclError> {
250 let plane_ast = plane_ast("XY", args.source_range);
251 let metadata = Metadata::from(args.source_range);
252 let plane_value = args
253 .ctx
254 .execute_expr(&plane_ast, exec_state, &metadata, &[], StatementKind::Expression)
255 .await?;
256 let plane_value = match plane_value.control {
257 ControlFlowKind::Continue => plane_value.into_value(),
258 ControlFlowKind::Exit => {
259 let message = "Early return inside plane value is currently not supported".to_owned();
260 debug_assert!(false, "{}", &message);
261 return Err(KclError::new_internal(KclErrorDetails::new(
262 message,
263 vec![args.source_range],
264 )));
265 }
266 };
267 Ok(plane_value
268 .as_plane()
269 .ok_or_else(|| {
270 KclError::new_internal(KclErrorDetails::new(
271 "Expected XY plane to be defined".to_owned(),
272 vec![args.source_range],
273 ))
274 })?
275 .clone())
276}
277
278fn plane_ast(plane_name: &str, range: SourceRange) -> ast::Node<ast::Expr> {
280 ast::Node::new(
281 ast::Expr::Name(Box::new(ast::Node::new(
282 ast::Name {
283 name: ast::Identifier::new(plane_name),
284 path: Vec::new(),
285 abs_path: false,
288 digest: None,
289 },
290 range.start(),
291 range.end(),
292 range.module_id(),
293 ))),
294 range.start(),
295 range.end(),
296 range.module_id(),
297 )
298}