Tag and name end and start caps (#5874)

* initial commit

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* update for revolve

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
This commit is contained in:
Jess Frazelle
2025-03-19 12:18:19 -07:00
committed by GitHub
parent 45fae52afc
commit d19a7df7e8
16 changed files with 13361 additions and 15 deletions

View File

@ -12,6 +12,8 @@ You can provide more than one sketch to extrude, and they will all be extruded i
extrude(
sketches: [Sketch],
length: number,
tagStart?: TagDeclarator,
tagEnd?: TagDeclarator,
): [Solid]
```
@ -22,6 +24,8 @@ extrude(
|----------|------|-------------|----------|
| `sketches` | [`[Sketch]`](/docs/kcl/types/Sketch) | Which sketch or sketches should be extruded | Yes |
| `length` | [`number`](/docs/kcl/types/number) | How far to extrude the given sketches | Yes |
| `tagStart` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | A named tag for the face at the start of the extrusion, i.e. the original sketch | No |
| `tagEnd` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | A named tag for the face at the end of the extrusion, i.e. the new face created by extruding the original sketch | No |
### Returns

View File

@ -15,6 +15,8 @@ loft(
bezApproximateRational: bool,
baseCurveIndex?: integer,
tolerance?: number,
tagStart?: TagDeclarator,
tagEnd?: TagDeclarator,
): Solid
```
@ -28,6 +30,8 @@ loft(
| `bezApproximateRational` | [`bool`](/docs/kcl/types/bool) | Attempt to approximate rational curves (such as arcs) using a bezier. This will remove banding around interpolations between arcs and non-arcs. It may produce errors in other scenarios Over time, this field won't be necessary. | Yes |
| `baseCurveIndex` | `integer` | This can be set to override the automatically determined topological base curve, which is usually the first section encountered. | No |
| `tolerance` | [`number`](/docs/kcl/types/number) | Tolerance for the loft operation. | No |
| `tagStart` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | A named tag for the face at the start of the loft, i.e. the original sketch | No |
| `tagEnd` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | A named tag for the face at the end of the loft, i.e. the last sketch | No |
### Returns

View File

@ -18,6 +18,8 @@ revolve(
axis: Axis2dOrEdgeReference,
angle?: number,
tolerance?: number,
tagStart?: TagDeclarator,
tagEnd?: TagDeclarator,
): [Solid]
```
@ -30,6 +32,8 @@ revolve(
| `axis` | [`Axis2dOrEdgeReference`](/docs/kcl/types/Axis2dOrEdgeReference) | Axis of revolution. | Yes |
| `angle` | [`number`](/docs/kcl/types/number) | Angle to revolve (in degrees). Default is 360. | No |
| `tolerance` | [`number`](/docs/kcl/types/number) | Tolerance for the revolve operation. | No |
| `tagStart` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | A named tag for the face at the start of the revolve, i.e. the original sketch | No |
| `tagEnd` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | A named tag for the face at the end of the revolve | No |
### Returns

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -16,6 +16,8 @@ sweep(
path: SweepPath,
sectional?: bool,
tolerance?: number,
tagStart?: TagDeclarator,
tagEnd?: TagDeclarator,
): [Solid]
```
@ -28,6 +30,8 @@ sweep(
| `path` | [`SweepPath`](/docs/kcl/types/SweepPath) | The path to sweep the sketch along | Yes |
| `sectional` | [`bool`](/docs/kcl/types/bool) | If true, the sweep will be broken up into sub-sweeps (extrusions, revolves, sweeps) based on the trajectory path components. | No |
| `tolerance` | [`number`](/docs/kcl/types/number) | Tolerance for this operation | No |
| `tagStart` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | A named tag for the face at the start of the sweep, i.e. the original sketch | No |
| `tagEnd` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | A named tag for the face at the end of the sweep | No |
### Returns

View File

@ -22,6 +22,7 @@ use crate::{
kcl_value::{ArrayLen, RuntimeType},
ArtifactId, ExecState, ExtrudeSurface, GeoMeta, KclValue, Path, PrimitiveType, Sketch, SketchSurface, Solid,
},
parsing::ast::types::TagNode,
std::Args,
};
@ -33,8 +34,10 @@ pub async fn extrude(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
exec_state,
)?;
let length = args.get_kw_arg("length")?;
let tag_start = args.get_kw_arg_opt("tagStart")?;
let tag_end = args.get_kw_arg_opt("tagEnd")?;
let result = inner_extrude(sketches, length, exec_state, args).await?;
let result = inner_extrude(sketches, length, tag_start, tag_end, exec_state, args).await?;
Ok(result.into())
}
@ -96,11 +99,16 @@ pub async fn extrude(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
args = {
sketches = { docs = "Which sketch or sketches should be extruded"},
length = { docs = "How far to extrude the given sketches"},
tag_start = { docs = "A named tag for the face at the start of the extrusion, i.e. the original sketch" },
tag_end = { docs = "A named tag for the face at the end of the extrusion, i.e. the new face created by extruding the original sketch" },
}
}]
#[allow(clippy::too_many_arguments)]
async fn inner_extrude(
sketches: Vec<Sketch>,
length: f64,
tag_start: Option<TagNode>,
tag_end: Option<TagNode>,
exec_state: &mut ExecState,
args: Args,
) -> Result<Vec<Solid>, KclError> {
@ -121,18 +129,38 @@ async fn inner_extrude(
))
.await?;
solids.push(do_post_extrude(sketch.clone(), id.into(), length, exec_state, args.clone()).await?);
solids.push(
do_post_extrude(
sketch,
id.into(),
length,
&NamedCapTags {
start: tag_start.as_ref(),
end: tag_end.as_ref(),
},
exec_state,
&args,
)
.await?,
);
}
Ok(solids)
}
pub(crate) async fn do_post_extrude(
sketch: Sketch,
#[derive(Debug, Default)]
pub(crate) struct NamedCapTags<'a> {
pub start: Option<&'a TagNode>,
pub end: Option<&'a TagNode>,
}
pub(crate) async fn do_post_extrude<'a>(
sketch: &Sketch,
solid_id: ArtifactId,
length: f64,
named_cap_tags: &'a NamedCapTags<'a>,
exec_state: &mut ExecState,
args: Args,
args: &Args,
) -> Result<Solid, KclError> {
// Bring the object to the front of the scene.
// See: https://github.com/KittyCAD/modeling-app/issues/806
@ -217,10 +245,11 @@ pub(crate) async fn do_post_extrude(
sides: face_id_map,
start_cap_id,
end_cap_id,
} = analyze_faces(exec_state, &args, face_infos).await;
} = analyze_faces(exec_state, args, face_infos).await;
// Iterate over the sketch.value array and add face_id to GeoMeta
let no_engine_commands = args.ctx.no_engine_commands().await;
let new_value = sketch
let mut new_value: Vec<ExtrudeSurface> = sketch
.paths
.iter()
.flat_map(|path| {
@ -283,6 +312,48 @@ pub(crate) async fn do_post_extrude(
})
.collect();
// Add the tags for the start or end caps.
if let Some(tag_start) = named_cap_tags.start {
let Some(start_cap_id) = start_cap_id else {
return Err(KclError::Type(KclErrorDetails {
message: format!(
"Expected a start cap ID for tag `{}` for extrusion of sketch {:?}",
tag_start.name, sketch.id
),
source_ranges: vec![args.source_range],
}));
};
new_value.push(ExtrudeSurface::ExtrudePlane(crate::execution::ExtrudePlane {
face_id: start_cap_id,
tag: Some(tag_start.clone()),
geo_meta: GeoMeta {
id: start_cap_id,
metadata: args.source_range.into(),
},
}));
}
if let Some(tag_end) = named_cap_tags.end {
let Some(end_cap_id) = end_cap_id else {
return Err(KclError::Type(KclErrorDetails {
message: format!(
"Expected an end cap ID for tag `{}` for extrusion of sketch {:?}",
tag_end.name, sketch.id
),
source_ranges: vec![args.source_range],
}));
};
new_value.push(ExtrudeSurface::ExtrudePlane(crate::execution::ExtrudePlane {
face_id: end_cap_id,
tag: Some(tag_end.clone()),
geo_meta: GeoMeta {
id: end_cap_id,
metadata: args.source_range.into(),
},
}));
}
Ok(Solid {
// Ok so you would think that the id would be the id of the solid,
// that we passed in to the function, but it's actually the id of the

View File

@ -13,6 +13,7 @@ use crate::{
kcl_value::{ArrayLen, RuntimeType},
ExecState, KclValue, PrimitiveType, Sketch, Solid,
},
parsing::ast::types::TagNode,
std::{extrude::do_post_extrude, fillet::default_tolerance, Args},
};
@ -36,6 +37,8 @@ pub async fn loft(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
let base_curve_index: Option<u32> = args.get_kw_arg_opt("baseCurveIndex")?;
// Tolerance for the loft operation.
let tolerance: Option<f64> = args.get_kw_arg_opt("tolerance")?;
let tag_start = args.get_kw_arg_opt("tagStart")?;
let tag_end = args.get_kw_arg_opt("tagEnd")?;
let value = inner_loft(
sketches,
@ -43,6 +46,8 @@ pub async fn loft(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
bez_approximate_rational,
base_curve_index,
tolerance,
tag_start,
tag_end,
exec_state,
args,
)
@ -127,14 +132,19 @@ pub async fn loft(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
bez_approximate_rational = {docs = "Attempt to approximate rational curves (such as arcs) using a bezier. This will remove banding around interpolations between arcs and non-arcs. It may produce errors in other scenarios Over time, this field won't be necessary."},
base_curve_index = {docs = "This can be set to override the automatically determined topological base curve, which is usually the first section encountered."},
tolerance = {docs = "Tolerance for the loft operation."},
tag_start = { docs = "A named tag for the face at the start of the loft, i.e. the original sketch" },
tag_end = { docs = "A named tag for the face at the end of the loft, i.e. the last sketch" },
}
}]
#[allow(clippy::too_many_arguments)]
async fn inner_loft(
sketches: Vec<Sketch>,
v_degree: NonZeroU32,
bez_approximate_rational: bool,
base_curve_index: Option<u32>,
tolerance: Option<f64>,
tag_start: Option<TagNode>,
tag_end: Option<TagNode>,
exec_state: &mut ExecState,
args: Args,
) -> Result<Box<Solid>, KclError> {
@ -167,6 +177,17 @@ async fn inner_loft(
// Override its id with the loft id so we can get its faces later
sketch.id = id;
Ok(Box::new(
do_post_extrude(sketch, id.into(), 0.0, exec_state, args).await?,
do_post_extrude(
&sketch,
id.into(),
0.0,
&super::extrude::NamedCapTags {
start: tag_start.as_ref(),
end: tag_end.as_ref(),
},
exec_state,
&args,
)
.await?,
))
}

View File

@ -11,6 +11,7 @@ use crate::{
kcl_value::{ArrayLen, RuntimeType},
ExecState, KclValue, PrimitiveType, Sketch, Solid,
},
parsing::ast::types::TagNode,
std::{axis_or_reference::Axis2dOrEdgeReference, extrude::do_post_extrude, fillet::default_tolerance, Args},
};
@ -24,8 +25,10 @@ pub async fn revolve(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
let axis: Axis2dOrEdgeReference = args.get_kw_arg("axis")?;
let angle = args.get_kw_arg_opt("angle")?;
let tolerance = args.get_kw_arg_opt("tolerance")?;
let tag_start = args.get_kw_arg_opt("tagStart")?;
let tag_end = args.get_kw_arg_opt("tagEnd")?;
let value = inner_revolve(sketches, axis, angle, tolerance, exec_state, args).await?;
let value = inner_revolve(sketches, axis, angle, tolerance, tag_start, tag_end, exec_state, args).await?;
Ok(value.into())
}
@ -234,13 +237,18 @@ pub async fn revolve(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
axis = { docs = "Axis of revolution." },
angle = { docs = "Angle to revolve (in degrees). Default is 360." },
tolerance = { docs = "Tolerance for the revolve operation." },
tag_start = { docs = "A named tag for the face at the start of the revolve, i.e. the original sketch" },
tag_end = { docs = "A named tag for the face at the end of the revolve" },
}
}]
#[allow(clippy::too_many_arguments)]
async fn inner_revolve(
sketches: Vec<Sketch>,
axis: Axis2dOrEdgeReference,
angle: Option<f64>,
tolerance: Option<f64>,
tag_start: Option<TagNode>,
tag_end: Option<TagNode>,
exec_state: &mut ExecState,
args: Args,
) -> Result<Vec<Solid>, KclError> {
@ -293,7 +301,20 @@ async fn inner_revolve(
}
}
solids.push(do_post_extrude(sketch.clone(), id.into(), 0.0, exec_state, args.clone()).await?);
solids.push(
do_post_extrude(
sketch,
id.into(),
0.0,
&super::extrude::NamedCapTags {
start: tag_start.as_ref(),
end: tag_end.as_ref(),
},
exec_state,
&args,
)
.await?,
);
}
Ok(solids)

