2024-06-17 13:10:40 -07:00
|
|
|
//! Standard library shells.
|
|
|
|
|
|
|
|
use anyhow::Result;
|
|
|
|
use derive_docs::stdlib;
|
2024-09-19 14:06:29 -07:00
|
|
|
use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, ModelingCmd};
|
2024-09-18 17:04:04 -05:00
|
|
|
use kittycad_modeling_cmds as kcmc;
|
2024-06-17 13:10:40 -07:00
|
|
|
use schemars::JsonSchema;
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
|
|
|
use crate::{
|
|
|
|
errors::{KclError, KclErrorDetails},
|
2024-12-07 07:16:04 +13:00
|
|
|
execution::{ExecState, KclValue, Solid, SolidSet},
|
2024-06-23 23:04:32 -07:00
|
|
|
std::{sketch::FaceTag, Args},
|
2024-06-17 13:10:40 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
/// Data for shells.
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
|
|
|
#[ts(export)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct ShellData {
|
|
|
|
/// The thickness of the shell.
|
|
|
|
pub thickness: f64,
|
|
|
|
/// The faces you want removed.
|
|
|
|
pub faces: Vec<FaceTag>,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Create a shell.
|
2024-09-16 15:10:33 -04:00
|
|
|
pub async fn shell(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
2024-09-27 15:44:44 -07:00
|
|
|
let (data, solid_set): (ShellData, SolidSet) = args.get_data_and_solid_set()?;
|
2024-06-17 13:10:40 -07:00
|
|
|
|
2024-09-27 15:44:44 -07:00
|
|
|
let result = inner_shell(data, solid_set, exec_state, args).await?;
|
2024-09-17 18:49:08 -07:00
|
|
|
Ok(result.into())
|
2024-06-17 13:10:40 -07:00
|
|
|
}
|
|
|
|
|
2024-08-06 20:27:26 -04:00
|
|
|
/// Remove volume from a 3-dimensional shape such that a wall of the
|
|
|
|
/// provided thickness remains, taking volume starting at the provided
|
|
|
|
/// face, leaving it open in that direction.
|
2024-06-17 13:10:40 -07:00
|
|
|
///
|
|
|
|
/// ```no_run
|
2024-09-10 19:50:34 -07:00
|
|
|
/// // Remove the end face for the extrusion.
|
2024-12-12 11:33:37 -05:00
|
|
|
/// firstSketch = startSketchOn('XY')
|
2024-06-17 13:10:40 -07:00
|
|
|
/// |> startProfileAt([-12, 12], %)
|
|
|
|
/// |> line([24, 0], %)
|
|
|
|
/// |> line([0, -24], %)
|
|
|
|
/// |> line([-24, 0], %)
|
|
|
|
/// |> close(%)
|
|
|
|
/// |> extrude(6, %)
|
|
|
|
///
|
|
|
|
/// // Remove the end face for the extrusion.
|
|
|
|
/// shell({
|
2024-11-25 09:21:55 +13:00
|
|
|
/// faces = ['end'],
|
|
|
|
/// thickness = 0.25,
|
2024-06-17 13:10:40 -07:00
|
|
|
/// }, firstSketch)
|
|
|
|
/// ```
|
2024-08-13 14:14:23 -07:00
|
|
|
///
|
|
|
|
/// ```no_run
|
2024-09-10 19:50:34 -07:00
|
|
|
/// // Remove the start face for the extrusion.
|
2024-12-12 11:33:37 -05:00
|
|
|
/// firstSketch = startSketchOn('-XZ')
|
2024-08-13 14:14:23 -07:00
|
|
|
/// |> startProfileAt([-12, 12], %)
|
|
|
|
/// |> line([24, 0], %)
|
|
|
|
/// |> line([0, -24], %)
|
|
|
|
/// |> line([-24, 0], %)
|
|
|
|
/// |> close(%)
|
|
|
|
/// |> extrude(6, %)
|
|
|
|
///
|
|
|
|
/// // Remove the start face for the extrusion.
|
|
|
|
/// shell({
|
2024-11-25 09:21:55 +13:00
|
|
|
/// faces = ['start'],
|
|
|
|
/// thickness = 0.25,
|
2024-08-13 14:14:23 -07:00
|
|
|
/// }, firstSketch)
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// ```no_run
|
2024-09-10 19:50:34 -07:00
|
|
|
/// // Remove a tagged face and the end face for the extrusion.
|
2024-12-12 11:33:37 -05:00
|
|
|
/// firstSketch = startSketchOn('XY')
|
2024-08-13 14:14:23 -07:00
|
|
|
/// |> startProfileAt([-12, 12], %)
|
|
|
|
/// |> line([24, 0], %)
|
|
|
|
/// |> line([0, -24], %)
|
|
|
|
/// |> line([-24, 0], %, $myTag)
|
|
|
|
/// |> close(%)
|
|
|
|
/// |> extrude(6, %)
|
|
|
|
///
|
|
|
|
/// // Remove a tagged face for the extrusion.
|
|
|
|
/// shell({
|
2024-11-25 09:21:55 +13:00
|
|
|
/// faces = [myTag],
|
|
|
|
/// thickness = 0.25,
|
2024-08-13 14:14:23 -07:00
|
|
|
/// }, firstSketch)
|
|
|
|
/// ```
|
2024-09-10 19:50:34 -07:00
|
|
|
///
|
|
|
|
/// ```no_run
|
|
|
|
/// // Remove multiple faces at once.
|
2024-12-12 11:33:37 -05:00
|
|
|
/// firstSketch = startSketchOn('XY')
|
2024-09-10 19:50:34 -07:00
|
|
|
/// |> startProfileAt([-12, 12], %)
|
|
|
|
/// |> line([24, 0], %)
|
|
|
|
/// |> line([0, -24], %)
|
|
|
|
/// |> line([-24, 0], %, $myTag)
|
|
|
|
/// |> close(%)
|
|
|
|
/// |> extrude(6, %)
|
|
|
|
///
|
|
|
|
/// // Remove a tagged face and the end face for the extrusion.
|
|
|
|
/// shell({
|
2024-11-25 09:21:55 +13:00
|
|
|
/// faces = [myTag, 'end'],
|
|
|
|
/// thickness = 0.25,
|
2024-09-10 19:50:34 -07:00
|
|
|
/// }, firstSketch)
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// ```no_run
|
|
|
|
/// // Shell a sketch on face.
|
2024-12-12 11:33:37 -05:00
|
|
|
/// size = 100
|
|
|
|
/// case = startSketchOn('-XZ')
|
2024-09-10 19:50:34 -07:00
|
|
|
/// |> startProfileAt([-size, -size], %)
|
|
|
|
/// |> line([2 * size, 0], %)
|
|
|
|
/// |> line([0, 2 * size], %)
|
|
|
|
/// |> tangentialArcTo([-size, size], %)
|
|
|
|
/// |> close(%)
|
|
|
|
/// |> extrude(65, %)
|
|
|
|
///
|
2024-12-12 11:33:37 -05:00
|
|
|
/// thing1 = startSketchOn(case, 'end')
|
2024-11-25 09:21:55 +13:00
|
|
|
/// |> circle({ center = [-size / 2, -size / 2], radius = 25 }, %)
|
2024-09-10 19:50:34 -07:00
|
|
|
/// |> extrude(50, %)
|
|
|
|
///
|
2024-12-12 11:33:37 -05:00
|
|
|
/// thing2 = startSketchOn(case, 'end')
|
2024-11-25 09:21:55 +13:00
|
|
|
/// |> circle({ center = [size / 2, -size / 2], radius = 25 }, %)
|
2024-09-10 19:50:34 -07:00
|
|
|
/// |> extrude(50, %)
|
|
|
|
///
|
|
|
|
/// // We put "case" in the shell function to shell the entire object.
|
2024-11-25 09:21:55 +13:00
|
|
|
/// shell({ faces = ['start'], thickness = 5 }, case)
|
2024-09-10 19:50:34 -07:00
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// ```no_run
|
|
|
|
/// // Shell a sketch on face object on the end face.
|
2024-12-12 11:33:37 -05:00
|
|
|
/// size = 100
|
|
|
|
/// case = startSketchOn('XY')
|
2024-09-10 19:50:34 -07:00
|
|
|
/// |> startProfileAt([-size, -size], %)
|
|
|
|
/// |> line([2 * size, 0], %)
|
|
|
|
/// |> line([0, 2 * size], %)
|
|
|
|
/// |> tangentialArcTo([-size, size], %)
|
|
|
|
/// |> close(%)
|
|
|
|
/// |> extrude(65, %)
|
|
|
|
///
|
2024-12-12 11:33:37 -05:00
|
|
|
/// thing1 = startSketchOn(case, 'end')
|
2024-11-25 09:21:55 +13:00
|
|
|
/// |> circle({ center = [-size / 2, -size / 2], radius = 25 }, %)
|
2024-09-10 19:50:34 -07:00
|
|
|
/// |> extrude(50, %)
|
|
|
|
///
|
2024-12-12 11:33:37 -05:00
|
|
|
/// thing2 = startSketchOn(case, 'end')
|
2024-11-25 09:21:55 +13:00
|
|
|
/// |> circle({ center = [size / 2, -size / 2], radius = 25 }, %)
|
2024-09-10 19:50:34 -07:00
|
|
|
/// |> extrude(50, %)
|
|
|
|
///
|
|
|
|
/// // We put "thing1" in the shell function to shell the end face of the object.
|
2024-11-25 09:21:55 +13:00
|
|
|
/// shell({ faces = ['end'], thickness = 5 }, thing1)
|
2024-09-10 19:50:34 -07:00
|
|
|
/// ```
|
2024-09-17 18:49:08 -07:00
|
|
|
///
|
|
|
|
/// ```no_run
|
|
|
|
/// // Shell sketched on face objects on the end face, include all sketches to shell
|
|
|
|
/// // the entire object.
|
|
|
|
///
|
2024-12-12 11:33:37 -05:00
|
|
|
/// size = 100
|
|
|
|
/// case = startSketchOn('XY')
|
2024-09-17 18:49:08 -07:00
|
|
|
/// |> startProfileAt([-size, -size], %)
|
|
|
|
/// |> line([2 * size, 0], %)
|
|
|
|
/// |> line([0, 2 * size], %)
|
|
|
|
/// |> tangentialArcTo([-size, size], %)
|
|
|
|
/// |> close(%)
|
|
|
|
/// |> extrude(65, %)
|
|
|
|
///
|
2024-12-12 11:33:37 -05:00
|
|
|
/// thing1 = startSketchOn(case, 'end')
|
2024-11-25 09:21:55 +13:00
|
|
|
/// |> circle({ center = [-size / 2, -size / 2], radius = 25 }, %)
|
2024-09-17 18:49:08 -07:00
|
|
|
/// |> extrude(50, %)
|
|
|
|
///
|
2024-12-12 11:33:37 -05:00
|
|
|
/// thing2 = startSketchOn(case, 'end')
|
2024-11-25 09:21:55 +13:00
|
|
|
/// |> circle({ center = [size / 2, -size / 2], radius = 25 }, %)
|
2024-09-17 18:49:08 -07:00
|
|
|
/// |> extrude(50, %)
|
|
|
|
///
|
|
|
|
/// // We put "thing1" and "thing2" in the shell function to shell the end face of the object.
|
2024-11-25 09:21:55 +13:00
|
|
|
/// shell({ faces = ['end'], thickness = 5 }, [thing1, thing2])
|
2024-09-17 18:49:08 -07:00
|
|
|
/// ```
|
2024-06-17 13:10:40 -07:00
|
|
|
#[stdlib {
|
|
|
|
name = "shell",
|
2024-12-16 13:10:31 -05:00
|
|
|
feature_tree_operation = true,
|
2024-06-17 13:10:40 -07:00
|
|
|
}]
|
|
|
|
async fn inner_shell(
|
|
|
|
data: ShellData,
|
2024-09-27 15:44:44 -07:00
|
|
|
solid_set: SolidSet,
|
2024-09-16 15:10:33 -04:00
|
|
|
exec_state: &mut ExecState,
|
2024-06-17 13:10:40 -07:00
|
|
|
args: Args,
|
2024-09-27 15:44:44 -07:00
|
|
|
) -> Result<SolidSet, KclError> {
|
2024-06-17 13:10:40 -07:00
|
|
|
if data.faces.is_empty() {
|
|
|
|
return Err(KclError::Type(KclErrorDetails {
|
|
|
|
message: "Expected at least one face".to_string(),
|
|
|
|
source_ranges: vec![args.source_range],
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
2024-09-27 15:44:44 -07:00
|
|
|
let solids: Vec<Box<Solid>> = solid_set.clone().into();
|
|
|
|
if solids.is_empty() {
|
2024-09-17 18:49:08 -07:00
|
|
|
return Err(KclError::Type(KclErrorDetails {
|
2024-09-27 15:44:44 -07:00
|
|
|
message: "Expected at least one solid".to_string(),
|
2024-09-17 18:49:08 -07:00
|
|
|
source_ranges: vec![args.source_range],
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
2024-06-17 13:10:40 -07:00
|
|
|
let mut face_ids = Vec::new();
|
2024-09-27 15:44:44 -07:00
|
|
|
for solid in &solids {
|
2024-09-17 18:49:08 -07:00
|
|
|
// Flush the batch for our fillets/chamfers if there are any.
|
|
|
|
// If we do not do these for sketch on face, things will fail with face does not exist.
|
2024-09-27 15:44:44 -07:00
|
|
|
args.flush_batch_for_solid_set(exec_state, solid.clone().into()).await?;
|
2024-09-17 18:49:08 -07:00
|
|
|
|
|
|
|
for tag in &data.faces {
|
2024-09-27 15:44:44 -07:00
|
|
|
let extrude_plane_id = tag.get_face_id(solid, exec_state, &args, false).await?;
|
2024-06-17 13:10:40 -07:00
|
|
|
|
2024-09-17 18:49:08 -07:00
|
|
|
face_ids.push(extrude_plane_id);
|
|
|
|
}
|
2024-06-17 13:10:40 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if face_ids.is_empty() {
|
|
|
|
return Err(KclError::Type(KclErrorDetails {
|
|
|
|
message: "Expected at least one valid face".to_string(),
|
|
|
|
source_ranges: vec![args.source_range],
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
2024-09-27 15:44:44 -07:00
|
|
|
// Make sure all the solids have the same id, as we are going to shell them all at
|
2024-09-17 18:49:08 -07:00
|
|
|
// once.
|
2024-09-27 15:44:44 -07:00
|
|
|
if !solids.iter().all(|eg| eg.id == solids[0].id) {
|
2024-09-17 18:49:08 -07:00
|
|
|
return Err(KclError::Type(KclErrorDetails {
|
2024-09-27 15:44:44 -07:00
|
|
|
message: "All solids stem from the same root object, like multiple sketch on face extrusions, etc."
|
2024-09-17 18:49:08 -07:00
|
|
|
.to_string(),
|
|
|
|
source_ranges: vec![args.source_range],
|
|
|
|
}));
|
|
|
|
}
|
2024-08-04 15:37:40 -07:00
|
|
|
|
2024-06-19 13:57:50 -07:00
|
|
|
args.batch_modeling_cmd(
|
2024-10-09 19:38:40 -04:00
|
|
|
exec_state.id_generator.next_uuid(),
|
2024-09-18 17:04:04 -05:00
|
|
|
ModelingCmd::from(mcmd::Solid3dShellFace {
|
2024-08-23 07:50:30 -05:00
|
|
|
hollow: false,
|
2024-06-17 13:10:40 -07:00
|
|
|
face_ids,
|
2024-09-27 15:44:44 -07:00
|
|
|
object_id: solids[0].id,
|
2024-09-18 17:04:04 -05:00
|
|
|
shell_thickness: LengthUnit(data.thickness),
|
|
|
|
}),
|
2024-06-17 13:10:40 -07:00
|
|
|
)
|
|
|
|
.await?;
|
|
|
|
|
2024-09-27 15:44:44 -07:00
|
|
|
Ok(solid_set)
|
2024-06-17 13:10:40 -07:00
|
|
|
}
|
2024-08-23 09:57:02 -07:00
|
|
|
|
|
|
|
/// Make the inside of a 3D object hollow.
|
2024-09-16 15:10:33 -04:00
|
|
|
pub async fn hollow(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
2024-09-27 15:44:44 -07:00
|
|
|
let (thickness, solid): (f64, Box<Solid>) = args.get_data_and_solid()?;
|
2024-08-23 09:57:02 -07:00
|
|
|
|
2024-09-27 15:44:44 -07:00
|
|
|
let solid = inner_hollow(thickness, solid, exec_state, args).await?;
|
|
|
|
Ok(KclValue::Solid(solid))
|
2024-08-23 09:57:02 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Make the inside of a 3D object hollow.
|
|
|
|
///
|
|
|
|
/// Remove volume from a 3-dimensional shape such that a wall of the
|
|
|
|
/// provided thickness remains around the exterior of the shape.
|
|
|
|
///
|
|
|
|
/// ```no_run
|
2024-09-17 18:49:08 -07:00
|
|
|
/// // Hollow a basic sketch.
|
2024-12-12 11:33:37 -05:00
|
|
|
/// firstSketch = startSketchOn('XY')
|
2024-08-23 09:57:02 -07:00
|
|
|
/// |> startProfileAt([-12, 12], %)
|
|
|
|
/// |> line([24, 0], %)
|
|
|
|
/// |> line([0, -24], %)
|
|
|
|
/// |> line([-24, 0], %)
|
|
|
|
/// |> close(%)
|
|
|
|
/// |> extrude(6, %)
|
|
|
|
/// |> hollow (0.25, %)
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// ```no_run
|
2024-09-17 18:49:08 -07:00
|
|
|
/// // Hollow a basic sketch.
|
2024-12-12 11:33:37 -05:00
|
|
|
/// firstSketch = startSketchOn('-XZ')
|
2024-08-23 09:57:02 -07:00
|
|
|
/// |> startProfileAt([-12, 12], %)
|
|
|
|
/// |> line([24, 0], %)
|
|
|
|
/// |> line([0, -24], %)
|
|
|
|
/// |> line([-24, 0], %)
|
|
|
|
/// |> close(%)
|
|
|
|
/// |> extrude(6, %)
|
|
|
|
/// |> hollow (0.5, %)
|
|
|
|
/// ```
|
2024-09-17 18:49:08 -07:00
|
|
|
///
|
|
|
|
/// ```no_run
|
|
|
|
/// // Hollow a sketch on face object.
|
2024-12-12 11:33:37 -05:00
|
|
|
/// size = 100
|
|
|
|
/// case = startSketchOn('-XZ')
|
2024-09-17 18:49:08 -07:00
|
|
|
/// |> startProfileAt([-size, -size], %)
|
|
|
|
/// |> line([2 * size, 0], %)
|
|
|
|
/// |> line([0, 2 * size], %)
|
|
|
|
/// |> tangentialArcTo([-size, size], %)
|
|
|
|
/// |> close(%)
|
|
|
|
/// |> extrude(65, %)
|
|
|
|
///
|
2024-12-12 11:33:37 -05:00
|
|
|
/// thing1 = startSketchOn(case, 'end')
|
2024-11-25 09:21:55 +13:00
|
|
|
/// |> circle({ center = [-size / 2, -size / 2], radius = 25 }, %)
|
2024-09-17 18:49:08 -07:00
|
|
|
/// |> extrude(50, %)
|
|
|
|
///
|
2024-12-12 11:33:37 -05:00
|
|
|
/// thing2 = startSketchOn(case, 'end')
|
2024-11-25 09:21:55 +13:00
|
|
|
/// |> circle({ center = [size / 2, -size / 2], radius = 25 }, %)
|
2024-09-17 18:49:08 -07:00
|
|
|
/// |> extrude(50, %)
|
|
|
|
///
|
|
|
|
/// hollow(0.5, case)
|
|
|
|
/// ```
|
2024-08-23 09:57:02 -07:00
|
|
|
#[stdlib {
|
|
|
|
name = "hollow",
|
2024-12-16 13:10:31 -05:00
|
|
|
feature_tree_operation = true,
|
2024-08-23 09:57:02 -07:00
|
|
|
}]
|
|
|
|
async fn inner_hollow(
|
|
|
|
thickness: f64,
|
2024-09-27 15:44:44 -07:00
|
|
|
solid: Box<Solid>,
|
2024-09-16 15:10:33 -04:00
|
|
|
exec_state: &mut ExecState,
|
2024-08-23 09:57:02 -07:00
|
|
|
args: Args,
|
2024-09-27 15:44:44 -07:00
|
|
|
) -> Result<Box<Solid>, KclError> {
|
2024-08-23 09:57:02 -07:00
|
|
|
// Flush the batch for our fillets/chamfers if there are any.
|
|
|
|
// If we do not do these for sketch on face, things will fail with face does not exist.
|
2024-09-27 15:44:44 -07:00
|
|
|
args.flush_batch_for_solid_set(exec_state, solid.clone().into()).await?;
|
2024-08-23 09:57:02 -07:00
|
|
|
|
|
|
|
args.batch_modeling_cmd(
|
2024-10-09 19:38:40 -04:00
|
|
|
exec_state.id_generator.next_uuid(),
|
2024-09-18 17:04:04 -05:00
|
|
|
ModelingCmd::from(mcmd::Solid3dShellFace {
|
2024-08-23 09:57:02 -07:00
|
|
|
hollow: true,
|
|
|
|
face_ids: Vec::new(), // This is empty because we want to hollow the entire object.
|
2024-09-27 15:44:44 -07:00
|
|
|
object_id: solid.id,
|
2024-09-18 17:04:04 -05:00
|
|
|
shell_thickness: LengthUnit(thickness),
|
|
|
|
}),
|
2024-08-23 09:57:02 -07:00
|
|
|
)
|
|
|
|
.await?;
|
|
|
|
|
2024-09-27 15:44:44 -07:00
|
|
|
Ok(solid)
|
2024-08-23 09:57:02 -07:00
|
|
|
}
|