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 Metadata, 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 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::none())
53}
54
55async fn inner_datum(
56 face: TagIdentifier,
57 name: String,
58 frame_position: Option<[TyF64; 2]>,
59 frame_plane: Option<Plane>,
60 style: AnnotationStyle,
61 exec_state: &mut ExecState,
62 args: &Args,
63) -> Result<(), KclError> {
64 const DATUM_LENGTH_ERROR: &str = "Datum name must be a single character.";
65 if name.len() > 1 {
66 return Err(KclError::new_semantic(KclErrorDetails::new(
67 DATUM_LENGTH_ERROR.to_owned(),
68 vec![args.source_range],
69 )));
70 }
71 let name_char = name.chars().next().ok_or_else(|| {
72 KclError::new_semantic(KclErrorDetails::new(
73 DATUM_LENGTH_ERROR.to_owned(),
74 vec![args.source_range],
75 ))
76 })?;
77 let frame_plane = if let Some(plane) = frame_plane {
78 plane
79 } else {
80 xy_plane(exec_state, args).await?
82 };
83 let frame_plane_id = if frame_plane.value == crate::exec::PlaneType::Uninit {
84 let engine_plane =
86 make_sketch_plane_from_orientation(frame_plane.info.into_plane_data(), exec_state, args).await?;
87 engine_plane.id
88 } else {
89 frame_plane.id
90 };
91 let face_id = args.get_adjacent_face_to_tag(exec_state, &face, false).await?;
92 exec_state
93 .batch_modeling_cmd(
94 args.into(),
95 ModelingCmd::from(mcmd::NewAnnotation {
96 options: AnnotationOptions {
97 text: None,
98 line_ends: None,
99 line_width: None,
100 color: None,
101 position: None,
102 dimension: None,
103 feature_control: Some(AnnotationFeatureControl {
104 entity_id: face_id,
105 entity_pos: KPoint2d { x: 0.5, y: 0.5 },
107 leader_type: AnnotationLineEnd::Dot,
108 dimension: None,
109 control_frame: None,
110 defined_datum: Some(name_char),
111 prefix: None,
112 suffix: None,
113 plane_id: frame_plane_id,
114 offset: if let Some(offset) = &frame_position {
115 KPoint2d {
116 x: offset[0].to_mm(),
117 y: offset[1].to_mm(),
118 }
119 } else {
120 KPoint2d { x: 100.0, y: 100.0 }
121 },
122 precision: 0,
123 font_scale: style.font_scale.as_ref().map(|n| n.n as f32).unwrap_or(1.0),
124 font_point_size: style.font_point_size.as_ref().map(|n| n.n.round() as u32).unwrap_or(36),
125 }),
126 feature_tag: None,
127 },
128 clobber: false,
129 annotation_type: AnnotationType::T3D,
130 }),
131 )
132 .await?;
133 Ok(())
134}
135
136pub async fn flatness(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
137 let faces: Vec<TagIdentifier> = args.get_kw_arg(
138 "faces",
139 &RuntimeType::Array(Box::new(RuntimeType::tagged_face()), ArrayLen::Minimum(1)),
140 exec_state,
141 )?;
142 let tolerance = args.get_kw_arg("tolerance", &RuntimeType::length(), exec_state)?;
143 let precision = args.get_kw_arg_opt("precision", &RuntimeType::count(), exec_state)?;
144 let frame_position: Option<[TyF64; 2]> =
145 args.get_kw_arg_opt("framePosition", &RuntimeType::point2d(), exec_state)?;
146 let frame_plane: Option<Plane> = args.get_kw_arg_opt("framePlane", &RuntimeType::plane(), exec_state)?;
147 let font_point_size: Option<TyF64> = args.get_kw_arg_opt("fontPointSize", &RuntimeType::count(), exec_state)?;
148 let font_scale: Option<TyF64> = args.get_kw_arg_opt("fontScale", &RuntimeType::count(), exec_state)?;
149
150 inner_flatness(
151 faces,
152 tolerance,
153 precision,
154 frame_position,
155 frame_plane,
156 AnnotationStyle {
157 font_point_size,
158 font_scale,
159 },
160 exec_state,
161 &args,
162 )
163 .await?;
164 Ok(KclValue::none())
165}
166
167#[allow(clippy::too_many_arguments)]
168async fn inner_flatness(
169 faces: Vec<TagIdentifier>,
170 tolerance: TyF64,
171 precision: Option<TyF64>,
172 frame_position: Option<[TyF64; 2]>,
173 frame_plane: Option<Plane>,
174 style: AnnotationStyle,
175 exec_state: &mut ExecState,
176 args: &Args,
177) -> Result<(), KclError> {
178 let frame_plane = if let Some(plane) = frame_plane {
179 plane
180 } else {
181 xy_plane(exec_state, args).await?
183 };
184 let frame_plane_id = if frame_plane.value == crate::exec::PlaneType::Uninit {
185 let engine_plane =
187 make_sketch_plane_from_orientation(frame_plane.info.into_plane_data(), exec_state, args).await?;
188 engine_plane.id
189 } else {
190 frame_plane.id
191 };
192 for face in &faces {
193 let face_id = args.get_adjacent_face_to_tag(exec_state, face, false).await?;
194 exec_state
195 .batch_modeling_cmd(
196 args.into(),
197 ModelingCmd::from(mcmd::NewAnnotation {
198 options: AnnotationOptions {
199 text: None,
200 line_ends: None,
201 line_width: None,
202 color: None,
203 position: None,
204 dimension: None,
205 feature_control: Some(AnnotationFeatureControl {
206 entity_id: face_id,
207 entity_pos: KPoint2d { x: 0.5, y: 0.5 },
209 leader_type: AnnotationLineEnd::Dot,
210 dimension: None,
211 control_frame: Some(AnnotationMbdControlFrame {
212 symbol: MbdSymbol::Flatness,
213 diameter_symbol: None,
214 tolerance: tolerance.to_mm(),
215 modifier: None,
216 primary_datum: None,
217 secondary_datum: None,
218 tertiary_datum: None,
219 }),
220 defined_datum: None,
221 prefix: None,
222 suffix: None,
223 plane_id: frame_plane_id,
224 offset: if let Some(offset) = &frame_position {
225 KPoint2d {
226 x: offset[0].to_mm(),
227 y: offset[1].to_mm(),
228 }
229 } else {
230 KPoint2d { x: 100.0, y: 100.0 }
231 },
232 precision: precision.as_ref().map(|n| n.n.round() as u32).unwrap_or(3),
233 font_scale: style.font_scale.as_ref().map(|n| n.n as f32).unwrap_or(1.0),
234 font_point_size: style.font_point_size.as_ref().map(|n| n.n.round() as u32).unwrap_or(36),
235 }),
236 feature_tag: None,
237 },
238 clobber: false,
239 annotation_type: AnnotationType::T3D,
240 }),
241 )
242 .await?;
243 }
244 Ok(())
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 .clone();
257 Ok(plane_value
258 .as_plane()
259 .ok_or_else(|| {
260 KclError::new_internal(KclErrorDetails::new(
261 "Expected XY plane to be defined".to_owned(),
262 vec![args.source_range],
263 ))
264 })?
265 .clone())
266}
267
268fn plane_ast(plane_name: &str, range: SourceRange) -> ast::Node<ast::Expr> {
270 ast::Node::new(
271 ast::Expr::Name(Box::new(ast::Node::new(
272 ast::Name {
273 name: ast::Identifier::new(plane_name),
274 path: Vec::new(),
275 abs_path: false,
278 digest: None,
279 },
280 range.start(),
281 range.end(),
282 range.module_id(),
283 ))),
284 range.start(),
285 range.end(),
286 range.module_id(),
287 )
288}