use anyhow::Result;
use derive_docs::stdlib;
use schemars::JsonSchema;
use uuid::Uuid;
use crate::{
errors::{KclError, KclErrorDetails},
executor::{
ExtrudeGroup, ExtrudeGroupSet, ExtrudeSurface, GeoMeta, MemoryItem, Path, SketchGroup, SketchGroupSet,
SketchSurface,
},
std::Args,
};
pub async fn extrude(args: Args) -> Result<MemoryItem, KclError> {
let (length, sketch_group_set) = args.get_number_sketch_group_set()?;
let result = inner_extrude(length, sketch_group_set, args).await?;
match result {
ExtrudeGroupSet::ExtrudeGroup(extrude_group) => Ok(MemoryItem::ExtrudeGroup(extrude_group)),
ExtrudeGroupSet::ExtrudeGroups(extrude_groups) => Ok(MemoryItem::ExtrudeGroups { value: extrude_groups }),
}
}
#[stdlib {
name = "extrude"
}]
async fn inner_extrude(length: f64, sketch_group_set: SketchGroupSet, args: Args) -> Result<ExtrudeGroupSet, KclError> {
let id = uuid::Uuid::new_v4();
let sketch_groups = match sketch_group_set {
SketchGroupSet::SketchGroup(sketch_group) => vec![sketch_group],
SketchGroupSet::SketchGroups(sketch_groups) => sketch_groups,
};
let mut extrude_groups = Vec::new();
for sketch_group in sketch_groups.iter() {
args.send_modeling_cmd(
id,
kittycad::types::ModelingCmd::Extrude {
target: sketch_group.id,
distance: length,
cap: true,
},
)
.await?;
extrude_groups.push(do_post_extrude(sketch_group.clone(), length, id, args.clone()).await?);
}
if extrude_groups.len() == 1 {
Ok(ExtrudeGroupSet::ExtrudeGroup(extrude_groups.pop().unwrap()))
} else {
Ok(ExtrudeGroupSet::ExtrudeGroups(extrude_groups))
}
}
pub(crate) async fn do_post_extrude(
sketch_group: Box<SketchGroup>,
length: f64,
id: Uuid,
args: Args,
) -> Result<Box<ExtrudeGroup>, KclError> {
if let SketchSurface::Face(_) = sketch_group.on {
args.batch_modeling_cmd(uuid::Uuid::new_v4(), kittycad::types::ModelingCmd::SketchModeDisable {})
.await?;
}
args.batch_modeling_cmd(
uuid::Uuid::new_v4(),
kittycad::types::ModelingCmd::ObjectBringToFront {
object_id: sketch_group.id,
},
)
.await?;
if sketch_group.value.is_empty() {
return Err(KclError::Type(KclErrorDetails {
message: "Expected a non-empty sketch group".to_string(),
source_ranges: vec![args.source_range],
}));
}
let mut edge_id = None;
for segment in sketch_group.value.iter() {
if let Path::ToPoint { base } = segment {
edge_id = Some(base.geo_meta.id);
break;
}
}
let Some(edge_id) = edge_id else {
return Err(KclError::Type(KclErrorDetails {
message: "Expected a Path::ToPoint variant".to_string(),
source_ranges: vec![args.source_range],
}));
};
let mut sketch_group = *sketch_group.clone();
if let SketchSurface::Face(ref face) = sketch_group.on {
sketch_group.id = face.sketch_group_id;
}
let solid3d_info = args
.send_modeling_cmd(
id,
kittycad::types::ModelingCmd::Solid3DGetExtrusionFaceInfo {
edge_id,
object_id: sketch_group.id,
},
)
.await?;
let face_infos = if let kittycad::types::OkWebSocketResponseData::Modeling {
modeling_response: kittycad::types::OkModelingCmdResponse::Solid3DGetExtrusionFaceInfo { data },
} = solid3d_info
{
data.faces
} else {
vec![]
};
let mut face_id_map = std::collections::HashMap::new();
let mut start_cap_id = if args.ctx.is_mock { Some(Uuid::new_v4()) } else { None };
let mut end_cap_id = if args.ctx.is_mock { Some(Uuid::new_v4()) } else { None };
for face_info in face_infos {
match face_info.cap {
kittycad::types::ExtrusionFaceCapType::Bottom => start_cap_id = face_info.face_id,
kittycad::types::ExtrusionFaceCapType::Top => end_cap_id = face_info.face_id,
_ => {
if let Some(curve_id) = face_info.curve_id {
face_id_map.insert(curve_id, face_info.face_id);
}
}
}
}
let mut new_value: Vec<ExtrudeSurface> = Vec::new();
for path in sketch_group.value.iter() {
if let Some(Some(actual_face_id)) = face_id_map.get(&path.get_base().geo_meta.id) {
match path {
Path::TangentialArc { .. } | Path::TangentialArcTo { .. } => {
let extrude_surface = ExtrudeSurface::ExtrudeArc(crate::executor::ExtrudeArc {
face_id: *actual_face_id,
name: path.get_base().name.clone(),
geo_meta: GeoMeta {
id: path.get_base().geo_meta.id,
metadata: path.get_base().geo_meta.metadata.clone(),
},
});
new_value.push(extrude_surface);
}
Path::Base { .. } | Path::ToPoint { .. } | Path::Horizontal { .. } | Path::AngledLineTo { .. } => {
let extrude_surface = ExtrudeSurface::ExtrudePlane(crate::executor::ExtrudePlane {
face_id: *actual_face_id,
name: path.get_base().name.clone(),
geo_meta: GeoMeta {
id: path.get_base().geo_meta.id,
metadata: path.get_base().geo_meta.metadata.clone(),
},
});
new_value.push(extrude_surface);
}
}
} else if args.ctx.is_mock {
new_value.push(ExtrudeSurface::ExtrudePlane(crate::executor::ExtrudePlane {
face_id: Uuid::new_v4(),
name: path.get_base().name.clone(),
geo_meta: GeoMeta {
id: path.get_base().geo_meta.id,
metadata: path.get_base().geo_meta.metadata.clone(),
},
}));
}
}
Ok(Box::new(ExtrudeGroup {
id: sketch_group.id,
value: new_value,
sketch_group_values: sketch_group.value.clone(),
height: length,
x_axis: sketch_group.on.x_axis(),
y_axis: sketch_group.on.y_axis(),
z_axis: sketch_group.on.z_axis(),
start_cap_id,
end_cap_id,
meta: sketch_group.meta,
}))
}