2024-06-17 12:13:19 -07:00
//! Standard library chamfers.
use anyhow ::Result ;
use derive_docs ::stdlib ;
2024-09-19 14:06:29 -07:00
use kcmc ::{ each_cmd as mcmd , length_unit ::LengthUnit , shared ::CutType , ModelingCmd } ;
2024-09-18 17:04:04 -05:00
use kittycad_modeling_cmds as kcmc ;
2024-06-17 12:13:19 -07:00
use schemars ::JsonSchema ;
use serde ::{ Deserialize , Serialize } ;
use crate ::{
errors ::{ KclError , KclErrorDetails } ,
2024-12-07 07:16:04 +13:00
execution ::{ ChamferSurface , EdgeCut , ExecState , ExtrudeSurface , GeoMeta , KclValue , Solid } ,
2024-12-05 17:56:49 +13:00
parsing ::ast ::types ::TagNode ,
2024-06-24 14:45:07 -07:00
std ::{ fillet ::EdgeReference , Args } ,
2024-06-17 12:13:19 -07:00
} ;
pub ( crate ) const DEFAULT_TOLERANCE : f64 = 0.0000001 ;
/// Data for chamfers.
#[ derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema) ]
#[ ts(export) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct ChamferData {
2024-06-18 18:33:57 -07:00
/// The length of the chamfer.
pub length : f64 ,
2024-06-17 12:13:19 -07:00
/// The tags of the paths you want to chamfer.
pub tags : Vec < EdgeReference > ,
}
/// Create chamfers on tagged paths.
2024-09-16 15:10:33 -04:00
pub async fn chamfer ( exec_state : & mut ExecState , args : Args ) -> Result < KclValue , KclError > {
2024-10-30 16:52:17 -04:00
let ( data , solid , tag ) : ( ChamferData , Box < Solid > , Option < TagNode > ) = args . get_data_and_solid_and_tag ( ) ? ;
2024-06-17 12:13:19 -07:00
2025-01-22 09:42:09 +13:00
let value = inner_chamfer ( data , solid , tag , exec_state , args ) . await ? ;
Ok ( KclValue ::Solid { value } )
2024-06-17 12:13:19 -07:00
}
2024-08-06 20:27:26 -04:00
/// Cut a straight transitional edge along a tagged path.
///
/// Chamfer is similar in function and use to a fillet, except
/// a fillet will blend the transition along an edge, rather than cut
/// a sharp, straight transitional edge.
2024-06-17 12:13:19 -07:00
///
/// ```no_run
2024-09-12 16:13:11 -07:00
/// // Chamfer a mounting plate.
2024-12-12 11:33:37 -05:00
/// width = 20
/// length = 10
/// thickness = 1
/// chamferLength = 2
2024-06-17 12:13:19 -07:00
///
2024-12-12 11:33:37 -05:00
/// mountingPlateSketch = startSketchOn("XY")
2024-06-17 12:13:19 -07:00
/// |> startProfileAt([-width/2, -length/2], %)
2024-07-27 17:59:41 -07:00
/// |> lineTo([width/2, -length/2], %, $edge1)
/// |> lineTo([width/2, length/2], %, $edge2)
/// |> lineTo([-width/2, length/2], %, $edge3)
/// |> close(%, $edge4)
2024-06-17 12:13:19 -07:00
///
2024-12-12 11:33:37 -05:00
/// mountingPlate = extrude(thickness, mountingPlateSketch)
2024-06-17 12:13:19 -07:00
/// |> chamfer({
2024-11-25 09:21:55 +13:00
/// length = chamferLength,
/// tags = [
2024-07-27 22:56:46 -07:00
/// getNextAdjacentEdge(edge1),
/// getNextAdjacentEdge(edge2),
/// getNextAdjacentEdge(edge3),
/// getNextAdjacentEdge(edge4)
2024-06-17 12:13:19 -07:00
/// ],
/// }, %)
/// ```
2024-09-12 16:13:11 -07:00
///
/// ```no_run
/// // Sketch on the face of a chamfer.
2024-12-12 11:33:37 -05:00
/// fn cube(pos, scale) {
/// sg = startSketchOn('XY')
2024-09-12 16:13:11 -07:00
/// |> startProfileAt(pos, %)
/// |> line([0, scale], %)
/// |> line([scale, 0], %)
/// |> line([0, -scale], %)
///
/// return sg
/// }
///
2024-12-12 11:33:37 -05:00
/// part001 = cube([0,0], 20)
2024-09-12 16:13:11 -07:00
/// |> close(%, $line1)
/// |> extrude(20, %)
/// |> chamfer({
2024-11-25 09:21:55 +13:00
/// length = 10,
/// tags = [getOppositeEdge(line1)]
2024-09-12 16:13:11 -07:00
/// }, %, $chamfer1) // We tag the chamfer to reference it later.
///
2024-12-12 11:33:37 -05:00
/// sketch001 = startSketchOn(part001, chamfer1)
2024-09-12 16:13:11 -07:00
/// |> startProfileAt([10, 10], %)
/// |> line([2, 0], %)
/// |> line([0, 2], %)
/// |> line([-2, 0], %)
/// |> lineTo([profileStartX(%), profileStartY(%)], %)
/// |> close(%)
/// |> extrude(10, %)
/// ```
2024-06-17 12:13:19 -07:00
#[ stdlib {
name = " chamfer " ,
2024-12-16 13:10:31 -05:00
feature_tree_operation = true ,
2024-06-17 12:13:19 -07:00
} ]
async fn inner_chamfer (
data : ChamferData ,
2024-09-27 15:44:44 -07:00
solid : Box < Solid > ,
2024-10-30 16:52:17 -04:00
tag : Option < TagNode > ,
2024-09-16 15:10:33 -04:00
exec_state : & mut ExecState ,
2024-06-17 12:13:19 -07:00
args : Args ,
2024-09-27 15:44:44 -07:00
) -> Result < Box < Solid > , KclError > {
2024-06-17 12:13:19 -07:00
// Check if tags contains any duplicate values.
let mut tags = data . tags . clone ( ) ;
tags . sort ( ) ;
tags . dedup ( ) ;
if tags . len ( ) ! = data . tags . len ( ) {
return Err ( KclError ::Type ( KclErrorDetails {
message : " Duplicate tags are not allowed. " . to_string ( ) ,
source_ranges : vec ! [ args . source_range ] ,
} ) ) ;
}
2024-06-23 23:04:32 -07:00
// If you try and tag multiple edges with a tagged chamfer, we want to return an
// error to the user that they can only tag one edge at a time.
if tag . is_some ( ) & & data . tags . len ( ) > 1 {
return Err ( KclError ::Type ( KclErrorDetails {
2024-06-27 15:43:49 -07:00
message : " You can only tag one edge at a time with a tagged chamfer. Either delete the tag for the chamfer fn if you don't need it OR separate into individual chamfer functions for each tag. " . to_string ( ) ,
source_ranges : vec ! [ args . source_range ] ,
} ) ) ;
2024-06-23 23:04:32 -07:00
}
2024-09-27 15:44:44 -07:00
let mut solid = solid . clone ( ) ;
2024-06-23 23:04:32 -07:00
for edge_tag in data . tags {
let edge_id = match edge_tag {
2024-06-17 12:13:19 -07:00
EdgeReference ::Uuid ( uuid ) = > uuid ,
2024-09-16 15:10:33 -04:00
EdgeReference ::Tag ( edge_tag ) = > args . get_tag_engine_info ( exec_state , & edge_tag ) ? . id ,
2024-06-17 12:13:19 -07:00
} ;
2024-12-17 09:38:32 +13:00
let id = exec_state . global . id_generator . next_uuid ( ) ;
2024-06-22 14:31:37 -07:00
args . batch_end_cmd (
2024-06-23 19:19:24 -07:00
id ,
2024-09-18 17:04:04 -05:00
ModelingCmd ::from ( mcmd ::Solid3dFilletEdge {
2024-06-17 12:13:19 -07:00
edge_id ,
2024-09-27 15:44:44 -07:00
object_id : solid . id ,
2024-09-18 17:04:04 -05:00
radius : LengthUnit ( data . length ) ,
tolerance : LengthUnit ( DEFAULT_TOLERANCE ) , // We can let the user set this in the future.
cut_type : CutType ::Chamfer ,
2024-09-19 14:06:29 -07:00
// We pass in the command id as the face id.
// So the resulting face of the fillet will be the same.
// This is because that's how most other endpoints work.
2024-09-12 16:13:11 -07:00
face_id : Some ( id ) ,
2024-09-18 17:04:04 -05:00
} ) ,
2024-06-17 12:13:19 -07:00
)
. await ? ;
2024-06-23 19:19:24 -07:00
2024-09-27 15:44:44 -07:00
solid . edge_cuts . push ( EdgeCut ::Chamfer {
2024-06-23 19:19:24 -07:00
id ,
edge_id ,
length : data . length ,
2024-07-09 12:24:42 -04:00
tag : Box ::new ( tag . clone ( ) ) ,
2024-06-23 19:19:24 -07:00
} ) ;
2024-07-28 00:30:04 -07:00
if let Some ( ref tag ) = tag {
2024-09-27 15:44:44 -07:00
solid . value . push ( ExtrudeSurface ::Chamfer ( ChamferSurface {
2024-09-12 16:13:11 -07:00
face_id : id ,
2024-07-28 00:30:04 -07:00
tag : Some ( tag . clone ( ) ) ,
geo_meta : GeoMeta {
id ,
metadata : args . source_range . into ( ) ,
} ,
} ) ) ;
}
2024-06-17 12:13:19 -07:00
}
2024-09-27 15:44:44 -07:00
Ok ( solid )
2024-06-17 12:13:19 -07:00
}