View File

@ -951,6 +951,37 @@ pub async fn start_sketch_on(exec_state: &mut ExecState, args: Args) -> Result<K
/// ```
///
/// ```no_run
/// // Sketch on the end of an extruded face by tagging the end face.
///
/// exampleSketch = startSketchOn(XY)
/// |> startProfileAt([0, 0], %)
/// |> line(end = [10, 0])
/// |> line(end = [0, 10])
/// |> line(end = [-10, 0])
/// |> close()
///
/// example = extrude(exampleSketch, length = 5, tagEnd = $end01)
///
/// exampleSketch002 = startSketchOn(example, end01)
/// |> startProfileAt([1, 1], %)
/// |> line(end = [8, 0])
/// |> line(end = [0, 8])
/// |> line(end = [-8, 0])
/// |> close()
///
/// example002 = extrude(exampleSketch002, length = 5, tagEnd = $end02)
///
/// exampleSketch003 = startSketchOn(example002, end02)
/// |> startProfileAt([2, 2], %)
/// |> line(end = [6, 0])
/// |> line(end = [0, 6])
/// |> line(end = [-6, 0])
/// |> close()
///
/// example003 = extrude(exampleSketch003, length = 5)
/// ```
///
/// ```no_run
/// exampleSketch = startSketchOn(XY)
/// |> startProfileAt([0, 0], %)
/// |> line(end = [10, 0])
@ -1004,6 +1035,32 @@ pub async fn start_sketch_on(exec_state: &mut ExecState, args: Args) -> Result<K
/// ```
///
/// ```no_run
/// // Sketch on the end of a revolved face by tagging the end face.
///
/// exampleSketch = startSketchOn(XY)
/// |> startProfileAt([4, 12], %)
/// |> line(end = [2, 0])
/// |> line(end = [0, -6])
/// |> line(end = [4, -6])
/// |> line(end = [0, -6])
/// |> line(end = [-3.75, -4.5])
/// |> line(end = [0, -5.5])
/// |> line(end = [-2, 0])
/// |> close()
///
/// example = revolve(exampleSketch, axis = 'y', angle = 180, tagEnd = $end01)
///
/// exampleSketch002 = startSketchOn(example, end01)
/// |> startProfileAt([4.5, -5], %)
/// |> line(end = [0, 5])
/// |> line(end = [5, 0])
/// |> line(end = [0, -5])
/// |> close()
///
/// example002 = extrude(exampleSketch002, length = 5)
/// ```
///
/// ```no_run
/// a1 = startSketchOn({
/// plane: {
/// origin = { x = 0, y = 0, z = 0 },

