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