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
28async 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 },
55 meta: vec![Metadata {
56 source_range: args.source_range,
57 }],
58 });
59 }
60
61 let face_id = face.get_face_id(&solid, exec_state, args, true).await?;
63 let meta = args.into();
64 let cmd = ModelingCmd::FaceIsPlanar(mcmd::FaceIsPlanar { object_id: face_id });
65 let plane_resp = exec_state.send_modeling_cmd(meta, cmd).await?;
66 let OkWebSocketResponseData::Modeling {
67 modeling_response: OkModelingCmdResponse::FaceIsPlanar(planar),
68 } = plane_resp
69 else {
70 return Err(KclError::new_semantic(KclErrorDetails::new(
71 format!(
72 "Engine returned invalid response, it should have returned FaceIsPlanar but it returned {plane_resp:#?}"
73 ),
74 vec![args.source_range],
75 )));
76 };
77 let not_planar: Result<_, KclError> = Err(KclError::new_semantic(KclErrorDetails::new(
79 "The face you provided doesn't lie on any plane. It might be curved.".to_owned(),
80 vec![args.source_range],
81 )));
82 let Some(x_axis) = planar.x_axis else { return not_planar };
83 let Some(y_axis) = planar.y_axis else { return not_planar };
84 let Some(origin) = planar.origin else { return not_planar };
85
86 let engine_units = UnitLen::Mm;
88 let x_axis = crate::execution::Point3d {
89 x: x_axis.x,
90 y: x_axis.y,
91 z: x_axis.z,
92 units: engine_units,
93 };
94 let y_axis = crate::execution::Point3d {
95 x: y_axis.x,
96 y: y_axis.y,
97 z: y_axis.z,
98 units: engine_units,
99 };
100 let origin = crate::execution::Point3d {
101 x: origin.x.0,
102 y: origin.y.0,
103 z: origin.z.0,
104 units: engine_units,
105 };
106
107 let plane_id = exec_state.id_generator().next_uuid();
109 Ok(Plane {
110 artifact_id: plane_id.into(),
111 id: plane_id,
112 value: PlaneType::Uninit,
114 info: crate::execution::PlaneInfo { origin, x_axis, y_axis },
115 meta: vec![Metadata {
116 source_range: args.source_range,
117 }],
118 })
119}
120
121pub async fn offset_plane(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
123 let std_plane = args.get_unlabeled_kw_arg("plane", &RuntimeType::plane(), exec_state)?;
124 let offset: TyF64 = args.get_kw_arg("offset", &RuntimeType::length(), exec_state)?;
125 let plane = inner_offset_plane(std_plane, offset, exec_state, &args).await?;
126 Ok(KclValue::Plane { value: Box::new(plane) })
127}
128
129async fn inner_offset_plane(
130 plane: PlaneData,
131 offset: TyF64,
132 exec_state: &mut ExecState,
133 args: &Args,
134) -> Result<Plane, KclError> {
135 let mut plane = Plane::from_plane_data(plane, exec_state)?;
136 plane.value = PlaneType::Custom;
139
140 let normal = plane.info.x_axis.axes_cross_product(&plane.info.y_axis);
141 plane.info.origin += normal * offset.to_length_units(plane.info.origin.units);
142 make_offset_plane_in_engine(&plane, exec_state, args).await?;
143
144 Ok(plane)
145}
146
147async fn make_offset_plane_in_engine(plane: &Plane, exec_state: &mut ExecState, args: &Args) -> Result<(), KclError> {
151 let default_size = 100.0;
153 let color = Color {
154 r: 0.6,
155 g: 0.6,
156 b: 0.6,
157 a: 0.3,
158 };
159
160 let meta = ModelingCmdMeta::from_args_id(args, plane.id);
161 exec_state
162 .batch_modeling_cmd(
163 meta,
164 ModelingCmd::from(mcmd::MakePlane {
165 clobber: false,
166 origin: plane.info.origin.into(),
167 size: LengthUnit(default_size),
168 x_axis: plane.info.x_axis.into(),
169 y_axis: plane.info.y_axis.into(),
170 hide: Some(false),
171 }),
172 )
173 .await?;
174
175 exec_state
177 .batch_modeling_cmd(
178 args.into(),
179 ModelingCmd::from(mcmd::PlaneSetColor {
180 color,
181 plane_id: plane.id,
182 }),
183 )
184 .await?;
185
186 Ok(())
187}