Skip to main content

kcl_lib/std/
gdt.rs

1use kcl_error::SourceRange;
2use kcmc::ModelingCmd;
3use kcmc::each_cmd as mcmd;
4use kittycad_modeling_cmds::shared::AnnotationBasicDimension;
5use kittycad_modeling_cmds::shared::AnnotationFeatureControl;
6use kittycad_modeling_cmds::shared::AnnotationLineEnd;
7use kittycad_modeling_cmds::shared::AnnotationMbdBasicDimension;
8use kittycad_modeling_cmds::shared::AnnotationMbdControlFrame;
9use kittycad_modeling_cmds::shared::AnnotationOptions;
10use kittycad_modeling_cmds::shared::AnnotationType;
11use kittycad_modeling_cmds::shared::MbdSymbol;
12use kittycad_modeling_cmds::shared::Point2d as KPoint2d;
13use kittycad_modeling_cmds::{self as kcmc};
14
15use crate::ExecState;
16use crate::KclError;
17use crate::errors::KclErrorDetails;
18use crate::exec::KclValue;
19use crate::execution::ControlFlowKind;
20use crate::execution::Face;
21use crate::execution::GdtAnnotation;
22use crate::execution::Metadata;
23use crate::execution::ModelingCmdMeta;
24use crate::execution::Plane;
25use crate::execution::StatementKind;
26use crate::execution::TagIdentifier;
27use crate::execution::types::ArrayLen;
28use crate::execution::types::RuntimeType;
29use crate::parsing::ast::types as ast;
30use crate::std::Args;
31use crate::std::args::FromKclValue;
32use crate::std::args::TyF64;
33use crate::std::fillet::EdgeReference;
34use crate::std::sketch::ensure_sketch_plane_in_engine;
35
36/// Bundle of common GD&T annotation style arguments.
37#[derive(Debug, Clone)]
38pub(crate) struct AnnotationStyle {
39    pub font_point_size: Option<TyF64>,
40    pub font_scale: Option<TyF64>,
41}
42
43#[derive(Debug, Clone)]
44enum DistanceEntity {
45    Face(Box<Face>),
46    TaggedFace(Box<TagIdentifier>),
47    Edge(EdgeReference),
48}
49
50#[derive(Debug, Clone, Copy)]
51struct DistanceEndpoint {
52    entity_id: uuid::Uuid,
53    entity_pos: KPoint2d<f64>,
54}
55
56impl DistanceEntity {
57    async fn to_endpoint(&self, exec_state: &mut ExecState, args: &Args) -> Result<DistanceEndpoint, KclError> {
58        match self {
59            DistanceEntity::Face(face) => Ok(DistanceEndpoint {
60                entity_id: face.id,
61                entity_pos: KPoint2d { x: 0.5, y: 0.5 },
62            }),
63            DistanceEntity::TaggedFace(face) => Ok(DistanceEndpoint {
64                entity_id: args.get_adjacent_face_to_tag(exec_state, face, false).await?,
65                entity_pos: KPoint2d { x: 0.5, y: 0.5 },
66            }),
67            DistanceEntity::Edge(edge) => Ok(DistanceEndpoint {
68                entity_id: edge.get_engine_id(exec_state, args)?,
69                entity_pos: KPoint2d { x: 0.5, y: 0.0 },
70            }),
71        }
72    }
73}
74
75impl<'a> FromKclValue<'a> for DistanceEntity {
76    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
77        match arg {
78            KclValue::Face { value } => Some(Self::Face(value.to_owned())),
79            KclValue::Uuid { value, .. } => Some(Self::Edge(EdgeReference::Uuid(*value))),
80            KclValue::TagIdentifier(value) => Some(Self::TaggedFace(value.to_owned())),
81            _ => None,
82        }
83    }
84}
85
86fn distance_entity_type() -> RuntimeType {
87    RuntimeType::Union(vec![
88        RuntimeType::face(),
89        RuntimeType::tagged_face(),
90        RuntimeType::edge(),
91    ])
92}
93
94pub async fn datum(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
95    let face: TagIdentifier = args.get_kw_arg("face", &RuntimeType::tagged_face(), exec_state)?;
96    let name: String = args.get_kw_arg("name", &RuntimeType::string(), exec_state)?;
97    let frame_position: Option<[TyF64; 2]> =
98        args.get_kw_arg_opt("framePosition", &RuntimeType::point2d(), exec_state)?;
99    let frame_plane: Option<Plane> = args.get_kw_arg_opt("framePlane", &RuntimeType::plane(), exec_state)?;
100    let leader_scale: Option<TyF64> = args.get_kw_arg_opt("leaderScale", &RuntimeType::count(), exec_state)?;
101    let font_point_size: Option<TyF64> = args.get_kw_arg_opt("fontPointSize", &RuntimeType::count(), exec_state)?;
102    let font_scale: Option<TyF64> = args.get_kw_arg_opt("fontScale", &RuntimeType::count(), exec_state)?;
103
104    let annotation = inner_datum(
105        face,
106        name,
107        frame_position,
108        frame_plane,
109        leader_scale,
110        AnnotationStyle {
111            font_point_size,
112            font_scale,
113        },
114        exec_state,
115        &args,
116    )
117    .await?;
118    Ok(KclValue::GdtAnnotation {
119        value: Box::new(annotation),
120    })
121}
122
123#[allow(clippy::too_many_arguments)]
124async fn inner_datum(
125    face: TagIdentifier,
126    name: String,
127    frame_position: Option<[TyF64; 2]>,
128    frame_plane: Option<Plane>,
129    leader_scale: Option<TyF64>,
130    style: AnnotationStyle,
131    exec_state: &mut ExecState,
132    args: &Args,
133) -> Result<GdtAnnotation, KclError> {
134    const DATUM_LENGTH_ERROR: &str = "Datum name must be a single character.";
135    if name.len() > 1 {
136        return Err(KclError::new_semantic(KclErrorDetails::new(
137            DATUM_LENGTH_ERROR.to_owned(),
138            vec![args.source_range],
139        )));
140    }
141    let name_char = name.chars().next().ok_or_else(|| {
142        KclError::new_semantic(KclErrorDetails::new(
143            DATUM_LENGTH_ERROR.to_owned(),
144            vec![args.source_range],
145        ))
146    })?;
147    let mut frame_plane = if let Some(plane) = frame_plane {
148        plane
149    } else {
150        // No plane given. Use one of the standard planes.
151        xy_plane(exec_state, args).await?
152    };
153    ensure_sketch_plane_in_engine(
154        &mut frame_plane,
155        exec_state,
156        &args.ctx,
157        args.source_range,
158        args.node_path.clone(),
159    )
160    .await?;
161    let face_id = args.get_adjacent_face_to_tag(exec_state, &face, false).await?;
162    let meta = vec![Metadata::from(args.source_range)];
163    let annotation_id = exec_state.next_uuid();
164    let feature_control = AnnotationFeatureControl::builder()
165        .entity_id(face_id)
166        // Point to the center of the face.
167        .entity_pos(KPoint2d { x: 0.5, y: 0.5 })
168        .leader_type(AnnotationLineEnd::Dot)
169        .defined_datum(name_char)
170        .plane_id(frame_plane.id)
171        .offset(if let Some(offset) = &frame_position {
172            KPoint2d {
173                x: offset[0].to_mm(),
174                y: offset[1].to_mm(),
175            }
176        } else {
177            KPoint2d { x: 100.0, y: 100.0 }
178        })
179        .precision(0)
180        .font_scale(style.font_scale.as_ref().map(|n| n.n as f32).unwrap_or(1.0))
181        .font_point_size(style.font_point_size.as_ref().map(|n| n.n.round() as u32).unwrap_or(36))
182        .leader_scale(leader_scale.as_ref().map(|n| n.n as f32).unwrap_or(1.0))
183        .build();
184    exec_state
185        .batch_modeling_cmd(
186            ModelingCmdMeta::from_args_id(exec_state, args, annotation_id),
187            ModelingCmd::from(
188                mcmd::NewAnnotation::builder()
189                    .options(AnnotationOptions::builder().feature_control(feature_control).build())
190                    .clobber(false)
191                    .annotation_type(AnnotationType::T3D)
192                    .build(),
193            ),
194        )
195        .await?;
196    Ok(GdtAnnotation {
197        id: annotation_id,
198        meta,
199    })
200}
201
202pub async fn flatness(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
203    let faces: Vec<TagIdentifier> = args.get_kw_arg(
204        "faces",
205        &RuntimeType::Array(Box::new(RuntimeType::tagged_face()), ArrayLen::Minimum(1)),
206        exec_state,
207    )?;
208    let tolerance = args.get_kw_arg("tolerance", &RuntimeType::length(), exec_state)?;
209    let precision = args.get_kw_arg_opt("precision", &RuntimeType::count(), exec_state)?;
210    let frame_position: Option<[TyF64; 2]> =
211        args.get_kw_arg_opt("framePosition", &RuntimeType::point2d(), exec_state)?;
212    let frame_plane: Option<Plane> = args.get_kw_arg_opt("framePlane", &RuntimeType::plane(), exec_state)?;
213    let leader_scale: Option<TyF64> = args.get_kw_arg_opt("leaderScale", &RuntimeType::count(), exec_state)?;
214    let font_point_size: Option<TyF64> = args.get_kw_arg_opt("fontPointSize", &RuntimeType::count(), exec_state)?;
215    let font_scale: Option<TyF64> = args.get_kw_arg_opt("fontScale", &RuntimeType::count(), exec_state)?;
216
217    let annotations = inner_flatness(
218        faces,
219        tolerance,
220        precision,
221        frame_position,
222        frame_plane,
223        leader_scale,
224        AnnotationStyle {
225            font_point_size,
226            font_scale,
227        },
228        exec_state,
229        &args,
230    )
231    .await?;
232    Ok(annotations.into())
233}
234
235pub async fn profile(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
236    let edges: Vec<EdgeReference> = args.get_kw_arg(
237        "edges",
238        &RuntimeType::Array(Box::new(RuntimeType::edge()), ArrayLen::Minimum(1)),
239        exec_state,
240    )?;
241    let datums: Option<Vec<String>> = args.get_kw_arg_opt(
242        "datums",
243        &RuntimeType::Array(Box::new(RuntimeType::string()), ArrayLen::Minimum(1)),
244        exec_state,
245    )?;
246    let tolerance = args.get_kw_arg("tolerance", &RuntimeType::length(), exec_state)?;
247    let precision = args.get_kw_arg_opt("precision", &RuntimeType::count(), exec_state)?;
248    let frame_position: Option<[TyF64; 2]> =
249        args.get_kw_arg_opt("framePosition", &RuntimeType::point2d(), exec_state)?;
250    let frame_plane: Option<Plane> = args.get_kw_arg_opt("framePlane", &RuntimeType::plane(), exec_state)?;
251    let leader_scale: Option<TyF64> = args.get_kw_arg_opt("leaderScale", &RuntimeType::count(), exec_state)?;
252    let font_point_size: Option<TyF64> = args.get_kw_arg_opt("fontPointSize", &RuntimeType::count(), exec_state)?;
253    let font_scale: Option<TyF64> = args.get_kw_arg_opt("fontScale", &RuntimeType::count(), exec_state)?;
254
255    let annotations = inner_profile(
256        edges,
257        datums,
258        tolerance,
259        precision,
260        frame_position,
261        frame_plane,
262        leader_scale,
263        AnnotationStyle {
264            font_point_size,
265            font_scale,
266        },
267        exec_state,
268        &args,
269    )
270    .await?;
271    Ok(annotations.into())
272}
273
274pub async fn position(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
275    let faces: Option<Vec<TagIdentifier>> = args.get_kw_arg_opt(
276        "faces",
277        &RuntimeType::Array(Box::new(RuntimeType::tagged_face()), ArrayLen::Minimum(1)),
278        exec_state,
279    )?;
280    let edges: Option<Vec<EdgeReference>> = args.get_kw_arg_opt(
281        "edges",
282        &RuntimeType::Array(Box::new(RuntimeType::edge()), ArrayLen::Minimum(1)),
283        exec_state,
284    )?;
285    let datums: Option<Vec<String>> = args.get_kw_arg_opt(
286        "datums",
287        &RuntimeType::Array(Box::new(RuntimeType::string()), ArrayLen::Minimum(1)),
288        exec_state,
289    )?;
290    let tolerance = args.get_kw_arg("tolerance", &RuntimeType::length(), exec_state)?;
291    let precision = args.get_kw_arg_opt("precision", &RuntimeType::count(), exec_state)?;
292    let frame_position: Option<[TyF64; 2]> =
293        args.get_kw_arg_opt("framePosition", &RuntimeType::point2d(), exec_state)?;
294    let frame_plane: Option<Plane> = args.get_kw_arg_opt("framePlane", &RuntimeType::plane(), exec_state)?;
295    let leader_scale: Option<TyF64> = args.get_kw_arg_opt("leaderScale", &RuntimeType::count(), exec_state)?;
296    let font_point_size: Option<TyF64> = args.get_kw_arg_opt("fontPointSize", &RuntimeType::count(), exec_state)?;
297    let font_scale: Option<TyF64> = args.get_kw_arg_opt("fontScale", &RuntimeType::count(), exec_state)?;
298
299    let annotations = inner_position(
300        faces.unwrap_or_default(),
301        edges.unwrap_or_default(),
302        tolerance,
303        datums,
304        precision,
305        frame_position,
306        frame_plane,
307        leader_scale,
308        AnnotationStyle {
309            font_point_size,
310            font_scale,
311        },
312        exec_state,
313        &args,
314    )
315    .await?;
316    Ok(annotations.into())
317}
318
319pub async fn distance(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
320    let from: Option<DistanceEntity> = args.get_kw_arg_opt("from", &distance_entity_type(), exec_state)?;
321    let to: Option<DistanceEntity> = args.get_kw_arg_opt("to", &distance_entity_type(), exec_state)?;
322    let edges: Option<Vec<EdgeReference>> = args.get_kw_arg_opt(
323        "edges",
324        &RuntimeType::Array(Box::new(RuntimeType::edge()), ArrayLen::Minimum(1)),
325        exec_state,
326    )?;
327    let tolerance = args.get_kw_arg("tolerance", &RuntimeType::length(), exec_state)?;
328    let precision = args.get_kw_arg_opt("precision", &RuntimeType::count(), exec_state)?;
329    let frame_position: Option<[TyF64; 2]> =
330        args.get_kw_arg_opt("framePosition", &RuntimeType::point2d(), exec_state)?;
331    let frame_plane: Option<Plane> = args.get_kw_arg_opt("framePlane", &RuntimeType::plane(), exec_state)?;
332    let leader_scale: Option<TyF64> = args.get_kw_arg_opt("leaderScale", &RuntimeType::count(), exec_state)?;
333    let font_point_size: Option<TyF64> = args.get_kw_arg_opt("fontPointSize", &RuntimeType::count(), exec_state)?;
334    let font_scale: Option<TyF64> = args.get_kw_arg_opt("fontScale", &RuntimeType::count(), exec_state)?;
335
336    let annotations = inner_distance(
337        from,
338        to,
339        edges.unwrap_or_default(),
340        tolerance,
341        precision,
342        frame_position,
343        frame_plane,
344        leader_scale,
345        AnnotationStyle {
346            font_point_size,
347            font_scale,
348        },
349        exec_state,
350        &args,
351    )
352    .await?;
353    Ok(annotations.into())
354}
355
356pub async fn perpendicularity(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
357    let faces: Option<Vec<TagIdentifier>> = args.get_kw_arg_opt(
358        "faces",
359        &RuntimeType::Array(Box::new(RuntimeType::tagged_face()), ArrayLen::Minimum(1)),
360        exec_state,
361    )?;
362    let edges: Option<Vec<EdgeReference>> = args.get_kw_arg_opt(
363        "edges",
364        &RuntimeType::Array(Box::new(RuntimeType::edge()), ArrayLen::Minimum(1)),
365        exec_state,
366    )?;
367    let datums: Option<Vec<String>> = args.get_kw_arg_opt(
368        "datums",
369        &RuntimeType::Array(Box::new(RuntimeType::string()), ArrayLen::Minimum(1)),
370        exec_state,
371    )?;
372    let tolerance = args.get_kw_arg("tolerance", &RuntimeType::length(), exec_state)?;
373    let precision = args.get_kw_arg_opt("precision", &RuntimeType::count(), exec_state)?;
374    let frame_position: Option<[TyF64; 2]> =
375        args.get_kw_arg_opt("framePosition", &RuntimeType::point2d(), exec_state)?;
376    let frame_plane: Option<Plane> = args.get_kw_arg_opt("framePlane", &RuntimeType::plane(), exec_state)?;
377    let leader_scale: Option<TyF64> = args.get_kw_arg_opt("leaderScale", &RuntimeType::count(), exec_state)?;
378    let font_point_size: Option<TyF64> = args.get_kw_arg_opt("fontPointSize", &RuntimeType::count(), exec_state)?;
379    let font_scale: Option<TyF64> = args.get_kw_arg_opt("fontScale", &RuntimeType::count(), exec_state)?;
380
381    let annotations = inner_perpendicularity(
382        faces.unwrap_or_default(),
383        edges.unwrap_or_default(),
384        datums,
385        tolerance,
386        precision,
387        frame_position,
388        frame_plane,
389        leader_scale,
390        AnnotationStyle {
391            font_point_size,
392            font_scale,
393        },
394        exec_state,
395        &args,
396    )
397    .await?;
398    Ok(annotations.into())
399}
400
401pub async fn parallelism(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
402    let faces: Option<Vec<TagIdentifier>> = args.get_kw_arg_opt(
403        "faces",
404        &RuntimeType::Array(Box::new(RuntimeType::tagged_face()), ArrayLen::Minimum(1)),
405        exec_state,
406    )?;
407    let edges: Option<Vec<EdgeReference>> = args.get_kw_arg_opt(
408        "edges",
409        &RuntimeType::Array(Box::new(RuntimeType::edge()), ArrayLen::Minimum(1)),
410        exec_state,
411    )?;
412    let datums: Option<Vec<String>> = args.get_kw_arg_opt(
413        "datums",
414        &RuntimeType::Array(Box::new(RuntimeType::string()), ArrayLen::Minimum(1)),
415        exec_state,
416    )?;
417    let tolerance = args.get_kw_arg("tolerance", &RuntimeType::length(), exec_state)?;
418    let precision = args.get_kw_arg_opt("precision", &RuntimeType::count(), exec_state)?;
419    let frame_position: Option<[TyF64; 2]> =
420        args.get_kw_arg_opt("framePosition", &RuntimeType::point2d(), exec_state)?;
421    let frame_plane: Option<Plane> = args.get_kw_arg_opt("framePlane", &RuntimeType::plane(), exec_state)?;
422    let leader_scale: Option<TyF64> = args.get_kw_arg_opt("leaderScale", &RuntimeType::count(), exec_state)?;
423    let font_point_size: Option<TyF64> = args.get_kw_arg_opt("fontPointSize", &RuntimeType::count(), exec_state)?;
424    let font_scale: Option<TyF64> = args.get_kw_arg_opt("fontScale", &RuntimeType::count(), exec_state)?;
425
426    let annotations = inner_parallelism(
427        faces.unwrap_or_default(),
428        edges.unwrap_or_default(),
429        datums,
430        tolerance,
431        precision,
432        frame_position,
433        frame_plane,
434        leader_scale,
435        AnnotationStyle {
436            font_point_size,
437            font_scale,
438        },
439        exec_state,
440        &args,
441    )
442    .await?;
443    Ok(annotations.into())
444}
445
446pub async fn annotation(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
447    let annotation: String = args.get_kw_arg("annotation", &RuntimeType::string(), exec_state)?;
448    let faces: Option<Vec<TagIdentifier>> = args.get_kw_arg_opt(
449        "faces",
450        &RuntimeType::Array(Box::new(RuntimeType::tagged_face()), ArrayLen::Minimum(1)),
451        exec_state,
452    )?;
453    let edges: Option<Vec<EdgeReference>> = args.get_kw_arg_opt(
454        "edges",
455        &RuntimeType::Array(Box::new(RuntimeType::edge()), ArrayLen::Minimum(1)),
456        exec_state,
457    )?;
458    let frame_position: Option<[TyF64; 2]> =
459        args.get_kw_arg_opt("framePosition", &RuntimeType::point2d(), exec_state)?;
460    let frame_plane: Option<Plane> = args.get_kw_arg_opt("framePlane", &RuntimeType::plane(), exec_state)?;
461    let leader_scale: Option<TyF64> = args.get_kw_arg_opt("leaderScale", &RuntimeType::count(), exec_state)?;
462    let font_point_size: Option<TyF64> = args.get_kw_arg_opt("fontPointSize", &RuntimeType::count(), exec_state)?;
463    let font_scale: Option<TyF64> = args.get_kw_arg_opt("fontScale", &RuntimeType::count(), exec_state)?;
464
465    let annotations = inner_annotation(
466        annotation,
467        faces.unwrap_or_default(),
468        edges.unwrap_or_default(),
469        frame_position,
470        frame_plane,
471        leader_scale,
472        AnnotationStyle {
473            font_point_size,
474            font_scale,
475        },
476        exec_state,
477        &args,
478    )
479    .await?;
480    Ok(annotations.into())
481}
482
483#[allow(clippy::too_many_arguments)]
484async fn inner_perpendicularity(
485    faces: Vec<TagIdentifier>,
486    edges: Vec<EdgeReference>,
487    datums: Option<Vec<String>>,
488    tolerance: TyF64,
489    precision: Option<TyF64>,
490    frame_position: Option<[TyF64; 2]>,
491    frame_plane: Option<Plane>,
492    leader_scale: Option<TyF64>,
493    style: AnnotationStyle,
494    exec_state: &mut ExecState,
495    args: &Args,
496) -> Result<Vec<GdtAnnotation>, KclError> {
497    if faces.is_empty() && edges.is_empty() {
498        return Err(KclError::new_semantic(KclErrorDetails::new(
499            "Perpendicularity requires at least one face or edge.".to_owned(),
500            vec![args.source_range],
501        )));
502    }
503
504    let precision = resolve_precision(precision, args)?;
505    let datums = resolve_datums(datums, args, "Perpendicularity")?;
506    let mut frame_plane = if let Some(plane) = frame_plane {
507        plane
508    } else {
509        xy_plane(exec_state, args).await?
510    };
511    ensure_sketch_plane_in_engine(
512        &mut frame_plane,
513        exec_state,
514        &args.ctx,
515        args.source_range,
516        args.node_path.clone(),
517    )
518    .await?;
519
520    let mut annotations = Vec::with_capacity(faces.len() + edges.len());
521    for face in &faces {
522        let face_id = args.get_adjacent_face_to_tag(exec_state, face, false).await?;
523        create_feature_control_annotation(
524            face_id,
525            MbdSymbol::Perpendicularity,
526            &tolerance,
527            &datums,
528            precision,
529            frame_position.as_ref(),
530            frame_plane.id,
531            leader_scale.as_ref(),
532            &style,
533            exec_state,
534            args,
535            &mut annotations,
536        )
537        .await?;
538    }
539    for edge in &edges {
540        let edge_id = edge.get_engine_id(exec_state, args)?;
541        create_feature_control_annotation(
542            edge_id,
543            MbdSymbol::Perpendicularity,
544            &tolerance,
545            &datums,
546            precision,
547            frame_position.as_ref(),
548            frame_plane.id,
549            leader_scale.as_ref(),
550            &style,
551            exec_state,
552            args,
553            &mut annotations,
554        )
555        .await?;
556    }
557
558    Ok(annotations)
559}
560
561#[allow(clippy::too_many_arguments)]
562async fn inner_parallelism(
563    faces: Vec<TagIdentifier>,
564    edges: Vec<EdgeReference>,
565    datums: Option<Vec<String>>,
566    tolerance: TyF64,
567    precision: Option<TyF64>,
568    frame_position: Option<[TyF64; 2]>,
569    frame_plane: Option<Plane>,
570    leader_scale: Option<TyF64>,
571    style: AnnotationStyle,
572    exec_state: &mut ExecState,
573    args: &Args,
574) -> Result<Vec<GdtAnnotation>, KclError> {
575    if faces.is_empty() && edges.is_empty() {
576        return Err(KclError::new_semantic(KclErrorDetails::new(
577            "Parallelism requires at least one face or edge.".to_owned(),
578            vec![args.source_range],
579        )));
580    }
581
582    let precision = resolve_precision(precision, args)?;
583    let datums = resolve_datums(datums, args, "Parallelism")?;
584    let mut frame_plane = if let Some(plane) = frame_plane {
585        plane
586    } else {
587        xy_plane(exec_state, args).await?
588    };
589    ensure_sketch_plane_in_engine(
590        &mut frame_plane,
591        exec_state,
592        &args.ctx,
593        args.source_range,
594        args.node_path.clone(),
595    )
596    .await?;
597
598    let mut annotations = Vec::with_capacity(faces.len() + edges.len());
599    for face in &faces {
600        let face_id = args.get_adjacent_face_to_tag(exec_state, face, false).await?;
601        create_feature_control_annotation(
602            face_id,
603            MbdSymbol::Parallelism,
604            &tolerance,
605            &datums,
606            precision,
607            frame_position.as_ref(),
608            frame_plane.id,
609            leader_scale.as_ref(),
610            &style,
611            exec_state,
612            args,
613            &mut annotations,
614        )
615        .await?;
616    }
617    for edge in &edges {
618        let edge_id = edge.get_engine_id(exec_state, args)?;
619        create_feature_control_annotation(
620            edge_id,
621            MbdSymbol::Parallelism,
622            &tolerance,
623            &datums,
624            precision,
625            frame_position.as_ref(),
626            frame_plane.id,
627            leader_scale.as_ref(),
628            &style,
629            exec_state,
630            args,
631            &mut annotations,
632        )
633        .await?;
634    }
635
636    Ok(annotations)
637}
638
639#[allow(clippy::too_many_arguments)]
640async fn inner_annotation(
641    annotation: String,
642    faces: Vec<TagIdentifier>,
643    edges: Vec<EdgeReference>,
644    frame_position: Option<[TyF64; 2]>,
645    frame_plane: Option<Plane>,
646    leader_scale: Option<TyF64>,
647    style: AnnotationStyle,
648    exec_state: &mut ExecState,
649    args: &Args,
650) -> Result<Vec<GdtAnnotation>, KclError> {
651    if annotation.is_empty() {
652        return Err(KclError::new_semantic(KclErrorDetails::new(
653            "Annotation text must not be empty.".to_owned(),
654            vec![args.source_range],
655        )));
656    }
657    if faces.is_empty() && edges.is_empty() {
658        return Err(KclError::new_semantic(KclErrorDetails::new(
659            "Annotation requires at least one face or edge.".to_owned(),
660            vec![args.source_range],
661        )));
662    }
663
664    let mut frame_plane = if let Some(plane) = frame_plane {
665        plane
666    } else {
667        xy_plane(exec_state, args).await?
668    };
669    ensure_sketch_plane_in_engine(
670        &mut frame_plane,
671        exec_state,
672        &args.ctx,
673        args.source_range,
674        args.node_path.clone(),
675    )
676    .await?;
677
678    let mut annotations = Vec::with_capacity(faces.len() + edges.len());
679    for face in &faces {
680        let face_id = args.get_adjacent_face_to_tag(exec_state, face, false).await?;
681        create_annotation(
682            face_id,
683            &annotation,
684            frame_position.as_ref(),
685            frame_plane.id,
686            leader_scale.as_ref(),
687            &style,
688            exec_state,
689            args,
690            &mut annotations,
691        )
692        .await?;
693    }
694    for edge in &edges {
695        let edge_id = edge.get_engine_id(exec_state, args)?;
696        create_annotation(
697            edge_id,
698            &annotation,
699            frame_position.as_ref(),
700            frame_plane.id,
701            leader_scale.as_ref(),
702            &style,
703            exec_state,
704            args,
705            &mut annotations,
706        )
707        .await?;
708    }
709
710    Ok(annotations)
711}
712
713#[allow(clippy::too_many_arguments)]
714async fn inner_distance(
715    from: Option<DistanceEntity>,
716    to: Option<DistanceEntity>,
717    edges: Vec<EdgeReference>,
718    tolerance: TyF64,
719    precision: Option<TyF64>,
720    frame_position: Option<[TyF64; 2]>,
721    frame_plane: Option<Plane>,
722    leader_scale: Option<TyF64>,
723    style: AnnotationStyle,
724    exec_state: &mut ExecState,
725    args: &Args,
726) -> Result<Vec<GdtAnnotation>, KclError> {
727    let precision = resolve_precision(precision, args)?;
728    let mut frame_plane = if let Some(plane) = frame_plane {
729        plane
730    } else {
731        xy_plane(exec_state, args).await?
732    };
733    ensure_sketch_plane_in_engine(
734        &mut frame_plane,
735        exec_state,
736        &args.ctx,
737        args.source_range,
738        args.node_path.clone(),
739    )
740    .await?;
741
742    if from.is_some() || to.is_some() {
743        if !edges.is_empty() {
744            return Err(KclError::new_semantic(KclErrorDetails::new(
745                "Distance cannot combine `from`/`to` with `edges`.".to_owned(),
746                vec![args.source_range],
747            )));
748        }
749
750        let (Some(from), Some(to)) = (from, to) else {
751            return Err(KclError::new_semantic(KclErrorDetails::new(
752                "Distance requires both `from` and `to` when measuring between entities.".to_owned(),
753                vec![args.source_range],
754            )));
755        };
756
757        let from = from.to_endpoint(exec_state, args).await?;
758        let to = to.to_endpoint(exec_state, args).await?;
759        let mut annotations = Vec::with_capacity(1);
760        create_basic_distance_annotation(
761            from,
762            to,
763            &tolerance,
764            precision,
765            frame_position.as_ref(),
766            frame_plane.id,
767            leader_scale.as_ref(),
768            &style,
769            exec_state,
770            args,
771            &mut annotations,
772        )
773        .await?;
774        return Ok(annotations);
775    }
776
777    if edges.is_empty() {
778        return Err(KclError::new_semantic(KclErrorDetails::new(
779            "Distance requires either `edges` or both `from` and `to`.".to_owned(),
780            vec![args.source_range],
781        )));
782    }
783
784    let mut annotations = Vec::with_capacity(edges.len());
785    for edge in &edges {
786        let edge_id = edge.get_engine_id(exec_state, args)?;
787        create_basic_distance_annotation(
788            DistanceEndpoint {
789                entity_id: edge_id,
790                entity_pos: KPoint2d { x: 0.0, y: 0.0 },
791            },
792            DistanceEndpoint {
793                entity_id: edge_id,
794                entity_pos: KPoint2d { x: 1.0, y: 0.0 },
795            },
796            &tolerance,
797            precision,
798            frame_position.as_ref(),
799            frame_plane.id,
800            leader_scale.as_ref(),
801            &style,
802            exec_state,
803            args,
804            &mut annotations,
805        )
806        .await?;
807    }
808    Ok(annotations)
809}
810
811#[allow(clippy::too_many_arguments)]
812async fn inner_profile(
813    edges: Vec<EdgeReference>,
814    datums: Option<Vec<String>>,
815    tolerance: TyF64,
816    precision: Option<TyF64>,
817    frame_position: Option<[TyF64; 2]>,
818    frame_plane: Option<Plane>,
819    leader_scale: Option<TyF64>,
820    style: AnnotationStyle,
821    exec_state: &mut ExecState,
822    args: &Args,
823) -> Result<Vec<GdtAnnotation>, KclError> {
824    let precision = resolve_precision(precision, args)?;
825    let datums = resolve_datums(datums, args, "Profile")?;
826    let mut frame_plane = if let Some(plane) = frame_plane {
827        plane
828    } else {
829        xy_plane(exec_state, args).await?
830    };
831    ensure_sketch_plane_in_engine(
832        &mut frame_plane,
833        exec_state,
834        &args.ctx,
835        args.source_range,
836        args.node_path.clone(),
837    )
838    .await?;
839
840    let mut annotations = Vec::with_capacity(edges.len());
841    for edge in &edges {
842        let edge_id = edge.get_engine_id(exec_state, args)?;
843        create_feature_control_annotation(
844            edge_id,
845            MbdSymbol::ProfileOfLine,
846            &tolerance,
847            &datums,
848            precision,
849            frame_position.as_ref(),
850            frame_plane.id,
851            leader_scale.as_ref(),
852            &style,
853            exec_state,
854            args,
855            &mut annotations,
856        )
857        .await?;
858    }
859    Ok(annotations)
860}
861
862#[allow(clippy::too_many_arguments)]
863async fn inner_position(
864    faces: Vec<TagIdentifier>,
865    edges: Vec<EdgeReference>,
866    tolerance: TyF64,
867    datums: Option<Vec<String>>,
868    precision: Option<TyF64>,
869    frame_position: Option<[TyF64; 2]>,
870    frame_plane: Option<Plane>,
871    leader_scale: Option<TyF64>,
872    style: AnnotationStyle,
873    exec_state: &mut ExecState,
874    args: &Args,
875) -> Result<Vec<GdtAnnotation>, KclError> {
876    if faces.is_empty() && edges.is_empty() {
877        return Err(KclError::new_semantic(KclErrorDetails::new(
878            "Position requires at least one face or edge.".to_owned(),
879            vec![args.source_range],
880        )));
881    }
882
883    let precision = resolve_precision(precision, args)?;
884    let datums = resolve_datums(datums, args, "Position")?;
885    let mut frame_plane = if let Some(plane) = frame_plane {
886        plane
887    } else {
888        xy_plane(exec_state, args).await?
889    };
890    ensure_sketch_plane_in_engine(
891        &mut frame_plane,
892        exec_state,
893        &args.ctx,
894        args.source_range,
895        args.node_path.clone(),
896    )
897    .await?;
898
899    let mut annotations = Vec::with_capacity(faces.len() + edges.len());
900    for face in &faces {
901        let face_id = args.get_adjacent_face_to_tag(exec_state, face, false).await?;
902        create_feature_control_annotation(
903            face_id,
904            MbdSymbol::Position,
905            &tolerance,
906            &datums,
907            precision,
908            frame_position.as_ref(),
909            frame_plane.id,
910            leader_scale.as_ref(),
911            &style,
912            exec_state,
913            args,
914            &mut annotations,
915        )
916        .await?;
917    }
918    for edge in &edges {
919        let edge_id = edge.get_engine_id(exec_state, args)?;
920        create_feature_control_annotation(
921            edge_id,
922            MbdSymbol::Position,
923            &tolerance,
924            &datums,
925            precision,
926            frame_position.as_ref(),
927            frame_plane.id,
928            leader_scale.as_ref(),
929            &style,
930            exec_state,
931            args,
932            &mut annotations,
933        )
934        .await?;
935    }
936    Ok(annotations)
937}
938
939#[allow(clippy::too_many_arguments)]
940async fn inner_flatness(
941    faces: Vec<TagIdentifier>,
942    tolerance: TyF64,
943    precision: Option<TyF64>,
944    frame_position: Option<[TyF64; 2]>,
945    frame_plane: Option<Plane>,
946    leader_scale: Option<TyF64>,
947    style: AnnotationStyle,
948    exec_state: &mut ExecState,
949    args: &Args,
950) -> Result<Vec<GdtAnnotation>, KclError> {
951    let precision = resolve_precision(precision, args)?;
952    let mut frame_plane = if let Some(plane) = frame_plane {
953        plane
954    } else {
955        // No plane given. Use one of the standard planes.
956        xy_plane(exec_state, args).await?
957    };
958    ensure_sketch_plane_in_engine(
959        &mut frame_plane,
960        exec_state,
961        &args.ctx,
962        args.source_range,
963        args.node_path.clone(),
964    )
965    .await?;
966    let mut annotations = Vec::with_capacity(faces.len());
967    for face in &faces {
968        let face_id = args.get_adjacent_face_to_tag(exec_state, face, false).await?;
969        let meta = vec![Metadata::from(args.source_range)];
970        let annotation_id = exec_state.next_uuid();
971        let feature_control = AnnotationFeatureControl::builder()
972            .entity_id(face_id)
973            // Point to the center of the face.
974            .entity_pos(KPoint2d { x: 0.5, y: 0.5 })
975            .leader_type(AnnotationLineEnd::Dot)
976            .control_frame(
977                AnnotationMbdControlFrame::builder()
978                    .symbol(MbdSymbol::Flatness)
979                    .tolerance(tolerance.to_mm())
980                    .build(),
981            )
982            .plane_id(frame_plane.id)
983            .offset(if let Some(offset) = &frame_position {
984                KPoint2d {
985                    x: offset[0].to_mm(),
986                    y: offset[1].to_mm(),
987                }
988            } else {
989                KPoint2d { x: 100.0, y: 100.0 }
990            })
991            .precision(precision)
992            .font_scale(style.font_scale.as_ref().map(|n| n.n as f32).unwrap_or(1.0))
993            .font_point_size(style.font_point_size.as_ref().map(|n| n.n.round() as u32).unwrap_or(36))
994            .leader_scale(leader_scale.as_ref().map(|n| n.n as f32).unwrap_or(1.0))
995            .build();
996        let options = AnnotationOptions::builder().feature_control(feature_control).build();
997        exec_state
998            .batch_modeling_cmd(
999                ModelingCmdMeta::from_args_id(exec_state, args, annotation_id),
1000                ModelingCmd::from(
1001                    mcmd::NewAnnotation::builder()
1002                        .options(options)
1003                        .clobber(false)
1004                        .annotation_type(AnnotationType::T3D)
1005                        .build(),
1006                ),
1007            )
1008            .await?;
1009        annotations.push(GdtAnnotation {
1010            id: annotation_id,
1011            meta,
1012        });
1013    }
1014    Ok(annotations)
1015}
1016
1017fn resolve_precision(precision: Option<TyF64>, args: &Args) -> Result<u32, KclError> {
1018    if let Some(precision) = precision {
1019        let rounded = precision.n.round();
1020        if !(0.0..=9.0).contains(&rounded) {
1021            return Err(KclError::new_semantic(KclErrorDetails::new(
1022                "Precision must be between 0 and 9".to_owned(),
1023                vec![args.source_range],
1024            )));
1025        }
1026        Ok(rounded as u32)
1027    } else {
1028        Ok(3)
1029    }
1030}
1031
1032#[allow(clippy::too_many_arguments)]
1033async fn create_basic_distance_annotation(
1034    from: DistanceEndpoint,
1035    to: DistanceEndpoint,
1036    tolerance: &TyF64,
1037    precision: u32,
1038    frame_position: Option<&[TyF64; 2]>,
1039    frame_plane_id: uuid::Uuid,
1040    leader_scale: Option<&TyF64>,
1041    style: &AnnotationStyle,
1042    exec_state: &mut ExecState,
1043    args: &Args,
1044    annotations: &mut Vec<GdtAnnotation>,
1045) -> Result<(), KclError> {
1046    let meta = vec![Metadata::from(args.source_range)];
1047    let annotation_id = exec_state.next_uuid();
1048    let dimension = AnnotationBasicDimension::builder()
1049        .from_entity_id(from.entity_id)
1050        .from_entity_pos(from.entity_pos)
1051        .to_entity_id(to.entity_id)
1052        .to_entity_pos(to.entity_pos)
1053        .dimension(
1054            AnnotationMbdBasicDimension::builder()
1055                .tolerance(tolerance.to_mm())
1056                .build(),
1057        )
1058        .plane_id(frame_plane_id)
1059        .offset(if let Some(offset) = frame_position {
1060            KPoint2d {
1061                x: offset[0].to_mm(),
1062                y: offset[1].to_mm(),
1063            }
1064        } else {
1065            KPoint2d { x: 100.0, y: 100.0 }
1066        })
1067        .precision(precision)
1068        .font_scale(style.font_scale.as_ref().map(|n| n.n as f32).unwrap_or(1.0))
1069        .font_point_size(style.font_point_size.as_ref().map(|n| n.n.round() as u32).unwrap_or(36))
1070        .arrow_scale(leader_scale.map(|n| n.n as f32).unwrap_or(1.0))
1071        .build();
1072    let options = AnnotationOptions::builder().dimension(dimension).build();
1073    exec_state
1074        .batch_modeling_cmd(
1075            ModelingCmdMeta::from_args_id(exec_state, args, annotation_id),
1076            ModelingCmd::from(
1077                mcmd::NewAnnotation::builder()
1078                    .options(options)
1079                    .clobber(false)
1080                    .annotation_type(AnnotationType::T3D)
1081                    .build(),
1082            ),
1083        )
1084        .await?;
1085    annotations.push(GdtAnnotation {
1086        id: annotation_id,
1087        meta,
1088    });
1089    Ok(())
1090}
1091
1092#[allow(clippy::too_many_arguments)]
1093async fn create_feature_control_annotation(
1094    entity_id: uuid::Uuid,
1095    symbol: MbdSymbol,
1096    tolerance: &TyF64,
1097    datums: &[char],
1098    precision: u32,
1099    frame_position: Option<&[TyF64; 2]>,
1100    frame_plane_id: uuid::Uuid,
1101    leader_scale: Option<&TyF64>,
1102    style: &AnnotationStyle,
1103    exec_state: &mut ExecState,
1104    args: &Args,
1105    annotations: &mut Vec<GdtAnnotation>,
1106) -> Result<(), KclError> {
1107    let meta = vec![Metadata::from(args.source_range)];
1108    let annotation_id = exec_state.next_uuid();
1109    let control_frame = gdt_control_frame(symbol, tolerance.to_mm(), datums);
1110    let feature_control = AnnotationFeatureControl::builder()
1111        .entity_id(entity_id)
1112        .entity_pos(KPoint2d { x: 0.5, y: 0.5 })
1113        .leader_type(AnnotationLineEnd::Dot)
1114        .control_frame(control_frame)
1115        .plane_id(frame_plane_id)
1116        .offset(if let Some(offset) = frame_position {
1117            KPoint2d {
1118                x: offset[0].to_mm(),
1119                y: offset[1].to_mm(),
1120            }
1121        } else {
1122            KPoint2d { x: 100.0, y: 100.0 }
1123        })
1124        .precision(precision)
1125        .font_scale(style.font_scale.as_ref().map(|n| n.n as f32).unwrap_or(1.0))
1126        .font_point_size(style.font_point_size.as_ref().map(|n| n.n.round() as u32).unwrap_or(36))
1127        .leader_scale(leader_scale.map(|n| n.n as f32).unwrap_or(1.0))
1128        .build();
1129    let options = AnnotationOptions::builder().feature_control(feature_control).build();
1130    exec_state
1131        .batch_modeling_cmd(
1132            ModelingCmdMeta::from_args_id(exec_state, args, annotation_id),
1133            ModelingCmd::from(
1134                mcmd::NewAnnotation::builder()
1135                    .options(options)
1136                    .clobber(false)
1137                    .annotation_type(AnnotationType::T3D)
1138                    .build(),
1139            ),
1140        )
1141        .await?;
1142    annotations.push(GdtAnnotation {
1143        id: annotation_id,
1144        meta,
1145    });
1146    Ok(())
1147}
1148
1149#[allow(clippy::too_many_arguments)]
1150async fn create_annotation(
1151    entity_id: uuid::Uuid,
1152    annotation: &str,
1153    frame_position: Option<&[TyF64; 2]>,
1154    frame_plane_id: uuid::Uuid,
1155    leader_scale: Option<&TyF64>,
1156    style: &AnnotationStyle,
1157    exec_state: &mut ExecState,
1158    args: &Args,
1159    annotations: &mut Vec<GdtAnnotation>,
1160) -> Result<(), KclError> {
1161    let meta = vec![Metadata::from(args.source_range)];
1162    let annotation_id = exec_state.next_uuid();
1163    let feature_control = AnnotationFeatureControl::builder()
1164        .entity_id(entity_id)
1165        .entity_pos(KPoint2d { x: 0.5, y: 0.5 })
1166        .leader_type(AnnotationLineEnd::Dot)
1167        .prefix(annotation.to_owned())
1168        .plane_id(frame_plane_id)
1169        .offset(if let Some(offset) = frame_position {
1170            KPoint2d {
1171                x: offset[0].to_mm(),
1172                y: offset[1].to_mm(),
1173            }
1174        } else {
1175            KPoint2d { x: 100.0, y: 100.0 }
1176        })
1177        .precision(0)
1178        .font_scale(style.font_scale.as_ref().map(|n| n.n as f32).unwrap_or(1.0))
1179        .font_point_size(style.font_point_size.as_ref().map(|n| n.n.round() as u32).unwrap_or(36))
1180        .leader_scale(leader_scale.map(|n| n.n as f32).unwrap_or(1.0))
1181        .build();
1182    let options = AnnotationOptions::builder().feature_control(feature_control).build();
1183    exec_state
1184        .batch_modeling_cmd(
1185            ModelingCmdMeta::from_args_id(exec_state, args, annotation_id),
1186            ModelingCmd::from(
1187                mcmd::NewAnnotation::builder()
1188                    .options(options)
1189                    .clobber(false)
1190                    .annotation_type(AnnotationType::T3D)
1191                    .build(),
1192            ),
1193        )
1194        .await?;
1195    annotations.push(GdtAnnotation {
1196        id: annotation_id,
1197        meta,
1198    });
1199    Ok(())
1200}
1201
1202fn gdt_control_frame(symbol: MbdSymbol, tolerance: f64, datums: &[char]) -> AnnotationMbdControlFrame {
1203    match datums {
1204        [] => AnnotationMbdControlFrame::builder()
1205            .symbol(symbol)
1206            .tolerance(tolerance)
1207            .build(),
1208        [primary] => AnnotationMbdControlFrame::builder()
1209            .symbol(symbol)
1210            .tolerance(tolerance)
1211            .primary_datum(*primary)
1212            .build(),
1213        [primary, secondary] => AnnotationMbdControlFrame::builder()
1214            .symbol(symbol)
1215            .tolerance(tolerance)
1216            .primary_datum(*primary)
1217            .secondary_datum(*secondary)
1218            .build(),
1219        [primary, secondary, tertiary] => AnnotationMbdControlFrame::builder()
1220            .symbol(symbol)
1221            .tolerance(tolerance)
1222            .primary_datum(*primary)
1223            .secondary_datum(*secondary)
1224            .tertiary_datum(*tertiary)
1225            .build(),
1226        _ => unreachable!("resolve_datums rejects more than three datums"),
1227    }
1228}
1229
1230fn resolve_datums(datums: Option<Vec<String>>, args: &Args, annotation_name: &str) -> Result<Vec<char>, KclError> {
1231    let datums = datums.unwrap_or_default();
1232    if datums.len() > 3 {
1233        return Err(KclError::new_semantic(KclErrorDetails::new(
1234            format!("{annotation_name} datums must include at most three names."),
1235            vec![args.source_range],
1236        )));
1237    }
1238
1239    let mut resolved = Vec::with_capacity(datums.len());
1240    for datum in &datums {
1241        let mut chars = datum.chars();
1242        let Some(name) = chars.next() else {
1243            return Err(KclError::new_semantic(KclErrorDetails::new(
1244                format!("{annotation_name} datum names must be a single character."),
1245                vec![args.source_range],
1246            )));
1247        };
1248        if chars.next().is_some() {
1249            return Err(KclError::new_semantic(KclErrorDetails::new(
1250                format!("{annotation_name} datum names must be a single character."),
1251                vec![args.source_range],
1252            )));
1253        }
1254        resolved.push(name);
1255    }
1256
1257    Ok(resolved)
1258}
1259
1260/// Get the XY plane by evaluating the `XY` expression so that it's the same as
1261/// if the user specified `XY`.
1262async fn xy_plane(exec_state: &mut ExecState, args: &Args) -> Result<Plane, KclError> {
1263    let plane_ast = plane_ast("XY", args.source_range);
1264    let metadata = Metadata::from(args.source_range);
1265    let plane_value = args
1266        .ctx
1267        .execute_expr(&plane_ast, exec_state, &metadata, &[], StatementKind::Expression)
1268        .await?;
1269    let plane_value = match plane_value.control {
1270        ControlFlowKind::Continue => plane_value.into_value(),
1271        ControlFlowKind::Exit => {
1272            let message = "Early return inside plane value is currently not supported".to_owned();
1273            debug_assert!(false, "{}", &message);
1274            return Err(KclError::new_internal(KclErrorDetails::new(
1275                message,
1276                vec![args.source_range],
1277            )));
1278        }
1279    };
1280    Ok(plane_value
1281        .as_plane()
1282        .ok_or_else(|| {
1283            KclError::new_internal(KclErrorDetails::new(
1284                "Expected XY plane to be defined".to_owned(),
1285                vec![args.source_range],
1286            ))
1287        })?
1288        .clone())
1289}
1290
1291/// An AST node for a plane with the given name.
1292fn plane_ast(plane_name: &str, range: SourceRange) -> ast::Node<ast::Expr> {
1293    ast::Node::new(
1294        ast::Expr::Name(Box::new(ast::Node::new(
1295            ast::Name {
1296                name: ast::Identifier::new(plane_name),
1297                path: Vec::new(),
1298                // TODO: We may want to set this to true once we implement it to
1299                // prevent it breaking if users redefine the identifier.
1300                abs_path: false,
1301                digest: None,
1302            },
1303            range.start(),
1304            range.end(),
1305            range.module_id(),
1306        ))),
1307        range.start(),
1308        range.end(),
1309        range.module_id(),
1310    )
1311}