kcl_lib/std/mirror.rs
1//! Standard library mirror.
2
3use anyhow::Result;
4use kcl_derive_docs::stdlib;
5use kcmc::{each_cmd as mcmd, ModelingCmd};
6use kittycad_modeling_cmds::{self as kcmc};
7use schemars::JsonSchema;
8use serde::{Deserialize, Serialize};
9
10use crate::{
11 errors::KclError,
12 execution::{ExecState, KclValue, Sketch, SketchSet},
13 std::{axis_or_reference::Axis2dOrEdgeReference, Args},
14};
15
16/// Data for a mirror.
17#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
18#[ts(export)]
19#[serde(rename_all = "camelCase")]
20pub struct Mirror2dData {
21 /// Axis to use as mirror.
22 pub axis: Axis2dOrEdgeReference,
23}
24
25/// Mirror a sketch.
26///
27/// Only works on unclosed sketches for now.
28pub async fn mirror_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
29 let (data, sketch_set): (Mirror2dData, SketchSet) = args.get_data_and_sketch_set()?;
30
31 let sketches = inner_mirror_2d(data, sketch_set, exec_state, args).await?;
32 Ok(sketches.into())
33}
34
35/// Mirror a sketch.
36///
37/// Only works on unclosed sketches for now.
38///
39/// Mirror occurs around a local sketch axis rather than a global axis.
40///
41/// ```no_run
42/// // Mirror an un-closed sketch across the Y axis.
43/// sketch001 = startSketchOn('XZ')
44/// |> startProfileAt([0, 10], %)
45/// |> line(end = [15, 0])
46/// |> line(end = [-7, -3])
47/// |> line(end = [9, -1])
48/// |> line(end = [-8, -5])
49/// |> line(end = [9, -3])
50/// |> line(end = [-8, -3])
51/// |> line(end = [9, -1])
52/// |> line(end = [-19, -0])
53/// |> mirror2d({axis = 'Y'}, %)
54///
55/// example = extrude(sketch001, length = 10)
56/// ```
57///
58/// ```no_run
59/// // Mirror a un-closed sketch across the Y axis.
60/// sketch001 = startSketchOn('XZ')
61/// |> startProfileAt([0, 8.5], %)
62/// |> line(end = [20, -8.5])
63/// |> line(end = [-20, -8.5])
64/// |> mirror2d({axis = 'Y'}, %)
65///
66/// example = extrude(sketch001, length = 10)
67/// ```
68///
69/// ```no_run
70/// // Mirror a un-closed sketch across an edge.
71/// helper001 = startSketchOn('XZ')
72/// |> startProfileAt([0, 0], %)
73/// |> line(end = [0, 10], tag = $edge001)
74///
75/// sketch001 = startSketchOn('XZ')
76/// |> startProfileAt([0, 8.5], %)
77/// |> line(end = [20, -8.5])
78/// |> line(end = [-20, -8.5])
79/// |> mirror2d({axis = edge001}, %)
80///
81/// // example = extrude(sketch001, length = 10)
82/// ```
83///
84/// ```no_run
85/// // Mirror an un-closed sketch across a custom axis.
86/// sketch001 = startSketchOn('XZ')
87/// |> startProfileAt([0, 8.5], %)
88/// |> line(end = [20, -8.5])
89/// |> line(end = [-20, -8.5])
90/// |> mirror2d({
91/// axis = {
92/// custom = {
93/// axis = [0.0, 1.0],
94/// origin = [0.0, 0.0]
95/// }
96/// }
97/// }, %)
98///
99/// example = extrude(sketch001, length = 10)
100/// ```
101#[stdlib {
102 name = "mirror2d",
103}]
104async fn inner_mirror_2d(
105 data: Mirror2dData,
106 sketch_set: SketchSet,
107 exec_state: &mut ExecState,
108 args: Args,
109) -> Result<Vec<Box<Sketch>>, KclError> {
110 let starting_sketches = match sketch_set {
111 SketchSet::Sketch(sketch) => vec![sketch],
112 SketchSet::Sketches(sketches) => sketches,
113 };
114
115 if args.ctx.no_engine_commands().await {
116 return Ok(starting_sketches);
117 }
118
119 match data.axis {
120 Axis2dOrEdgeReference::Axis(axis) => {
121 let (axis, origin) = axis.axis_and_origin()?;
122
123 args.batch_modeling_cmd(
124 exec_state.next_uuid(),
125 ModelingCmd::from(mcmd::EntityMirror {
126 ids: starting_sketches.iter().map(|sketch| sketch.id).collect(),
127 axis,
128 point: origin,
129 }),
130 )
131 .await?;
132 }
133 Axis2dOrEdgeReference::Edge(edge) => {
134 let edge_id = edge.get_engine_id(exec_state, &args)?;
135
136 args.batch_modeling_cmd(
137 exec_state.next_uuid(),
138 ModelingCmd::from(mcmd::EntityMirrorAcrossEdge {
139 ids: starting_sketches.iter().map(|sketch| sketch.id).collect(),
140 edge_id,
141 }),
142 )
143 .await?;
144 }
145 };
146
147 Ok(starting_sketches)
148}