2023-08-24 15:34:51 -07:00
|
|
|
//! Functions related to extruding.
|
|
|
|
|
2023-08-29 14:12:48 -07:00
|
|
|
use anyhow::Result;
|
|
|
|
use derive_docs::stdlib;
|
|
|
|
use schemars::JsonSchema;
|
2024-03-22 10:23:04 +11:00
|
|
|
use uuid::Uuid;
|
2023-08-29 14:12:48 -07:00
|
|
|
|
2023-08-24 15:34:51 -07:00
|
|
|
use crate::{
|
|
|
|
errors::{KclError, KclErrorDetails},
|
2024-02-16 16:42:01 -08:00
|
|
|
executor::{ExtrudeGroup, ExtrudeSurface, ExtrudeTransform, GeoMeta, MemoryItem, Path, SketchGroup, SketchSurface},
|
2023-08-24 15:34:51 -07:00
|
|
|
std::Args,
|
|
|
|
};
|
|
|
|
|
|
|
|
/// Extrudes by a given amount.
|
2023-09-20 18:27:08 -07:00
|
|
|
pub async fn extrude(args: Args) -> Result<MemoryItem, KclError> {
|
2023-08-24 15:34:51 -07:00
|
|
|
let (length, sketch_group) = args.get_number_sketch_group()?;
|
|
|
|
|
2023-09-20 18:27:08 -07:00
|
|
|
let result = inner_extrude(length, sketch_group, args).await?;
|
2023-08-25 13:41:04 -07:00
|
|
|
|
|
|
|
Ok(MemoryItem::ExtrudeGroup(result))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Extrudes by a given amount.
|
2024-03-13 12:56:46 -07:00
|
|
|
///
|
|
|
|
/// ```no_run
|
|
|
|
/// startSketchOn('XY')
|
|
|
|
/// |> startProfileAt([0, 0], %)
|
|
|
|
/// |> line([0, 10], %)
|
|
|
|
/// |> line([10, 0], %)
|
|
|
|
/// |> line([0, -10], %)
|
|
|
|
/// |> close(%)
|
|
|
|
/// |> extrude(5, %)
|
|
|
|
/// ```
|
2023-08-25 13:41:04 -07:00
|
|
|
#[stdlib {
|
|
|
|
name = "extrude"
|
|
|
|
}]
|
2023-09-20 18:27:08 -07:00
|
|
|
async fn inner_extrude(length: f64, sketch_group: Box<SketchGroup>, args: Args) -> Result<Box<ExtrudeGroup>, KclError> {
|
2023-08-24 15:34:51 -07:00
|
|
|
let id = uuid::Uuid::new_v4();
|
2023-10-11 18:30:04 -07:00
|
|
|
// Extrude the element.
|
|
|
|
args.send_modeling_cmd(
|
|
|
|
id,
|
|
|
|
kittycad::types::ModelingCmd::Extrude {
|
|
|
|
target: sketch_group.id,
|
|
|
|
distance: length,
|
|
|
|
cap: true,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
.await?;
|
|
|
|
|
2024-02-16 16:42:01 -08:00
|
|
|
// We need to do this after extrude for sketch on face.
|
|
|
|
if let SketchSurface::Face(_) = sketch_group.on {
|
|
|
|
// Disable the sketch mode.
|
|
|
|
args.send_modeling_cmd(uuid::Uuid::new_v4(), kittycad::types::ModelingCmd::SketchModeDisable {})
|
|
|
|
.await?;
|
|
|
|
}
|
|
|
|
|
2023-10-11 18:30:04 -07:00
|
|
|
// Bring the object to the front of the scene.
|
|
|
|
// See: https://github.com/KittyCAD/modeling-app/issues/806
|
|
|
|
args.send_modeling_cmd(
|
|
|
|
uuid::Uuid::new_v4(),
|
|
|
|
kittycad::types::ModelingCmd::ObjectBringToFront {
|
|
|
|
object_id: sketch_group.id,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
.await?;
|
2023-08-24 15:34:51 -07:00
|
|
|
|
2024-02-12 18:08:42 -08:00
|
|
|
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],
|
|
|
|
}));
|
|
|
|
};
|
|
|
|
|
2024-02-26 14:54:42 -08:00
|
|
|
let mut sketch_group = *sketch_group.clone();
|
|
|
|
|
|
|
|
// If we were sketching on a face, we need the original face id.
|
|
|
|
if let SketchSurface::Face(face) = sketch_group.on {
|
|
|
|
sketch_group.id = face.sketch_group_id;
|
|
|
|
}
|
|
|
|
|
2024-02-12 18:08:42 -08:00
|
|
|
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![]
|
|
|
|
};
|
|
|
|
|
|
|
|
// Create a hashmap for quick id lookup
|
|
|
|
let mut face_id_map = std::collections::HashMap::new();
|
2024-03-22 10:23:04 +11:00
|
|
|
// creating fake ids for start and end caps is to make extrudes mock-execute safe
|
|
|
|
let mut start_cap_id = Some(Uuid::new_v4());
|
|
|
|
let mut end_cap_id = Some(Uuid::new_v4());
|
2024-02-12 18:08:42 -08:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Iterate over the sketch_group.value array and add face_id to GeoMeta
|
|
|
|
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) {
|
2024-02-22 19:07:17 -08:00
|
|
|
match path {
|
|
|
|
Path::TangentialArc { .. } | Path::TangentialArcTo { .. } => {
|
|
|
|
let extrude_surface = ExtrudeSurface::ExtrudeArc(crate::executor::ExtrudeArc {
|
|
|
|
position: sketch_group.position, // TODO should be for the extrude surface
|
|
|
|
rotation: sketch_group.rotation, // TODO should be for the extrude surface
|
|
|
|
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 {
|
|
|
|
position: sketch_group.position, // TODO should be for the extrude surface
|
|
|
|
rotation: sketch_group.rotation, // TODO should be for the extrude surface
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2024-03-22 10:23:04 +11:00
|
|
|
} else {
|
|
|
|
new_value.push(ExtrudeSurface::ExtrudePlane(crate::executor::ExtrudePlane {
|
|
|
|
position: sketch_group.position, // TODO should be for the extrude surface
|
|
|
|
rotation: sketch_group.rotation, // TODO should be for the extrude surface
|
|
|
|
// pushing this values with a fake face_id to make extrudes mock-execute safe
|
|
|
|
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(),
|
|
|
|
},
|
|
|
|
}));
|
2024-02-12 18:08:42 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-19 14:20:14 -07:00
|
|
|
Ok(Box::new(ExtrudeGroup {
|
2024-02-13 10:26:09 -08:00
|
|
|
// Ok so you would think that the id would be the id of the extrude group,
|
|
|
|
// that we passed in to the function, but it's actually the id of the
|
|
|
|
// sketch group.
|
|
|
|
id: sketch_group.id,
|
2024-02-12 18:08:42 -08:00
|
|
|
value: new_value,
|
2024-03-05 11:52:45 -08:00
|
|
|
sketch_group_values: sketch_group.value.clone(),
|
2023-08-24 15:34:51 -07:00
|
|
|
height: length,
|
|
|
|
position: sketch_group.position,
|
|
|
|
rotation: sketch_group.rotation,
|
2024-02-12 18:08:42 -08:00
|
|
|
x_axis: sketch_group.x_axis,
|
|
|
|
y_axis: sketch_group.y_axis,
|
|
|
|
z_axis: sketch_group.z_axis,
|
|
|
|
start_cap_id,
|
|
|
|
end_cap_id,
|
2023-08-24 15:34:51 -07:00
|
|
|
meta: sketch_group.meta,
|
2023-09-19 14:20:14 -07:00
|
|
|
}))
|
2023-08-24 15:34:51 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the extrude wall transform.
|
2023-09-20 18:27:08 -07:00
|
|
|
pub async fn get_extrude_wall_transform(args: Args) -> Result<MemoryItem, KclError> {
|
2023-08-24 15:34:51 -07:00
|
|
|
let (surface_name, extrude_group) = args.get_path_name_extrude_group()?;
|
2023-09-19 14:20:14 -07:00
|
|
|
let result = inner_get_extrude_wall_transform(&surface_name, *extrude_group, args)?;
|
2023-08-25 13:41:04 -07:00
|
|
|
Ok(MemoryItem::ExtrudeTransform(result))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the extrude wall transform.
|
2024-03-13 12:56:46 -07:00
|
|
|
///
|
|
|
|
/// ```no_run
|
|
|
|
/// const box = startSketchOn('XY')
|
|
|
|
/// |> startProfileAt([0, 0], %)
|
|
|
|
/// |> line([0, 10], %)
|
|
|
|
/// |> line([10, 0], %)
|
2024-03-15 17:03:42 -04:00
|
|
|
/// |> line([0, -10], %, "surface")
|
2024-03-13 12:56:46 -07:00
|
|
|
/// |> close(%)
|
|
|
|
/// |> extrude(5, %)
|
|
|
|
///
|
|
|
|
/// const transform = getExtrudeWallTransform('surface', box)
|
|
|
|
/// ```
|
2023-08-25 13:41:04 -07:00
|
|
|
#[stdlib {
|
|
|
|
name = "getExtrudeWallTransform"
|
|
|
|
}]
|
|
|
|
fn inner_get_extrude_wall_transform(
|
|
|
|
surface_name: &str,
|
|
|
|
extrude_group: ExtrudeGroup,
|
2023-09-20 18:27:08 -07:00
|
|
|
args: Args,
|
|
|
|
) -> Result<Box<ExtrudeTransform>, KclError> {
|
2023-08-29 14:12:48 -07:00
|
|
|
let surface = extrude_group.get_path_by_name(surface_name).ok_or_else(|| {
|
|
|
|
KclError::Type(KclErrorDetails {
|
|
|
|
message: format!(
|
|
|
|
"Expected a surface name that exists in the given ExtrudeGroup, found `{}`",
|
|
|
|
surface_name
|
|
|
|
),
|
|
|
|
source_ranges: vec![args.source_range],
|
|
|
|
})
|
|
|
|
})?;
|
2023-08-24 15:34:51 -07:00
|
|
|
|
2023-09-20 18:27:08 -07:00
|
|
|
Ok(Box::new(ExtrudeTransform {
|
2023-08-24 15:34:51 -07:00
|
|
|
position: surface.get_position(),
|
|
|
|
rotation: surface.get_rotation(),
|
|
|
|
meta: extrude_group.meta,
|
2023-09-20 18:27:08 -07:00
|
|
|
}))
|
2023-08-24 15:34:51 -07:00
|
|
|
}
|