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}