1use kcmc::{ModelingCmd, each_cmd as mcmd, length_unit::LengthUnit, shared::Color};
4use kittycad_modeling_cmds::{
5 self as kcmc, ok_response::OkModelingCmdResponse, units::UnitLength, websocket::OkWebSocketResponseData,
6};
7
8use super::{
9 args::TyF64,
10 sketch::{FaceTag, PlaneData},
11};
12use crate::{
13 errors::{KclError, KclErrorDetails},
14 execution::{ExecState, KclValue, Metadata, ModelingCmdMeta, Plane, PlaneType, types::RuntimeType},
15 std::Args,
16};
17
18pub async fn plane_of(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
20 let solid = args.get_unlabeled_kw_arg("solid", &RuntimeType::solid(), exec_state)?;
21 let face = args.get_kw_arg("face", &RuntimeType::tagged_face(), exec_state)?;
22
23 inner_plane_of(solid, face, exec_state, &args)
24 .await
25 .map(Box::new)
26 .map(|value| KclValue::Plane { value })
27}
28
29pub(crate) async fn inner_plane_of(
30 solid: crate::execution::Solid,
31 face: FaceTag,
32 exec_state: &mut ExecState,
33 args: &Args,
34) -> Result<Plane, KclError> {
35 if args.ctx.no_engine_commands().await {
38 let plane_id = exec_state.id_generator().next_uuid();
39 exec_state.err(crate::CompilationError {
40 source_range: args.source_range,
41 message: "The engine isn't available, so returning an arbitrary incorrect plane".to_owned(),
42 suggestion: None,
43 severity: crate::errors::Severity::Error,
44 tag: crate::errors::Tag::None,
45 });
46 return Ok(Plane {
47 artifact_id: plane_id.into(),
48 id: plane_id,
49 value: PlaneType::Uninit,
51 info: crate::execution::PlaneInfo {
52 origin: Default::default(),
53 x_axis: Default::default(),
54 y_axis: Default::default(),
55 z_axis: Default::default(),
56 },
57 meta: vec![Metadata {
58 source_range: args.source_range,
59 }],
60 });
61 }
62
63 let face_id = face.get_face_id(&solid, exec_state, args, true).await?;
65 let plane_id = exec_state.id_generator().next_uuid();
66 let meta = ModelingCmdMeta::with_id(&args.ctx, args.source_range, plane_id);
67 let cmd = ModelingCmd::FaceIsPlanar(mcmd::FaceIsPlanar { object_id: face_id });
68 let plane_resp = exec_state.send_modeling_cmd(meta, cmd).await?;
69 let OkWebSocketResponseData::Modeling {
70 modeling_response: OkModelingCmdResponse::FaceIsPlanar(planar),
71 } = plane_resp
72 else {
73 return Err(KclError::new_semantic(KclErrorDetails::new(
74 format!(
75 "Engine returned invalid response, it should have returned FaceIsPlanar but it returned {plane_resp:#?}"
76 ),
77 vec![args.source_range],
78 )));
79 };
80
81 let not_planar: Result<_, KclError> = Err(KclError::new_semantic(KclErrorDetails::new(
83 "The face you provided doesn't lie on any plane. It might be curved.".to_owned(),
84 vec![args.source_range],
85 )));
86 let Some(x_axis) = planar.x_axis else { return not_planar };
87 let Some(y_axis) = planar.y_axis else { return not_planar };
88 let Some(z_axis) = planar.z_axis else { return not_planar };
89 let Some(origin) = planar.origin else { return not_planar };
90
91 let engine_units = Some(UnitLength::Millimeters);
93 let x_axis = crate::execution::Point3d {
94 x: x_axis.x,
95 y: x_axis.y,
96 z: x_axis.z,
97 units: engine_units,
98 };
99 let y_axis = crate::execution::Point3d {
100 x: y_axis.x,
101 y: y_axis.y,
102 z: y_axis.z,
103 units: engine_units,
104 };
105 let z_axis = crate::execution::Point3d {
106 x: z_axis.x,
107 y: z_axis.y,
108 z: z_axis.z,
109 units: engine_units,
110 };
111 let origin = crate::execution::Point3d {
112 x: origin.x.0,
113 y: origin.y.0,
114 z: origin.z.0,
115 units: engine_units,
116 };
117
118 let plane_info = crate::execution::PlaneInfo {
121 origin,
122 x_axis,
123 y_axis,
124 z_axis,
125 };
126 let plane_info = plane_info.make_right_handed();
127
128 Ok(Plane {
129 artifact_id: plane_id.into(),
130 id: plane_id,
131 value: PlaneType::Custom,
132 info: plane_info,
133 meta: vec![Metadata {
134 source_range: args.source_range,
135 }],
136 })
137}
138
139pub async fn offset_plane(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
141 let std_plane = args.get_unlabeled_kw_arg("plane", &RuntimeType::plane(), exec_state)?;
142 let offset: TyF64 = args.get_kw_arg("offset", &RuntimeType::length(), exec_state)?;
143 let plane = inner_offset_plane(std_plane, offset, exec_state, &args).await?;
144 Ok(KclValue::Plane { value: Box::new(plane) })
145}
146
147async fn inner_offset_plane(
148 plane: PlaneData,
149 offset: TyF64,
150 exec_state: &mut ExecState,
151 args: &Args,
152) -> Result<Plane, KclError> {
153 let mut plane = Plane::from_plane_data(plane, exec_state)?;
154 plane.value = PlaneType::Custom;
157
158 let normal = plane.info.x_axis.axes_cross_product(&plane.info.y_axis);
159 plane.info.origin += normal * offset.to_length_units(plane.info.origin.units.unwrap_or(UnitLength::Millimeters));
160 make_offset_plane_in_engine(&plane, exec_state, args).await?;
161
162 Ok(plane)
163}
164
165async fn make_offset_plane_in_engine(plane: &Plane, exec_state: &mut ExecState, args: &Args) -> Result<(), KclError> {
169 let default_size = 100.0;
171 let color = Color {
172 r: 0.6,
173 g: 0.6,
174 b: 0.6,
175 a: 0.3,
176 };
177
178 let meta = ModelingCmdMeta::from_args_id(args, plane.id);
179 exec_state
180 .batch_modeling_cmd(
181 meta,
182 ModelingCmd::from(mcmd::MakePlane {
183 clobber: false,
184 origin: plane.info.origin.into(),
185 size: LengthUnit(default_size),
186 x_axis: plane.info.x_axis.into(),
187 y_axis: plane.info.y_axis.into(),
188 hide: Some(false),
189 }),
190 )
191 .await?;
192
193 exec_state
195 .batch_modeling_cmd(
196 args.into(),
197 ModelingCmd::from(mcmd::PlaneSetColor {
198 color,
199 plane_id: plane.id,
200 }),
201 )
202 .await?;
203
204 Ok(())
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210 use crate::execution::{PlaneInfo, Point3d};
211
212 #[test]
213 fn fixes_left_handed_plane() {
214 let plane_info = PlaneInfo {
215 origin: Point3d {
216 x: 0.0,
217 y: 0.0,
218 z: 0.0,
219 units: Some(UnitLength::Millimeters),
220 },
221 x_axis: Point3d {
222 x: 1.0,
223 y: 0.0,
224 z: 0.0,
225 units: None,
226 },
227 y_axis: Point3d {
228 x: 0.0,
229 y: 1.0,
230 z: 0.0,
231 units: None,
232 },
233 z_axis: Point3d {
234 x: 0.0,
235 y: 0.0,
236 z: -1.0,
237 units: None,
238 },
239 };
240
241 assert!(plane_info.is_left_handed());
243 let fixed = plane_info.make_right_handed();
245 assert!(fixed.is_right_handed());
246 }
247}