Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
0a263a3
add ratio and angle to chamfer kcl
benjamaan476 Aug 15, 2025
0c7deea
Merge remote-tracking branch 'origin/main' into ben/chamfer_angle
benjamaan476 Aug 25, 2025
6199983
specify the strategy if needed
benjamaan476 Aug 26, 2025
8118e84
Merge remote-tracking branch 'origin/main' into ben/chamfer_angle
benjamaan476 Aug 26, 2025
3016c3b
hash out custom profiles for chamfer cuts
benjamaan476 Aug 26, 2025
0329344
merge main
benjamaan476 Sep 3, 2025
8aaf6c1
Merge remote-tracking branch 'origin/main' into ben/chamfer_angle
benjamaan476 Sep 4, 2025
69eab2e
add swap argument to chamfer kcl and pass through to API
benjamaan476 Sep 4, 2025
d8c3a0b
turn of custom profiles for now
benjamaan476 Sep 4, 2025
9dcc59b
change ratio to second_length and start ui
benjamaan476 Sep 5, 2025
89b923f
reword todo
benjamaan476 Sep 5, 2025
ea05d32
change chamfer over to Solid3dCutEdges
benjamaan476 Sep 30, 2025
1713801
limit chamfer angle to 90deg
benjamaan476 Sep 30, 2025
fdf1c53
remove typescript changes
benjamaan476 Oct 1, 2025
59292ad
fmt
benjamaan476 Oct 1, 2025
8fe81b2
cleaup
benjamaan476 Oct 1, 2025
7ae79bd
clippy and fmt
benjamaan476 Oct 1, 2025
98e735c
clippy
benjamaan476 Oct 1, 2025
d4c3202
moar clippy
benjamaan476 Oct 1, 2025
6d0cd75
Merge remote-tracking branch 'origin/main' into ben/chamfer_angle
benjamaan476 Oct 1, 2025
0205a13
redo sim tests and docs
benjamaan476 Oct 1, 2025
b0926ad
fmt nightly
benjamaan476 Oct 1, 2025
c55aef8
Merge remote-tracking branch 'origin/main' into ben/chamfer_angle
benjamaan476 Oct 21, 2025
bba0e0d
sim tests and new images
benjamaan476 Oct 21, 2025
b5268f4
Improve error and fix kcl kwarg type
benjamaan476 Oct 21, 2025
f8f603b
redo docs
benjamaan476 Oct 21, 2025
113723c
fix typo
benjamaan476 Oct 21, 2025
2e515dd
check for le 0 angle
benjamaan476 Oct 21, 2025
53ce92b
Merge remote-tracking branch 'origin/main' into ben/chamfer_angle
benjamaan476 Oct 21, 2025
4663e0b
redo sim tests
benjamaan476 Oct 21, 2025
40d1979
remove swap as chamfer argument
benjamaan476 Oct 21, 2025
44c91f8
remove swap from docs and test image
benjamaan476 Oct 21, 2025
e692c2d
Update docs
adamchalmers Oct 21, 2025
d3e1673
Add valid range to angle doc
benjamaan476 Oct 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion docs/kcl-std/functions/std-solid-chamfer.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ chamfer(
@solid: Solid,
length: number(Length),
tags: [Edge; 1+],
secondLength?: number(Length),
angle?: number(Angle),
tag?: TagDecl,
): Solid
```
Expand All @@ -25,8 +27,10 @@ a sharp, straight transitional edge.
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `solid` | [`Solid`](/docs/kcl-std/types/std-types-Solid) | The solid whose edges should be chamfered | Yes |
| `length` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The length of the chamfer | Yes |
| `length` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | Chamfering cuts away two faces to create a third face. This is the length to chamfer away from each face. The larger this length to chamfer away, the larger the new face will be. | Yes |
| `tags` | [`[Edge; 1+]`](/docs/kcl-std/types/std-types-Edge) | The paths you want to chamfer | Yes |
| `secondLength` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | Chamfering cuts away two faces to create a third face. If this argument isn't given, the lengths chamfered away from both the first and second face are both given by `length`. If this argument _is_ given, it determines how much is cut away from the second face. Incompatible with `angle`. | No |
| `angle` | [`number(Angle)`](/docs/kcl-std/types/std-types-number) | Chamfering cuts away two faces to create a third face. This argument determines the angle between the two cut edges. Requires `length`, incompatible with `secondLength`. The valid range is 0deg < angle < 90deg. | No |
| `tag` | [`TagDecl`](/docs/kcl-std/types/std-types-TagDecl) | Create a new tag which refers to this chamfer | No |

### Returns
Expand Down Expand Up @@ -120,4 +124,37 @@ sketch001 = startSketchOn(part001, face = chamfer1)
>
</model-viewer>

```kcl
// Specify a custom chamfer angle.
fn cube(pos, scale) {
sg = startSketchOn(XY)
|> startProfile(at = pos)
|> line(end = [0, scale])
|> line(end = [scale, 0])
|> line(end = [0, -scale])

return sg
}

