1use kcmc::ModelingCmd;
4use kcmc::each_cmd as mcmd;
5use kcmc::length_unit::LengthUnit;
6use kcmc::shared::Color;
7use kittycad_modeling_cmds::ok_response::OkModelingCmdResponse;
8use kittycad_modeling_cmds::units::UnitLength;
9use kittycad_modeling_cmds::websocket::OkWebSocketResponseData;
10use kittycad_modeling_cmds::{self as kcmc};
11
12use super::args::TyF64;
13use super::sketch::PlaneData;
14use crate::errors::KclError;
15use crate::errors::KclErrorDetails;
16use crate::execution::ExecState;
17use crate::execution::KclValue;
18use crate::execution::Metadata;
19use crate::execution::ModelingCmdMeta;
20use crate::execution::Plane;
21use crate::execution::PlaneInfo;
22use crate::execution::PlaneKind;
23use crate::execution::types::RuntimeType;
24use crate::std::Args;
25use crate::std::faces::FaceSpecifier;
26
27pub async fn plane_of(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
29 let solid = args.get_unlabeled_kw_arg("solid", &RuntimeType::solid(), exec_state)?;
30 let face = args.get_kw_arg("face", &RuntimeType::tagged_face_or_segment(), exec_state)?;
31
32 inner_plane_of(solid, face, exec_state, &args)
33 .await
34 .map(Box::new)
35 .map(|value| KclValue::Plane { value })
36}
37
38pub(crate) async fn inner_plane_of(
39 solid: crate::execution::Solid,
40 face: FaceSpecifier,
41 exec_state: &mut ExecState,
42 args: &Args,
43) -> Result<Plane, KclError> {
44 let plane_id = exec_state.id_generator().next_uuid();
45
46 if args.ctx.no_engine_commands().await {
49 exec_state.err(crate::CompilationError {
50 source_range: args.source_range,
51 message: "The engine isn't available, so returning an arbitrary incorrect plane".to_owned(),
52 suggestion: None,
53 severity: crate::errors::Severity::Error,
54 tag: crate::errors::Tag::None,
55 });
56 return Ok(Plane {
57 artifact_id: plane_id.into(),
58 id: plane_id,
59 object_id: None,
62 kind: PlaneKind::Custom,
63 info: crate::execution::PlaneInfo {
64 origin: crate::execution::Point3d {
65 x: 0.0,
66 y: 0.0,
67 z: 0.0,
68 units: Some(UnitLength::Millimeters),
69 },
70 x_axis: crate::execution::Point3d {
71 x: 1.0,
72 y: 0.0,
73 z: 0.0,
74 units: None,
75 },
76 y_axis: crate::execution::Point3d {
77 x: 0.0,
78 y: 1.0,
79 z: 0.0,
80 units: None,
81 },
82 z_axis: crate::execution::Point3d {
83 x: 0.0,
84 y: 0.0,
85 z: 1.0,
86 units: None,
87 },
88 },
89 meta: vec![Metadata {
90 source_range: args.source_range,
91 }],
92 });
93 }
94
95 exec_state
97 .flush_batch_for_solids(
98 ModelingCmdMeta::from_args(exec_state, args),
99 std::slice::from_ref(&solid),
100 )
101 .await?;
102
103 let face_id = face.face_id(&solid, exec_state, args, true).await?;
105 let meta = ModelingCmdMeta::from_args_id(exec_state, args, plane_id);
106 let cmd = ModelingCmd::FaceIsPlanar(mcmd::FaceIsPlanar::builder().object_id(face_id).build());
107 let plane_resp = exec_state.send_modeling_cmd(meta, cmd).await?;
108 let OkWebSocketResponseData::Modeling {
109 modeling_response: OkModelingCmdResponse::FaceIsPlanar(planar),
110 } = plane_resp
111 else {
112 return Err(KclError::new_semantic(KclErrorDetails::new(
113 format!(
114 "Engine returned invalid response, it should have returned FaceIsPlanar but it returned {plane_resp:#?}"
115 ),
116 vec![args.source_range],
117 )));
118 };
119
120 let not_planar: Result<_, KclError> = Err(KclError::new_semantic(KclErrorDetails::new(
122 "The face you provided doesn't lie on any plane. It might be curved.".to_owned(),
123 vec![args.source_range],
124 )));
125 let Some(x_axis) = planar.x_axis else { return not_planar };
126 let Some(y_axis) = planar.y_axis else { return not_planar };
127 let Some(z_axis) = planar.z_axis else { return not_planar };
128 let Some(origin) = planar.origin else { return not_planar };
129
130 let engine_units = Some(UnitLength::Millimeters);
132 let x_axis = crate::execution::Point3d {
133 x: x_axis.x,
134 y: x_axis.y,
135 z: x_axis.z,
136 units: engine_units,
137 };
138 let y_axis = crate::execution::Point3d {
139 x: y_axis.x,
140 y: y_axis.y,
141 z: y_axis.z,
142 units: engine_units,
143 };
144 let z_axis = crate::execution::Point3d {
145 x: z_axis.x,
146 y: z_axis.y,
147 z: z_axis.z,
148 units: engine_units,
149 };
150 let origin = crate::execution::Point3d {
151 x: origin.x.0,
152 y: origin.y.0,
153 z: origin.z.0,
154 units: engine_units,
155 };
156
157 let plane_info = crate::execution::PlaneInfo {
160 origin,
161 x_axis,
162 y_axis,
163 z_axis,
164 };
165 let plane_info = plane_info.make_right_handed();
166
167 let plane_object_id = exec_state.next_object_id();
168 #[cfg(feature = "artifact-graph")]
169 {
170 use crate::execution::ArtifactId;
171
172 let plane_object = crate::front::Object {
173 id: plane_object_id,
174 kind: crate::front::ObjectKind::Plane(crate::front::Plane::Object(plane_object_id)),
175 label: Default::default(),
176 comments: Default::default(),
177 artifact_id: ArtifactId::new(plane_id),
178 source: args.source_range.into(),
179 };
180 exec_state.add_scene_object(plane_object, args.source_range);
181 }
182
183 Ok(Plane {
184 artifact_id: plane_id.into(),
185 id: plane_id,
186 object_id: Some(plane_object_id),
187 kind: PlaneKind::Custom,
188 info: plane_info,
189 meta: vec![Metadata {
190 source_range: args.source_range,
191 }],
192 })
193}
194
195pub async fn offset_plane(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
197 let std_plane = args.get_unlabeled_kw_arg("plane", &RuntimeType::plane(), exec_state)?;
198 let offset: TyF64 = args.get_kw_arg("offset", &RuntimeType::length(), exec_state)?;
199 let plane = inner_offset_plane(std_plane, offset, exec_state, &args).await?;
200 Ok(KclValue::Plane { value: Box::new(plane) })
201}
202
203async fn inner_offset_plane(
204 plane: PlaneData,
205 offset: TyF64,
206 exec_state: &mut ExecState,
207 args: &Args,
208) -> Result<Plane, KclError> {
209 let mut info = PlaneInfo::try_from(plane)?;
210
211 let normal = info.x_axis.axes_cross_product(&info.y_axis);
212 info.origin += normal * offset.to_length_units(info.origin.units.unwrap_or(UnitLength::Millimeters));
213
214 let id = exec_state.next_uuid();
215 let mut plane = Plane {
216 id,
217 artifact_id: id.into(),
218 object_id: None,
219 kind: PlaneKind::Custom,
220 info,
221 meta: vec![Metadata {
222 source_range: args.source_range,
223 }],
224 };
225 make_offset_plane_in_engine(&mut plane, exec_state, args).await?;
226
227 Ok(plane)
228}
229
230async fn make_offset_plane_in_engine(
234 plane: &mut Plane,
235 exec_state: &mut ExecState,
236 args: &Args,
237) -> Result<(), KclError> {
238 let plane_object_id = exec_state.next_object_id();
239 #[cfg(feature = "artifact-graph")]
240 {
241 let plane_object = crate::front::Object {
242 id: plane_object_id,
243 kind: crate::front::ObjectKind::Plane(crate::front::Plane::Object(plane_object_id)),
244 label: Default::default(),
245 comments: Default::default(),
246 artifact_id: plane.artifact_id,
247 source: args.source_range.into(),
248 };
249 exec_state.add_scene_object(plane_object, args.source_range);
250 }
251
252 let default_size = 100.0;
254 let color = Color::from_rgba(0.6, 0.6, 0.6, 0.3);
255
256 let meta = ModelingCmdMeta::from_args_id(exec_state, args, plane.id);
257 exec_state
258 .batch_modeling_cmd(
259 meta,
260 ModelingCmd::from(
261 mcmd::MakePlane::builder()
262 .clobber(false)
263 .origin(plane.info.origin.into())
264 .size(LengthUnit(default_size))
265 .x_axis(plane.info.x_axis.into())
266 .y_axis(plane.info.y_axis.into())
267 .hide(false)
268 .build(),
269 ),
270 )
271 .await?;
272
273 exec_state
275 .batch_modeling_cmd(
276 ModelingCmdMeta::from_args(exec_state, args),
277 ModelingCmd::from(mcmd::PlaneSetColor::builder().color(color).plane_id(plane.id).build()),
278 )
279 .await?;
280
281 plane.kind = PlaneKind::Custom;
284 plane.object_id = Some(plane_object_id);
285
286 Ok(())
287}
288
289#[cfg(test)]
290mod tests {
291 use super::*;
292 use crate::execution::PlaneInfo;
293 use crate::execution::Point3d;
294
295 #[test]
296 fn fixes_left_handed_plane() {
297 let plane_info = PlaneInfo {
298 origin: Point3d {
299 x: 0.0,
300 y: 0.0,
301 z: 0.0,
302 units: Some(UnitLength::Millimeters),
303 },
304 x_axis: Point3d {
305 x: 1.0,
306 y: 0.0,
307 z: 0.0,
308 units: None,
309 },
310 y_axis: Point3d {
311 x: 0.0,
312 y: 1.0,
313 z: 0.0,
314 units: None,
315 },
316 z_axis: Point3d {
317 x: 0.0,
318 y: 0.0,
319 z: -1.0,
320 units: None,
321 },
322 };
323
324 assert!(plane_info.is_left_handed());
326 let fixed = plane_info.make_right_handed();
328 assert!(fixed.is_right_handed());
329 }
330}