kcl_lib/std/
mirror.rs

1//! Standard library mirror.
2
3use anyhow::Result;
4use kcmc::{each_cmd as mcmd, ModelingCmd};
5use kittycad_modeling_cmds::{
6    self as kcmc, length_unit::LengthUnit, ok_response::OkModelingCmdResponse, output::EntityGetAllChildUuids,
7    shared::Point3d, websocket::OkWebSocketResponseData,
8};
9
10use crate::{
11    errors::{KclError, KclErrorDetails},
12    execution::{
13        types::{PrimitiveType, RuntimeType},
14        ExecState, KclValue, Sketch,
15    },
16    std::{axis_or_reference::Axis2dOrEdgeReference, Args},
17};
18
19/// Mirror a sketch.
20///
21/// Only works on unclosed sketches for now.
22pub async fn mirror_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
23    let sketches = args.get_unlabeled_kw_arg_typed("sketches", &RuntimeType::sketches(), exec_state)?;
24    let axis = args.get_kw_arg_typed(
25        "axis",
26        &RuntimeType::Union(vec![
27            RuntimeType::Primitive(PrimitiveType::Edge),
28            RuntimeType::Primitive(PrimitiveType::Axis2d),
29        ]),
30        exec_state,
31    )?;
32
33    let sketches = inner_mirror_2d(sketches, axis, exec_state, args).await?;
34    Ok(sketches.into())
35}
36
37/// Mirror a sketch.
38///
39/// Only works on unclosed sketches for now.
40async fn inner_mirror_2d(
41    sketches: Vec<Sketch>,
42    axis: Axis2dOrEdgeReference,
43    exec_state: &mut ExecState,
44    args: Args,
45) -> Result<Vec<Sketch>, KclError> {
46    let mut starting_sketches = sketches.clone();
47
48    // Update all to have a mirror.
49    starting_sketches.iter_mut().for_each(|sketch| {
50        sketch.mirror = Some(exec_state.next_uuid());
51    });
52
53    if args.ctx.no_engine_commands().await {
54        return Ok(starting_sketches);
55    }
56
57    match axis {
58        Axis2dOrEdgeReference::Axis { direction, origin } => {
59            args.batch_modeling_cmd(
60                exec_state.next_uuid(),
61                ModelingCmd::from(mcmd::EntityMirror {
62                    ids: starting_sketches.iter().map(|sketch| sketch.id).collect(),
63                    axis: Point3d {
64                        x: direction[0].n,
65                        y: direction[1].n,
66                        z: 0.0,
67                    },
68                    point: Point3d {
69                        x: LengthUnit(origin[0].n),
70                        y: LengthUnit(origin[1].n),
71                        z: LengthUnit(0.0),
72                    },
73                }),
74            )
75            .await?;
76        }
77        Axis2dOrEdgeReference::Edge(edge) => {
78            let edge_id = edge.get_engine_id(exec_state, &args)?;
79
80            args.batch_modeling_cmd(
81                exec_state.next_uuid(),
82                ModelingCmd::from(mcmd::EntityMirrorAcrossEdge {
83                    ids: starting_sketches.iter().map(|sketch| sketch.id).collect(),
84                    edge_id,
85                }),
86            )
87            .await?;
88        }
89    };
90
91    // After the mirror, get the first child uuid for the path.
92    // The "get extrusion face info" API call requires *any* edge on the sketch being extruded.
93    // But if you mirror2d a sketch these IDs might change so we need to get the children versus
94    // using the IDs we already have.
95    // We only do this with mirrors because otherwise it is a waste of a websocket call.
96    for sketch in &mut starting_sketches {
97        let response = args
98            .send_modeling_cmd(
99                exec_state.next_uuid(),
100                ModelingCmd::from(mcmd::EntityGetAllChildUuids { entity_id: sketch.id }),
101            )
102            .await?;
103        let OkWebSocketResponseData::Modeling {
104            modeling_response:
105                OkModelingCmdResponse::EntityGetAllChildUuids(EntityGetAllChildUuids { entity_ids: child_ids }),
106        } = response
107        else {
108            return Err(KclError::Internal(KclErrorDetails {
109                message: "Expected a successful response from EntityGetAllChildUuids".to_string(),
110                source_ranges: vec![args.source_range],
111            }));
112        };
113
114        if child_ids.len() >= 2 {
115            // The first child is the original sketch, the second is the mirrored sketch.
116            let child_id = child_ids[1];
117            sketch.mirror = Some(child_id);
118        } else {
119            return Err(KclError::Type(KclErrorDetails {
120                message: "Expected child uuids to be >= 2".to_string(),
121                source_ranges: vec![args.source_range],
122            }));
123        }
124    }
125
126    Ok(starting_sketches)
127}