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