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 frame_plane = if let Some(plane) = frame_plane {
186 plane
187 } else {
188 xy_plane(exec_state, args).await?
190 };
191 let frame_plane_id = if frame_plane.value == crate::exec::PlaneType::Uninit {
192 let engine_plane =
194 make_sketch_plane_from_orientation(frame_plane.info.into_plane_data(), exec_state, args).await?;
195 engine_plane.id
196 } else {
197 frame_plane.id
198 };
199 let mut annotations = Vec::with_capacity(faces.len());
200 for face in &faces {
201 let face_id = args.get_adjacent_face_to_tag(exec_state, face, false).await?;
202 let meta = vec![Metadata::from(args.source_range)];
203 let annotation_id = exec_state.next_uuid();
204 exec_state
205 .batch_modeling_cmd(
206 ModelingCmdMeta::from_args_id(args, annotation_id),
207 ModelingCmd::from(mcmd::NewAnnotation {
208 options: AnnotationOptions {
209 text: None,
210 line_ends: None,
211 line_width: None,
212 color: None,
213 position: None,
214 dimension: None,
215 feature_control: Some(AnnotationFeatureControl {
216 entity_id: face_id,
217 entity_pos: KPoint2d { x: 0.5, y: 0.5 },
219 leader_type: AnnotationLineEnd::Dot,
220 dimension: None,
221 control_frame: Some(AnnotationMbdControlFrame {
222 symbol: MbdSymbol::Flatness,
223 diameter_symbol: None,
224 tolerance: tolerance.to_mm(),
225 modifier: None,
226 primary_datum: None,
227 secondary_datum: None,
228 tertiary_datum: None,
229 }),
230 defined_datum: None,
231 prefix: None,
232 suffix: None,
233 plane_id: frame_plane_id,
234 offset: if let Some(offset) = &frame_position {
235 KPoint2d {
236 x: offset[0].to_mm(),
237 y: offset[1].to_mm(),
238 }
239 } else {
240 KPoint2d { x: 100.0, y: 100.0 }
241 },
242 precision: precision.as_ref().map(|n| n.n.round() as u32).unwrap_or(3),
243 font_scale: style.font_scale.as_ref().map(|n| n.n as f32).unwrap_or(1.0),
244 font_point_size: style.font_point_size.as_ref().map(|n| n.n.round() as u32).unwrap_or(36),
245 }),
246 feature_tag: None,
247 },
248 clobber: false,
249 annotation_type: AnnotationType::T3D,
250 }),
251 )
252 .await?;
253 annotations.push(GdtAnnotation {
254 id: annotation_id,
255 meta,
256 });
257 }
258 Ok(annotations)
259}
260
261async fn xy_plane(exec_state: &mut ExecState, args: &Args) -> Result<Plane, KclError> {
264 let plane_ast = plane_ast("XY", args.source_range);
265 let metadata = Metadata::from(args.source_range);
266 let plane_value = args
267 .ctx
268 .execute_expr(&plane_ast, exec_state, &metadata, &[], StatementKind::Expression)
269 .await?
270 .clone();
271 Ok(plane_value
272 .as_plane()
273 .ok_or_else(|| {
274 KclError::new_internal(KclErrorDetails::new(
275 "Expected XY plane to be defined".to_owned(),
276 vec![args.source_range],
277 ))
278 })?
279 .clone())
280}
281
282fn plane_ast(plane_name: &str, range: SourceRange) -> ast::Node<ast::Expr> {
284 ast::Node::new(
285 ast::Expr::Name(Box::new(ast::Node::new(
286 ast::Name {
287 name: ast::Identifier::new(plane_name),
288 path: Vec::new(),
289 abs_path: false,
292 digest: None,
293 },
294 range.start(),
295 range.end(),
296 range.module_id(),
297 ))),
298 range.start(),
299 range.end(),
300 range.module_id(),
301 )
302}