diff --git a/docs/kcl-std/functions/std-solid-chamfer.md b/docs/kcl-std/functions/std-solid-chamfer.md index 6a5d0cd13..c6aed6b1f 100644 --- a/docs/kcl-std/functions/std-solid-chamfer.md +++ b/docs/kcl-std/functions/std-solid-chamfer.md @@ -13,6 +13,7 @@ chamfer( length: number(Length), tags: [Edge; 1+], tag?: tag, + strategy?: string, ): Solid ``` @@ -28,6 +29,7 @@ a sharp, straight transitional edge. | `length` | `number(Length)` | The length of the chamfer | Yes | | `tags` | [`[Edge; 1+]`](/docs/kcl-std/types/std-types-Edge) | The paths you want to chamfer | Yes | | [`tag`](/docs/kcl-std/types/std-types-tag) | [`tag`](/docs/kcl-std/types/std-types-tag) | Create a new tag which refers to this chamfer | No | +| `strategy` | [`string`](/docs/kcl-std/types/std-types-string) | Which strategy should be used to perform this chamfer? | No | ### Returns diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 636b1bc36..e470e9d03 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -535,7 +535,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -963,7 +963,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -1746,7 +1746,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -2079,9 +2079,9 @@ dependencies = [ [[package]] name = "kittycad-modeling-cmds" -version = "0.2.115" +version = "0.2.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e95dfcade93787f8a7529ad7b9b81f038823e273e7684297085ef720962b7497" +checksum = "6eed6c8a18f47919c0f873d58226438b737e3f873359b264bcf377f8843727b0" dependencies = [ "anyhow", "chrono", @@ -2986,7 +2986,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -3305,7 +3305,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -3899,7 +3899,7 @@ dependencies = [ "getrandom 0.3.1", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -4752,7 +4752,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] diff --git a/rust/kcl-lib/src/execution/artifact.rs b/rust/kcl-lib/src/execution/artifact.rs index 04a416793..c817af672 100644 --- a/rust/kcl-lib/src/execution/artifact.rs +++ b/rust/kcl-lib/src/execution/artifact.rs @@ -332,11 +332,10 @@ pub enum SweepEdgeSubType { pub struct EdgeCut { pub id: ArtifactId, pub sub_type: EdgeCutSubType, - pub consumed_edge_id: ArtifactId, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub edge_ids: Vec, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub surface_id: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub surface_ids: Vec, pub code_ref: CodeRef, } @@ -679,7 +678,7 @@ impl EdgeCut { let Artifact::EdgeCut(new) = new else { return Some(new); }; - merge_opt_id(&mut self.surface_id, new.surface_id); + merge_ids(&mut self.surface_ids, new.surface_ids); merge_ids(&mut self.edge_ids, new.edge_ids); None @@ -1270,22 +1269,19 @@ fn artifacts_to_update( } ModelingCmd::Solid3dFilletEdge(cmd) => { let mut return_arr = Vec::new(); + let mut surface_ids = Vec::with_capacity(cmd.extra_face_ids.len() + 1); + surface_ids.push(id); + for id in &cmd.extra_face_ids { + surface_ids.push(id.into()); + } return_arr.push(Artifact::EdgeCut(EdgeCut { id, sub_type: cmd.cut_type.into(), - consumed_edge_id: cmd.edge_id.into(), - edge_ids: Vec::new(), - surface_id: None, + edge_ids: cmd.edge_ids.iter().copied().map(From::from).collect(), + surface_ids, code_ref, })); - let consumed_edge = artifacts.get(&ArtifactId::new(cmd.edge_id)); - if let Some(Artifact::Segment(consumed_edge)) = consumed_edge { - let mut new_segment = consumed_edge.clone(); - new_segment.edge_cut_id = Some(id); - return_arr.push(Artifact::Segment(new_segment)); - } else { - // TODO: Handle other types like SweepEdge. - } + // TODO: Handle other types like SweepEdge. return Ok(return_arr); } ModelingCmd::EntityMakeHelixFromParams(_) => { diff --git a/rust/kcl-lib/src/execution/artifact/mermaid_tests.rs b/rust/kcl-lib/src/execution/artifact/mermaid_tests.rs index 01d018b8b..f44c09efc 100644 --- a/rust/kcl-lib/src/execution/artifact/mermaid_tests.rs +++ b/rust/kcl-lib/src/execution/artifact/mermaid_tests.rs @@ -82,7 +82,7 @@ impl Artifact { Artifact::Wall(a) => vec![a.seg_id, a.sweep_id], Artifact::Cap(a) => vec![a.sweep_id], Artifact::SweepEdge(a) => vec![a.seg_id, a.sweep_id], - Artifact::EdgeCut(a) => vec![a.consumed_edge_id], + Artifact::EdgeCut(a) => a.edge_ids.clone(), Artifact::EdgeCutEdge(a) => vec![a.edge_cut_id], Artifact::Helix(a) => a.axis_id.map(|id| vec![id]).unwrap_or_default(), } @@ -175,9 +175,7 @@ impl Artifact { // consumed_edge_id. let mut ids = Vec::new(); ids.extend(&a.edge_ids); - if let Some(surface_id) = a.surface_id { - ids.push(surface_id); - } + ids.extend(&a.surface_ids); ids } Artifact::EdgeCutEdge(a) => { diff --git a/rust/kcl-lib/src/modules.rs b/rust/kcl-lib/src/modules.rs index 74653fd15..c8bb3e34c 100644 --- a/rust/kcl-lib/src/modules.rs +++ b/rust/kcl-lib/src/modules.rs @@ -96,6 +96,7 @@ pub(crate) fn read_std(mod_name: &str) -> Option<&'static str> { "solid" => Some(include_str!("../std/solid.kcl")), "units" => Some(include_str!("../std/units.kcl")), "array" => Some(include_str!("../std/array.kcl")), + "cutStrategy" => Some(include_str!("../std/cutStrategy.kcl")), "transform" => Some(include_str!("../std/transform.kcl")), _ => None, } diff --git a/rust/kcl-lib/src/std/args.rs b/rust/kcl-lib/src/std/args.rs index 2bdf4af05..667e863cb 100644 --- a/rust/kcl-lib/src/std/args.rs +++ b/rust/kcl-lib/src/std/args.rs @@ -778,6 +778,18 @@ impl<'a> FromKclValue<'a> for TagNode { } } +impl<'a> FromKclValue<'a> for kcmc::shared::CutStrategy { + fn from_kcl_val(arg: &'a KclValue) -> Option { + match arg.as_str() { + Some("automatic") => Some(Self::Automatic), + Some("basic") => Some(Self::Basic), + Some("csg") => Some(Self::Csg), + Some(_) => None, + None => None, + } + } +} + impl<'a> FromKclValue<'a> for TagIdentifier { fn from_kcl_val(arg: &'a KclValue) -> Option { arg.get_tag_identifier().ok() diff --git a/rust/kcl-lib/src/std/chamfer.rs b/rust/kcl-lib/src/std/chamfer.rs index c3b6184b3..2d7b049f1 100644 --- a/rust/kcl-lib/src/std/chamfer.rs +++ b/rust/kcl-lib/src/std/chamfer.rs @@ -2,7 +2,7 @@ use anyhow::Result; use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, shared::CutType, ModelingCmd}; -use kittycad_modeling_cmds as kcmc; +use kittycad_modeling_cmds::{self as kcmc, shared::CutStrategy}; use super::args::TyF64; use crate::{ @@ -21,19 +21,24 @@ pub(crate) const DEFAULT_TOLERANCE: f64 = 0.0000001; pub async fn chamfer(exec_state: &mut ExecState, args: Args) -> Result { let solid = args.get_unlabeled_kw_arg_typed("solid", &RuntimeType::Primitive(PrimitiveType::Solid), exec_state)?; let length: TyF64 = args.get_kw_arg_typed("length", &RuntimeType::length(), exec_state)?; + let tolerance: Option = args.get_kw_arg_opt_typed("tolerance", &RuntimeType::length(), exec_state)?; + let strategy: Option = args.get_kw_arg_opt("strategy")?; let tags = args.kw_arg_array_and_source::("tags")?; let tag = args.get_kw_arg_opt("tag")?; super::fillet::validate_unique(&tags)?; let tags: Vec = tags.into_iter().map(|item| item.0).collect(); - let value = inner_chamfer(solid, length, tags, tag, exec_state, args).await?; + let value = inner_chamfer(solid, length, tags, tolerance, strategy, tag, exec_state, args).await?; Ok(KclValue::Solid { value }) } +#[allow(clippy::too_many_arguments)] async fn inner_chamfer( solid: Box, length: TyF64, tags: Vec, + tolerance: Option, + strategy: Option, tag: Option, exec_state: &mut ExecState, args: Args, @@ -46,44 +51,57 @@ async fn inner_chamfer( source_ranges: vec![args.source_range], })); } - + if tags.is_empty() { + return Err(KclError::Semantic(KclErrorDetails { + source_ranges: vec![args.source_range], + message: "You must chamfer at least one tag".to_owned(), + })); + } let mut solid = solid.clone(); - for edge_tag in tags { - let edge_id = match edge_tag { - EdgeReference::Uuid(uuid) => uuid, - EdgeReference::Tag(edge_tag) => args.get_tag_engine_info(exec_state, &edge_tag)?.id, - }; - - let id = exec_state.next_uuid(); - args.batch_end_cmd( - id, - ModelingCmd::from(mcmd::Solid3dFilletEdge { - edge_id, - object_id: solid.id, - radius: LengthUnit(length.to_mm()), - tolerance: LengthUnit(DEFAULT_TOLERANCE), // We can let the user set this in the future. - cut_type: CutType::Chamfer, - }), - ) - .await?; + let edge_ids: Vec<_> = tags + .into_iter() + .map(|edge_tag| edge_tag.get_engine_id(exec_state, &args)) + .collect::, _>>()?; + let id = exec_state.next_uuid(); + let mut extra_face_ids = Vec::new(); + let num_extra_ids = edge_ids.len() - 1; + for _ in 0..num_extra_ids { + extra_face_ids.push(exec_state.next_uuid()); + } + let strategy = strategy.unwrap_or_default(); + args.batch_end_cmd( + id, + ModelingCmd::from(mcmd::Solid3dFilletEdge { + edge_id: None, + edge_ids: edge_ids.clone(), + extra_face_ids: extra_face_ids.clone(), + strategy, + object_id: solid.id, + radius: LengthUnit(length.to_mm()), + tolerance: LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE)), + cut_type: CutType::Chamfer, + }), + ) + .await?; + for edge_id in edge_ids { solid.edge_cuts.push(EdgeCut::Chamfer { id, edge_id, length: length.clone(), tag: Box::new(tag.clone()), }); + } - if let Some(ref tag) = tag { - solid.value.push(ExtrudeSurface::Chamfer(ChamferSurface { - face_id: id, - tag: Some(tag.clone()), - geo_meta: GeoMeta { - id, - metadata: args.source_range.into(), - }, - })); - } + if let Some(ref tag) = tag { + solid.value.push(ExtrudeSurface::Chamfer(ChamferSurface { + face_id: id, + tag: Some(tag.clone()), + geo_meta: GeoMeta { + id, + metadata: args.source_range.into(), + }, + })); } Ok(solid) diff --git a/rust/kcl-lib/src/std/fillet.rs b/rust/kcl-lib/src/std/fillet.rs index d3d7b7686..661e090dd 100644 --- a/rust/kcl-lib/src/std/fillet.rs +++ b/rust/kcl-lib/src/std/fillet.rs @@ -3,7 +3,7 @@ use anyhow::Result; use indexmap::IndexMap; use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, shared::CutType, ModelingCmd}; -use kittycad_modeling_cmds as kcmc; +use kittycad_modeling_cmds::{self as kcmc, shared::CutStrategy}; use serde::{Deserialize, Serialize}; use super::{args::TyF64, DEFAULT_TOLERANCE}; @@ -62,59 +62,88 @@ pub async fn fillet(exec_state: &mut ExecState, args: Args) -> Result = args.get_kw_arg_opt_typed("tolerance", &RuntimeType::length(), exec_state)?; + let strategy: Option = args.get_kw_arg_opt("strategy")?; let tags = args.kw_arg_array_and_source::("tags")?; let tag = args.get_kw_arg_opt("tag")?; // Run the function. validate_unique(&tags)?; let tags: Vec = tags.into_iter().map(|item| item.0).collect(); - let value = inner_fillet(solid, radius, tags, tolerance, tag, exec_state, args).await?; + let value = inner_fillet(solid, radius, tags, tolerance, strategy, tag, exec_state, args).await?; Ok(KclValue::Solid { value }) } +#[allow(clippy::too_many_arguments)] async fn inner_fillet( solid: Box, radius: TyF64, tags: Vec, tolerance: Option, + strategy: Option, tag: Option, exec_state: &mut ExecState, args: Args, ) -> Result, KclError> { + // If you try and tag multiple edges with a tagged fillet, we want to return an + // error to the user that they can only tag one edge at a time. + if tag.is_some() && tags.len() > 1 { + return Err(KclError::Type(KclErrorDetails { + message: "You can only tag one edge at a time with a tagged fillet. Either delete the tag for the fillet fn if you don't need it OR separate into individual fillet functions for each tag.".to_string(), + source_ranges: vec![args.source_range], + })); + } + if tags.is_empty() { + return Err(KclError::Semantic(KclErrorDetails { + source_ranges: vec![args.source_range], + message: "You must fillet at least one tag".to_owned(), + })); + } let mut solid = solid.clone(); - for edge_tag in tags { - let edge_id = edge_tag.get_engine_id(exec_state, &args)?; + let edge_ids: Vec<_> = tags + .into_iter() + .map(|edge_tag| edge_tag.get_engine_id(exec_state, &args)) + .collect::, _>>()?; - let id = exec_state.next_uuid(); - args.batch_end_cmd( - id, - ModelingCmd::from(mcmd::Solid3dFilletEdge { - edge_id, - object_id: solid.id, - radius: LengthUnit(radius.to_mm()), - tolerance: LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE)), - cut_type: CutType::Fillet, - }), - ) - .await?; + let id = exec_state.next_uuid(); + let mut extra_face_ids = Vec::new(); + let num_extra_ids = edge_ids.len() - 1; + for _ in 0..num_extra_ids { + extra_face_ids.push(exec_state.next_uuid()); + } + let strategy = strategy.unwrap_or_default(); + args.batch_end_cmd( + id, + ModelingCmd::from(mcmd::Solid3dFilletEdge { + edge_id: None, + edge_ids: edge_ids.clone(), + extra_face_ids: extra_face_ids.clone(), + strategy, + object_id: solid.id, + radius: LengthUnit(radius.to_mm()), + tolerance: LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE)), + cut_type: CutType::Fillet, + }), + ) + .await?; + for edge_id in edge_ids { solid.edge_cuts.push(EdgeCut::Fillet { id, edge_id, radius: radius.clone(), tag: Box::new(tag.clone()), }); + } - if let Some(ref tag) = tag { - solid.value.push(ExtrudeSurface::Fillet(FilletSurface { - face_id: id, - tag: Some(tag.clone()), - geo_meta: GeoMeta { - id, - metadata: args.source_range.into(), - }, - })); - } + if let Some(ref tag) = tag { + solid.value.push(ExtrudeSurface::Fillet(FilletSurface { + face_id: id, + tag: Some(tag.clone()), + geo_meta: GeoMeta { + id, + metadata: args.source_range.into(), + }, + })); } Ok(solid) diff --git a/rust/kcl-lib/std/cutStrategy.kcl b/rust/kcl-lib/std/cutStrategy.kcl new file mode 100644 index 000000000..a474eae63 --- /dev/null +++ b/rust/kcl-lib/std/cutStrategy.kcl @@ -0,0 +1,9 @@ +@no_std +@settings(defaultLengthUnit = mm, kclVersion = 1.0) + +/// Try the fast but simple Basic strategy, and if that fails, try the slow and complex CSG strategy. +export automatic = "automatic" +/// The basic strategy is fastest, but it can't handle complicated cases (like chamfering two edges that touch). +export basic = "basic" +/// The CSG strategy is slowest, but it can handle complex cases (like chamfering two edges that touch). +export csg = "csg" diff --git a/rust/kcl-lib/std/prelude.kcl b/rust/kcl-lib/std/prelude.kcl index 01ec550f3..76047152c 100644 --- a/rust/kcl-lib/std/prelude.kcl +++ b/rust/kcl-lib/std/prelude.kcl @@ -16,6 +16,7 @@ export import * from "std::sketch" export import * from "std::solid" export import * from "std::transform" export import "std::turns" +export import "std::cutStrategy" export XY = { origin = { x = 0, y = 0, z = 0 }, diff --git a/rust/kcl-lib/std/solid.kcl b/rust/kcl-lib/std/solid.kcl index ad1858bb0..089bfdaec 100644 --- a/rust/kcl-lib/std/solid.kcl +++ b/rust/kcl-lib/std/solid.kcl @@ -71,6 +71,8 @@ export fn fillet( tolerance?: number(Length), /// Create a new tag which refers to this fillet tag?: tag, + /// Which strategy should be used to perform this chamfer? + strategy?: string, ): Solid {} /// Cut a straight transitional edge along a tagged path. @@ -146,6 +148,8 @@ export fn chamfer( tags: [Edge; 1+], /// Create a new tag which refers to this chamfer tag?: tag, + /// Which strategy should be used to perform this chamfer? + strategy?: string, ): Solid {} /// Remove volume from a 3-dimensional shape such that a wall of the