part001 = cube(pos = [0, 0], scale = 20)
|> close(tag = $line1)
|> extrude(length = 20)
|> chamfer(length = 10, angle = 30deg, tags = [getOppositeEdge(line1)])

```


<model-viewer
class="kcl-example"
alt="Example showing a rendered KCL program that uses the chamfer function"
src="/kcl-test-outputs/models/serial_test_example_fn_std-solid-chamfer2_output.gltf"
ar
environment-image="/moon_1k.hdr"
poster="/kcl-test-outputs/serial_test_example_fn_std-solid-chamfer2.png"
shadow-intensity="1"
camera-controls
touch-action="pan-y"
>
</model-viewer>


1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file modified public/kcl-samples/screenshots/surgical-drill-guide.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/kcl-samples/screenshots/teapot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/kcl-samples/screenshots/zoo-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ kittycad = { version = "0.4.2", default-features = false, features = [
"js",
"requests",
] }
kittycad-modeling-cmds = { version = "0.2.144", features = [
kittycad-modeling-cmds = { version = "0.2.145", features = [
"ts-rs",
"websocket",
] }
Expand Down Expand Up @@ -83,6 +83,7 @@ insta = { opt-level = 3 }
debug = "line-tables-only"

#Example: how to point modeling-app at a different repo (e.g. a branch or a local clone)
#[patch.crates-io]
#kittycad-modeling-cmds = { path = "../../modeling-api/modeling-cmds" }
#kittycad-modeling-session = { path = "../../modeling-api/modeling-session" }
# [patch.crates-io]
# kittycad-modeling-cmds = { git = "https://github.com/KittyCAD/modeling-api.git", branch = "ben/chamfer_angle" }
# kittycad-modeling-session = { git = "https://github.com/KittyCAD/modeling-api.git", branch = "ben/chamfer_angle" }
# #kittycad-modeling-session = { path = "../../modeling-api/modeling-session" }
1 change: 1 addition & 0 deletions rust/kcl-derive-docs/src/example_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ pub const TEST_NAMES: &[&str] = &[
"std-solid-appearance-9",
"std-solid-chamfer-0",
"std-solid-chamfer-1",
"std-solid-chamfer-2",
"std-solid-fillet-0",
"std-solid-fillet-1",
"std-solid-hollow-0",
Expand Down
36 changes: 36 additions & 0 deletions rust/kcl-lib/src/execution/artifact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ pub struct EdgeCut {
pub enum EdgeCutSubType {
Fillet,
Chamfer,
Custom,
}

impl From<kcmc::shared::CutType> for EdgeCutSubType {
Expand All @@ -305,6 +306,16 @@ impl From<kcmc::shared::CutType> for EdgeCutSubType {
}
}

impl From<kcmc::shared::CutTypeV2> for EdgeCutSubType {
fn from(cut_type: kcmc::shared::CutTypeV2) -> Self {
match cut_type {
kcmc::shared::CutTypeV2::Fillet { .. } => EdgeCutSubType::Fillet,
kcmc::shared::CutTypeV2::Chamfer { .. } => EdgeCutSubType::Chamfer,
kcmc::shared::CutTypeV2::Custom { .. } => EdgeCutSubType::Custom,
}
}
}

#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
#[ts(export_to = "Artifact.ts")]
#[serde(rename_all = "camelCase")]
Expand Down Expand Up @@ -1301,6 +1312,31 @@ fn artifacts_to_update(
}
return Ok(return_arr);
}
ModelingCmd::Solid3dCutEdges(cmd) => {
let mut return_arr = Vec::new();
let edge_id = if let Some(edge_id) = cmd.edge_ids.first() {
edge_id.into()
} else {
internal_error!(range, "Solid3dCutEdges command has no edge ID: id={id:?}, cmd={cmd:?}");
};
return_arr.push(Artifact::EdgeCut(EdgeCut {
id,
sub_type: cmd.cut_type.into(),
consumed_edge_id: edge_id,
edge_ids: Vec::new(),
surface_id: None,
code_ref,
}));
let consumed_edge = artifacts.get(&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.
}
return Ok(return_arr);
}
ModelingCmd::EntityMakeHelixFromParams(_) => {
let return_arr = vec![Artifact::Helix(Helix {
id,
Expand Down
75 changes: 66 additions & 9 deletions rust/kcl-lib/src/std/chamfer.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
//! Standard library chamfers.

use anyhow::Result;
use kcmc::{ModelingCmd, each_cmd as mcmd, length_unit::LengthUnit, shared::CutType};
use kittycad_modeling_cmds as kcmc;
use kcmc::{
ModelingCmd, each_cmd as mcmd,
length_unit::LengthUnit,
shared::{CutStrategy, CutTypeV2},
};
use kittycad_modeling_cmds::{self as kcmc, shared::Angle};

use super::args::TyF64;
use crate::{
errors::{KclError, KclErrorDetails},
execution::{
ChamferSurface, EdgeCut, ExecState, ExtrudeSurface, GeoMeta, KclValue, ModelingCmdMeta, Solid,
ChamferSurface, EdgeCut, ExecState, ExtrudeSurface, GeoMeta, KclValue, ModelingCmdMeta, Sketch, Solid,
types::RuntimeType,
},
parsing::ast::types::TagNode,
Expand All @@ -22,18 +26,26 @@ pub async fn chamfer(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
let solid = args.get_unlabeled_kw_arg("solid", &RuntimeType::solid(), exec_state)?;
let length: TyF64 = args.get_kw_arg("length", &RuntimeType::length(), exec_state)?;
let tags = args.kw_arg_edge_array_and_source("tags")?;
let second_length = args.get_kw_arg_opt("secondLength", &RuntimeType::length(), exec_state)?;
let angle = args.get_kw_arg_opt("angle", &RuntimeType::angle(), exec_state)?;
// TODO: custom profiles not ready yet

let tag = args.get_kw_arg_opt("tag", &RuntimeType::tag_decl(), exec_state)?;

super::fillet::validate_unique(&tags)?;
let tags: Vec<EdgeReference> = 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, second_length, angle, None, tag, exec_state, args).await?;
Ok(KclValue::Solid { value })
}

#[allow(clippy::too_many_arguments)]
async fn inner_chamfer(
solid: Box<Solid>,
length: TyF64,
tags: Vec<EdgeReference>,
second_length: Option<TyF64>,
angle: Option<TyF64>,
custom_profile: Option<Sketch>,
tag: Option<TagNode>,
exec_state: &mut ExecState,
args: Args,
Expand All @@ -47,6 +59,53 @@ async fn inner_chamfer(
)));
}

if angle.is_some() && second_length.is_some() {
return Err(KclError::new_semantic(KclErrorDetails::new(
"Cannot specify both an angle and a second length. Specify only one.".to_string(),
vec![args.source_range],
)));
}

let strategy = if second_length.is_some() || angle.is_some() || custom_profile.is_some() {
CutStrategy::Csg
} else {
Default::default()
};

let second_distance = second_length.map(|x| LengthUnit(x.to_mm()));
let angle = angle.map(|x| Angle::from_degrees(x.to_degrees(exec_state, args.source_range)));
if let Some(angle) = angle
&& (angle.ge(&Angle::quarter_circle()) || angle.le(&Angle::zero()))
{
return Err(KclError::new_semantic(KclErrorDetails::new(
"The angle of a chamfer must be greater than zero and less than 90 degrees.".to_string(),
vec![args.source_range],
)));
}

let cut_type = if let Some(custom_profile) = custom_profile {
// Hide the custom profile since it's no longer its own profile
exec_state
.batch_modeling_cmd(
ModelingCmdMeta::from(&args),
ModelingCmd::from(mcmd::ObjectVisible {
object_id: custom_profile.id,
hidden: true,
}),
)
.await?;
CutTypeV2::Custom {
path: custom_profile.id,
}
} else {
CutTypeV2::Chamfer {
distance: LengthUnit(length.to_mm()),
second_distance,
angle,
swap: false,
}
};

let mut solid = solid.clone();
for edge_tag in tags {
let edge_id = match edge_tag {
Expand All @@ -58,15 +117,13 @@ async fn inner_chamfer(
exec_state
.batch_end_cmd(
ModelingCmdMeta::from_args_id(&args, id),
ModelingCmd::from(mcmd::Solid3dFilletEdge {
edge_id: None,
ModelingCmd::from(mcmd::Solid3dCutEdges {
edge_ids: vec![edge_id],
extra_face_ids: vec![],
strategy: Default::default(),
strategy,
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,
cut_type,
}),
)
.await?;
Expand Down
38 changes: 37 additions & 1 deletion rust/kcl-lib/std/solid.kcl
Original file line number Diff line number Diff line change
Expand Up @@ -139,14 +139,50 @@ export fn fillet(
/// |> close()
/// |> extrude(length = 10)
/// ```
///
/// ```
/// // Specify a custom chamfer angle.
/// fn cube(pos, scale) {
/// sg = startSketchOn(XY)
/// |> startProfile(at = pos)
/// |> line(end = [0, scale])
/// |> line(end = [scale, 0])
/// |> line(end = [0, -scale])
///
/// return sg
/// }
///
/// part001 = cube(pos = [0,0], scale = 20)
/// |> close(tag = $line1)
/// |> extrude(length = 20)
/// |> chamfer(
/// length = 10,
/// angle = 30deg,
/// tags = [getOppositeEdge(line1)],
/// )
/// ```
@(impl = std_rust, feature_tree = true)
export fn chamfer(
/// The solid whose edges should be chamfered
@solid: Solid,
/// The length of the chamfer
/// Chamfering cuts away two faces to create a third face.
/// This is the length to chamfer away from each face.
/// The larger this length to chamfer away, the larger the
/// new face will be.
length: number(Length),
/// The paths you want to chamfer
tags: [Edge; 1+],
/// Chamfering cuts away two faces to create a third face.
/// If this argument isn't given, the lengths chamfered away from both
/// the first and second face are both given by `length`.
/// If this argument _is_ given, it determines how much is cut away
/// from the second face. Incompatible with `angle`.
secondLength?: number(Length),
/// Chamfering cuts away two faces to create a third face.
/// This argument determines the angle between the two cut edges.
/// Requires `length`, incompatible with `secondLength`.
/// The valid range is 0deg < angle < 90deg.
angle?: number(Angle),
/// Create a new tag which refers to this chamfer
tag?: TagDecl,
): Solid {}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Error from executing invalid_index_fractional.kcl
---
KCL Semantic error
Expand Down
Loading
Loading