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: crate::execution::Point3d {
53 x: 0.0,
54 y: 0.0,
55 z: 0.0,
56 units: Some(UnitLength::Millimeters),
57 },
58 x_axis: crate::execution::Point3d {
59 x: 1.0,
60 y: 0.0,
61 z: 0.0,
62 units: None,
63 },
64 y_axis: crate::execution::Point3d {
65 x: 0.0,
66 y: 1.0,
67 z: 0.0,
68 units: None,
69 },
70 z_axis: crate::execution::Point3d {
71 x: 0.0,
72 y: 0.0,
73 z: 1.0,
74 units: None,
75 },
76 },
77 meta: vec![Metadata {
78 source_range: args.source_range,
79 }],
80 });
81 }
82
83 exec_state
85 .flush_batch_for_solids(args.into(), std::slice::from_ref(&solid))
86 .await?;
87
88 let face_id = face.get_face_id(&solid, exec_state, args, true).await?;
90 let plane_id = exec_state.id_generator().next_uuid();
91 let meta = ModelingCmdMeta::with_id(&args.ctx, args.source_range, plane_id);
92 let cmd = ModelingCmd::FaceIsPlanar(mcmd::FaceIsPlanar { object_id: face_id });
93 let plane_resp = exec_state.send_modeling_cmd(meta, cmd).await?;
94 let OkWebSocketResponseData::Modeling {
95 modeling_response: OkModelingCmdResponse::FaceIsPlanar(planar),
96 } = plane_resp
97 else {
98 return Err(KclError::new_semantic(KclErrorDetails::new(
99 format!(
100 "Engine returned invalid response, it should have returned FaceIsPlanar but it returned {plane_resp:#?}"
101 ),
102 vec![args.source_range],
103 )));
104 };
105
106 let not_planar: Result<_, KclError> = Err(KclError::new_semantic(KclErrorDetails::new(
108 "The face you provided doesn't lie on any plane. It might be curved.".to_owned(),
109 vec![args.source_range],
110 )));
111 let Some(x_axis) = planar.x_axis else { return not_planar };
112 let Some(y_axis) = planar.y_axis else { return not_planar };
113 let Some(z_axis) = planar.z_axis else { return not_planar };
114 let Some(origin) = planar.origin else { return not_planar };
115
116 let engine_units = Some(UnitLength::Millimeters);
118 let x_axis = crate::execution::Point3d {
119 x: x_axis.x,
120 y: x_axis.y,
121 z: x_axis.z,
122 units: engine_units,
123 };
124 let y_axis = crate::execution::Point3d {
125 x: y_axis.x,
126 y: y_axis.y,
127 z: y_axis.z,
128 units: engine_units,
129 };
130 let z_axis = crate::execution::Point3d {
131 x: z_axis.x,
132 y: z_axis.y,
133 z: z_axis.z,
134 units: engine_units,
135 };
136 let origin = crate::execution::Point3d {
137 x: origin.x.0,
138 y: origin.y.0,
139 z: origin.z.0,
140 units: engine_units,
141 };
142
143 let plane_info = crate::execution::PlaneInfo {
146 origin,
147 x_axis,
148 y_axis,
149 z_axis,
150 };
151 let plane_info = plane_info.make_right_handed();
152
153 Ok(Plane {
154 artifact_id: plane_id.into(),
155 id: plane_id,
156 value: PlaneType::Custom,
157 info: plane_info,
158 meta: vec![Metadata {
159 source_range: args.source_range,
160 }],
161 })
162}
163
164pub async fn offset_plane(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
166 let std_plane = args.get_unlabeled_kw_arg("plane", &RuntimeType::plane(), exec_state)?;
167 let offset: TyF64 = args.get_kw_arg("offset", &RuntimeType::length(), exec_state)?;
168 let plane = inner_offset_plane(std_plane, offset, exec_state, &args).await?;
169 Ok(KclValue::Plane { value: Box::new(plane) })
170}
171
172async fn inner_offset_plane(
173 plane: PlaneData,
174 offset: TyF64,
175 exec_state: &mut ExecState,
176 args: &Args,
177) -> Result<Plane, KclError> {
178 let mut plane = Plane::from_plane_data(plane, exec_state)?;
179 plane.value = PlaneType::Custom;
182
183 let normal = plane.info.x_axis.axes_cross_product(&plane.info.y_axis);
184 plane.info.origin += normal * offset.to_length_units(plane.info.origin.units.unwrap_or(UnitLength::Millimeters));
185 make_offset_plane_in_engine(&plane, exec_state, args).await?;
186
187 Ok(plane)
188}
189
190async fn make_offset_plane_in_engine(plane: &Plane, exec_state: &mut ExecState, args: &Args) -> Result<(), KclError> {
194 let default_size = 100.0;
196 let color = Color {
197 r: 0.6,
198 g: 0.6,
199 b: 0.6,
200 a: 0.3,
201 };
202
203 let meta = ModelingCmdMeta::from_args_id(args, plane.id);
204 exec_state
205 .batch_modeling_cmd(
206 meta,
207 ModelingCmd::from(mcmd::MakePlane {
208 clobber: false,
209 origin: plane.info.origin.into(),
210 size: LengthUnit(default_size),
211 x_axis: plane.info.x_axis.into(),
212 y_axis: plane.info.y_axis.into(),
213 hide: Some(false),
214 }),
215 )
216 .await?;
217
218 exec_state
220 .batch_modeling_cmd(
221 args.into(),
222 ModelingCmd::from(mcmd::PlaneSetColor {
223 color,
224 plane_id: plane.id,
225 }),
226 )
227 .await?;
228
229 Ok(())
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235 use crate::execution::{PlaneInfo, Point3d};
236
237 #[test]
238 fn fixes_left_handed_plane() {
239 let plane_info = PlaneInfo {
240 origin: Point3d {
241 x: 0.0,
242 y: 0.0,
243 z: 0.0,
244 units: Some(UnitLength::Millimeters),
245 },
246 x_axis: Point3d {
247 x: 1.0,
248 y: 0.0,
249 z: 0.0,
250 units: None,
251 },
252 y_axis: Point3d {
253 x: 0.0,
254 y: 1.0,
255 z: 0.0,
256 units: None,
257 },
258 z_axis: Point3d {
259 x: 0.0,
260 y: 0.0,
261 z: -1.0,
262 units: None,
263 },
264 };
265
266 assert!(plane_info.is_left_handed());
268 let fixed = plane_info.make_right_handed();
270 assert!(fixed.is_right_handed());
271 }
272}