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