View File

@ -13,6 +13,7 @@ use crate::{
kcl_value::{ArrayLen, RuntimeType},
ExecState, Helix, KclValue, PrimitiveType, Sketch, Solid,
},
parsing::ast::types::TagNode,
std::{extrude::do_post_extrude, fillet::default_tolerance, Args},
};
@ -35,8 +36,13 @@ pub async fn sweep(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
let path: SweepPath = args.get_kw_arg("path")?;
let sectional = args.get_kw_arg_opt("sectional")?;
let tolerance = args.get_kw_arg_opt("tolerance")?;
let tag_start = args.get_kw_arg_opt("tagStart")?;
let tag_end = args.get_kw_arg_opt("tagEnd")?;
let value = inner_sweep(sketches, path, sectional, tolerance, exec_state, args).await?;
let value = inner_sweep(
sketches, path, sectional, tolerance, tag_start, tag_end, exec_state, args,
)
.await?;
Ok(value.into())
}
@ -145,13 +151,18 @@ pub async fn sweep(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
path = { docs = "The path to sweep the sketch along" },
sectional = { docs = "If true, the sweep will be broken up into sub-sweeps (extrusions, revolves, sweeps) based on the trajectory path components." },
tolerance = { docs = "Tolerance for this operation" },
tag_start = { docs = "A named tag for the face at the start of the sweep, i.e. the original sketch" },
tag_end = { docs = "A named tag for the face at the end of the sweep" },
}
}]
#[allow(clippy::too_many_arguments)]
async fn inner_sweep(
sketches: Vec<Sketch>,
path: SweepPath,
sectional: Option<bool>,
tolerance: Option<f64>,
tag_start: Option<TagNode>,
tag_end: Option<TagNode>,
exec_state: &mut ExecState,
args: Args,
) -> Result<Vec<Solid>, KclError> {
@ -174,7 +185,20 @@ async fn inner_sweep(
)
.await?;
solids.push(do_post_extrude(sketch.clone(), id.into(), 0.0, exec_state, args.clone()).await?);
solids.push(
do_post_extrude(
sketch,
id.into(),
0.0,
&super::extrude::NamedCapTags {
start: tag_start.as_ref(),
end: tag_end.as_ref(),
},
exec_state,
&args,
)
.await?,
);
}
Ok(solids)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB