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::CompilationIssue {
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 use crate::front::SourceRef;
172
173 let plane_object = crate::front::Object {
174 id: plane_object_id,
175 kind: crate::front::ObjectKind::Plane(crate::front::Plane::Object(plane_object_id)),
176 label: Default::default(),
177 comments: Default::default(),
178 artifact_id: ArtifactId::new(plane_id),
179 source: SourceRef::new(args.source_range, args.node_path.clone()),
180 };
181 exec_state.add_scene_object(plane_object, args.source_range);
182 }
183
184 Ok(Plane {
185 artifact_id: plane_id.into(),
186 id: plane_id,
187 object_id: Some(plane_object_id),
188 kind: PlaneKind::Custom,
189 info: plane_info,
190 meta: vec![Metadata {
191 source_range: args.source_range,
192 }],
193 })
194}
195
196pub async fn offset_plane(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
198 let std_plane = args.get_unlabeled_kw_arg("plane", &RuntimeType::plane(), exec_state)?;
199 let offset: TyF64 = args.get_kw_arg("offset", &RuntimeType::length(), exec_state)?;
200 let plane = inner_offset_plane(std_plane, offset, exec_state, &args).await?;
201 Ok(KclValue::Plane { value: Box::new(plane) })
202}
203
204async fn inner_offset_plane(
205 plane: PlaneData,
206 offset: TyF64,
207 exec_state: &mut ExecState,
208 args: &Args,
209) -> Result<Plane, KclError> {
210 let mut info = PlaneInfo::try_from(plane)?;
211
212 let normal = info.x_axis.axes_cross_product(&info.y_axis);
213 info.origin += normal * offset.to_length_units(info.origin.units.unwrap_or(UnitLength::Millimeters));
214
215 let id = exec_state.next_uuid();
216 let mut plane = Plane {
217 id,
218 artifact_id: id.into(),
219 object_id: None,
220 kind: PlaneKind::Custom,
221 info,
222 meta: vec![Metadata {
223 source_range: args.source_range,
224 }],
225 };
226 make_offset_plane_in_engine(&mut plane, exec_state, args).await?;
227
228 Ok(plane)
229}
230
231async fn make_offset_plane_in_engine(
235 plane: &mut Plane,
236 exec_state: &mut ExecState,
237 args: &Args,
238) -> Result<(), KclError> {
239 let plane_object_id = exec_state.next_object_id();
240 #[cfg(feature = "artifact-graph")]
241 {
242 use crate::front::SourceRef;
243
244 let plane_object = crate::front::Object {
245 id: plane_object_id,
246 kind: crate::front::ObjectKind::Plane(crate::front::Plane::Object(plane_object_id)),
247 label: Default::default(),
248 comments: Default::default(),
249 artifact_id: plane.artifact_id,
250 source: SourceRef::new(args.source_range, args.node_path.clone()),
251 };
252 exec_state.add_scene_object(plane_object, args.source_range);
253 }
254
255 let default_size = 100.0;
257 let color = Color::from_rgba(0.6, 0.6, 0.6, 0.3);
258
259 let meta = ModelingCmdMeta::from_args_id(exec_state, args, plane.id);
260 exec_state
261 .batch_modeling_cmd(
262 meta,
263 ModelingCmd::from(
264 mcmd::MakePlane::builder()
265 .clobber(false)
266 .origin(plane.info.origin.into())
267 .size(LengthUnit(default_size))
268 .x_axis(plane.info.x_axis.into())
269 .y_axis(plane.info.y_axis.into())
270 .hide(false)
271 .build(),
272 ),
273 )
274 .await?;
275
276 exec_state
278 .batch_modeling_cmd(
279 ModelingCmdMeta::from_args(exec_state, args),
280 ModelingCmd::from(mcmd::PlaneSetColor::builder().color(color).plane_id(plane.id).build()),
281 )
282 .await?;
283
284 plane.kind = PlaneKind::Custom;
287 plane.object_id = Some(plane_object_id);
288
289 Ok(())
290}
291
292#[cfg(test)]
293mod tests {
294 use super::*;
295 use crate::execution::PlaneInfo;
296 use crate::execution::Point3d;
297
298 #[test]
299 fn fixes_left_handed_plane() {
300 let plane_info = PlaneInfo {
301 origin: Point3d {
302 x: 0.0,
303 y: 0.0,
304 z: 0.0,
305 units: Some(UnitLength::Millimeters),
306 },
307 x_axis: Point3d {
308 x: 1.0,
309 y: 0.0,
310 z: 0.0,
311 units: None,
312 },
313 y_axis: Point3d {
314 x: 0.0,
315 y: 1.0,
316 z: 0.0,
317 units: None,
318 },
319 z_axis: Point3d {
320 x: 0.0,
321 y: 0.0,
322 z: -1.0,
323 units: None,
324 },
325 };
326
327 assert!(plane_info.is_left_handed());
329 let fixed = plane_info.make_right_handed();
331 assert!(fixed.is_right_handed());
332 }
333}