kcl_lib/std/
planes.rs

1//! Standard library plane helpers.
2
3use kcl_derive_docs::stdlib;
4use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, shared::Color, ModelingCmd};
5use kittycad_modeling_cmds as kcmc;
6
7use super::sketch::PlaneData;
8use crate::{
9    errors::KclError,
10    execution::{ExecState, KclValue, Plane, PlaneType},
11    std::Args,
12};
13
14/// Offset a plane by a distance along its normal.
15pub async fn offset_plane(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
16    let std_plane = args.get_unlabeled_kw_arg("plane")?;
17    let offset = args.get_kw_arg("offset")?;
18    let plane = inner_offset_plane(std_plane, offset, exec_state).await?;
19    make_offset_plane_in_engine(&plane, exec_state, &args).await?;
20    Ok(KclValue::Plane { value: Box::new(plane) })
21}
22
23/// Offset a plane by a distance along its normal.
24///
25/// For example, if you offset the 'XZ' plane by 10, the new plane will be parallel to the 'XZ'
26/// plane and 10 units away from it.
27///
28/// ```no_run
29/// // Loft a square and a circle on the `XY` plane using offset.
30/// squareSketch = startSketchOn('XY')
31///     |> startProfileAt([-100, 200], %)
32///     |> line(end = [200, 0])
33///     |> line(end = [0, -200])
34///     |> line(end = [-200, 0])
35///     |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
36///     |> close()
37///
38/// circleSketch = startSketchOn(offsetPlane('XY', offset = 150))
39///     |> circle( center = [0, 100], radius = 50 )
40///
41/// loft([squareSketch, circleSketch])
42/// ```
43///
44/// ```no_run
45/// // Loft a square and a circle on the `XZ` plane using offset.
46/// squareSketch = startSketchOn('XZ')
47///     |> startProfileAt([-100, 200], %)
48///     |> line(end = [200, 0])
49///     |> line(end = [0, -200])
50///     |> line(end = [-200, 0])
51///     |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
52///     |> close()
53///
54/// circleSketch = startSketchOn(offsetPlane('XZ', offset = 150))
55///     |> circle( center = [0, 100], radius = 50 )
56///
57/// loft([squareSketch, circleSketch])
58/// ```
59///
60/// ```no_run
61/// // Loft a square and a circle on the `YZ` plane using offset.
62/// squareSketch = startSketchOn('YZ')
63///     |> startProfileAt([-100, 200], %)
64///     |> line(end = [200, 0])
65///     |> line(end = [0, -200])
66///     |> line(end = [-200, 0])
67///     |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
68///     |> close()
69///
70/// circleSketch = startSketchOn(offsetPlane('YZ', offset = 150))
71///     |> circle( center = [0, 100], radius = 50 )
72///
73/// loft([squareSketch, circleSketch])
74/// ```
75///
76/// ```no_run
77/// // Loft a square and a circle on the `-XZ` plane using offset.
78/// squareSketch = startSketchOn('-XZ')
79///     |> startProfileAt([-100, 200], %)
80///     |> line(end = [200, 0])
81///     |> line(end = [0, -200])
82///     |> line(end = [-200, 0])
83///     |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
84///     |> close()
85///
86/// circleSketch = startSketchOn(offsetPlane('-XZ', offset = -150))
87///     |> circle( center = [0, 100], radius = 50 )
88///
89/// loft([squareSketch, circleSketch])
90/// ```
91/// ```no_run
92/// // A circle on the XY plane
93/// startSketchOn("XY")
94///   |> startProfileAt([0, 0], %)
95///   |> circle( radius = 10, center = [0, 0] )
96///   
97/// // Triangle on the plane 4 units above
98/// startSketchOn(offsetPlane("XY", offset = 4))
99///   |> startProfileAt([0, 0], %)
100///   |> line(end = [10, 0])
101///   |> line(end = [0, 10])
102///   |> close()
103/// ```
104
105#[stdlib {
106    name = "offsetPlane",
107    feature_tree_operation = true,
108    keywords = true,
109    unlabeled_first = true,
110    args = {
111        plane = { docs = "The plane (e.g. 'XY') which this new plane is created from." },
112        offset = { docs = "Distance from the standard plane this new plane will be created at." },
113    }
114}]
115async fn inner_offset_plane(plane: PlaneData, offset: f64, exec_state: &mut ExecState) -> Result<Plane, KclError> {
116    let mut plane = Plane::from_plane_data(plane, exec_state);
117    // Though offset planes might be derived from standard planes, they are not
118    // standard planes themselves.
119    plane.value = PlaneType::Custom;
120
121    plane.origin += plane.z_axis * offset;
122
123    Ok(plane)
124}
125
126// Engine-side effectful creation of an actual plane object.
127// offset planes are shown by default, and hidden by default if they
128// are used as a sketch plane. That hiding command is sent within inner_start_profile_at
129async fn make_offset_plane_in_engine(plane: &Plane, exec_state: &mut ExecState, args: &Args) -> Result<(), KclError> {
130    // Create new default planes.
131    let default_size = 100.0;
132    let color = Color {
133        r: 0.6,
134        g: 0.6,
135        b: 0.6,
136        a: 0.3,
137    };
138
139    args.batch_modeling_cmd(
140        plane.id,
141        ModelingCmd::from(mcmd::MakePlane {
142            clobber: false,
143            origin: plane.origin.into(),
144            size: LengthUnit(default_size),
145            x_axis: plane.x_axis.into(),
146            y_axis: plane.y_axis.into(),
147            hide: Some(false),
148        }),
149    )
150    .await?;
151
152    // Set the color.
153    args.batch_modeling_cmd(
154        exec_state.next_uuid(),
155        ModelingCmd::from(mcmd::PlaneSetColor {
156            color,
157            plane_id: plane.id,
158        }),
159    )
160    .await?;
161
162    Ok(())
163}