use kcmc::ModelingCmd;
use kcmc::each_cmd as mcmd;
use kcmc::length_unit::LengthUnit;
use kcmc::shared::Color;
use kittycad_modeling_cmds::ok_response::OkModelingCmdResponse;
use kittycad_modeling_cmds::units::UnitLength;
use kittycad_modeling_cmds::websocket::OkWebSocketResponseData;
use kittycad_modeling_cmds::{self as kcmc};
use super::args::TyF64;
use super::sketch::PlaneData;
use crate::errors::KclError;
use crate::errors::KclErrorDetails;
use crate::execution::ExecState;
use crate::execution::KclValue;
use crate::execution::Metadata;
use crate::execution::ModelingCmdMeta;
use crate::execution::Plane;
use crate::execution::PlaneInfo;
use crate::execution::PlaneKind;
use crate::execution::types::RuntimeType;
use crate::std::Args;
use crate::std::faces::FaceSpecifier;
pub async fn plane_of(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let solid = args.get_unlabeled_kw_arg("solid", &RuntimeType::solid(), exec_state)?;
let face = args.get_kw_arg("face", &RuntimeType::tagged_face_or_segment(), exec_state)?;
inner_plane_of(solid, face, exec_state, &args)
.await
.map(Box::new)
.map(|value| KclValue::Plane { value })
}
pub(crate) async fn inner_plane_of(
solid: crate::execution::Solid,
face: FaceSpecifier,
exec_state: &mut ExecState,
args: &Args,
) -> Result<Plane, KclError> {
let plane_id = exec_state.id_generator().next_uuid();
if args.ctx.no_engine_commands().await {
exec_state.err(crate::CompilationIssue {
source_range: args.source_range,
message: "The engine isn't available, so returning an arbitrary incorrect plane".to_owned(),
suggestion: None,
severity: crate::errors::Severity::Error,
tag: crate::errors::Tag::None,
});
return Ok(Plane {
artifact_id: plane_id.into(),
id: plane_id,
object_id: None,
kind: PlaneKind::Custom,
info: crate::execution::PlaneInfo {
origin: crate::execution::Point3d {
x: 0.0,
y: 0.0,
z: 0.0,
units: Some(UnitLength::Millimeters),
},
x_axis: crate::execution::Point3d {
x: 1.0,
y: 0.0,
z: 0.0,
units: None,
},
y_axis: crate::execution::Point3d {
x: 0.0,
y: 1.0,
z: 0.0,
units: None,
},
z_axis: crate::execution::Point3d {
x: 0.0,
y: 0.0,
z: 1.0,
units: None,
},
},
meta: vec![Metadata {
source_range: args.source_range,
}],
});
}
exec_state
.flush_batch_for_solids(
ModelingCmdMeta::from_args(exec_state, args),
std::slice::from_ref(&solid),
)
.await?;
let face_id = face.face_id(&solid, exec_state, args, true).await?;
let meta = ModelingCmdMeta::from_args_id(exec_state, args, plane_id);
let cmd = ModelingCmd::FaceIsPlanar(mcmd::FaceIsPlanar::builder().object_id(face_id).build());
let plane_resp = exec_state.send_modeling_cmd(meta, cmd).await?;
let OkWebSocketResponseData::Modeling {
modeling_response: OkModelingCmdResponse::FaceIsPlanar(planar),
} = plane_resp
else {
return Err(KclError::new_semantic(KclErrorDetails::new(
format!(
"Engine returned invalid response, it should have returned FaceIsPlanar but it returned {plane_resp:#?}"
),
vec![args.source_range],
)));
};
let not_planar: Result<_, KclError> = Err(KclError::new_semantic(KclErrorDetails::new(
"The face you provided doesn't lie on any plane. It might be curved.".to_owned(),
vec![args.source_range],
)));
let Some(x_axis) = planar.x_axis else { return not_planar };
let Some(y_axis) = planar.y_axis else { return not_planar };
let Some(z_axis) = planar.z_axis else { return not_planar };
let Some(origin) = planar.origin else { return not_planar };
let engine_units = Some(UnitLength::Millimeters);
let x_axis = crate::execution::Point3d {
x: x_axis.x,
y: x_axis.y,
z: x_axis.z,
units: engine_units,
};
let y_axis = crate::execution::Point3d {
x: y_axis.x,
y: y_axis.y,
z: y_axis.z,
units: engine_units,
};
let z_axis = crate::execution::Point3d {
x: z_axis.x,
y: z_axis.y,
z: z_axis.z,
units: engine_units,
};
let origin = crate::execution::Point3d {
x: origin.x.0,
y: origin.y.0,
z: origin.z.0,
units: engine_units,
};
let plane_info = crate::execution::PlaneInfo {
origin,
x_axis,
y_axis,
z_axis,
};
let plane_info = plane_info.make_right_handed();
let plane_object_id = exec_state.next_object_id();
#[cfg(feature = "artifact-graph")]
{
use crate::execution::ArtifactId;
use crate::front::SourceRef;
let plane_object = crate::front::Object {
id: plane_object_id,
kind: crate::front::ObjectKind::Plane(crate::front::Plane::Object(plane_object_id)),
label: Default::default(),
comments: Default::default(),
artifact_id: ArtifactId::new(plane_id),
source: SourceRef::new(args.source_range, args.node_path.clone()),
};
exec_state.add_scene_object(plane_object, args.source_range);
}
Ok(Plane {
artifact_id: plane_id.into(),
id: plane_id,
object_id: Some(plane_object_id),
kind: PlaneKind::Custom,
info: plane_info,
meta: vec![Metadata {
source_range: args.source_range,
}],
})
}
pub async fn offset_plane(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let std_plane = args.get_unlabeled_kw_arg("plane", &RuntimeType::plane(), exec_state)?;
let offset: TyF64 = args.get_kw_arg("offset", &RuntimeType::length(), exec_state)?;
let plane = inner_offset_plane(std_plane, offset, exec_state, &args).await?;
Ok(KclValue::Plane { value: Box::new(plane) })
}
async fn inner_offset_plane(
plane: PlaneData,
offset: TyF64,
exec_state: &mut ExecState,
args: &Args,
) -> Result<Plane, KclError> {
let mut info = PlaneInfo::try_from(plane)?;
let normal = info.x_axis.axes_cross_product(&info.y_axis);
info.origin += normal * offset.to_length_units(info.origin.units.unwrap_or(UnitLength::Millimeters));
let id = exec_state.next_uuid();
let mut plane = Plane {
id,
artifact_id: id.into(),
object_id: None,
kind: PlaneKind::Custom,
info,
meta: vec![Metadata {
source_range: args.source_range,
}],
};
make_offset_plane_in_engine(&mut plane, exec_state, args).await?;
Ok(plane)
}
async fn make_offset_plane_in_engine(
plane: &mut Plane,
exec_state: &mut ExecState,
args: &Args,
) -> Result<(), KclError> {
let plane_object_id = exec_state.next_object_id();
#[cfg(feature = "artifact-graph")]
{
use crate::front::SourceRef;
let plane_object = crate::front::Object {
id: plane_object_id,
kind: crate::front::ObjectKind::Plane(crate::front::Plane::Object(plane_object_id)),
label: Default::default(),
comments: Default::default(),
artifact_id: plane.artifact_id,
source: SourceRef::new(args.source_range, args.node_path.clone()),
};
exec_state.add_scene_object(plane_object, args.source_range);
}
let default_size = 100.0;
let color = Color::from_rgba(0.6, 0.6, 0.6, 0.3);
let meta = ModelingCmdMeta::from_args_id(exec_state, args, plane.id);
exec_state
.batch_modeling_cmd(
meta,
ModelingCmd::from(
mcmd::MakePlane::builder()
.clobber(false)
.origin(plane.info.origin.into())
.size(LengthUnit(default_size))
.x_axis(plane.info.x_axis.into())
.y_axis(plane.info.y_axis.into())
.hide(false)
.build(),
),
)
.await?;
exec_state
.batch_modeling_cmd(
ModelingCmdMeta::from_args(exec_state, args),
ModelingCmd::from(mcmd::PlaneSetColor::builder().color(color).plane_id(plane.id).build()),
)
.await?;
plane.kind = PlaneKind::Custom;
plane.object_id = Some(plane_object_id);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::execution::PlaneInfo;
use crate::execution::Point3d;
#[test]
fn fixes_left_handed_plane() {
let plane_info = PlaneInfo {
origin: Point3d {
x: 0.0,
y: 0.0,
z: 0.0,
units: Some(UnitLength::Millimeters),
},
x_axis: Point3d {
x: 1.0,
y: 0.0,
z: 0.0,
units: None,
},
y_axis: Point3d {
x: 0.0,
y: 1.0,
z: 0.0,
units: None,
},
z_axis: Point3d {
x: 0.0,
y: 0.0,
z: -1.0,
units: None,
},
};
assert!(plane_info.is_left_handed());
let fixed = plane_info.make_right_handed();
assert!(fixed.is_right_handed());
}
}