Compare commits

...

8 Commits

Author SHA1 Message Date
3fcdf845c1 merge main 2025-04-25 15:31:54 +01:00
b0594712f8 update std.json 2025-04-24 16:50:32 +01:00
a3821f6229 merge main 2025-04-24 16:31:30 +01:00
42082f72cd new simulation test for involute 2025-04-23 18:26:45 +01:00
55baf58682 redo docs 2025-04-23 18:20:58 +01:00
02bd211b70 fmt 2025-04-23 17:33:51 +01:00
37d34d45ff add check for end radius being larger 2025-04-23 17:29:09 +01:00
ed774e67b2 Add involute to Path info 2025-04-23 16:54:39 +01:00
17 changed files with 38296 additions and 2 deletions

File diff suppressed because it is too large Load Diff

View File

@ -51,6 +51,26 @@ An extruded arc.
| `sourceRange` |`[integer, integer, integer]`| The source range. | No | | `sourceRange` |`[integer, integer, integer]`| The source range. | No |
----
An extruded involute.
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `extrudeInvolute`| | No |
| `faceId` |[`string`](/docs/kcl/types/string)| The face id for the extrude surface. | No |
| [`tag`](/docs/kcl/types/tag) |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag. | No |
| `id` |[`string`](/docs/kcl/types/string)| The id of the geometry. | No |
| `sourceRange` |`[integer, integer, integer]`| The source range. | No |
---- ----
Geometry metadata. Geometry metadata.

View File

@ -241,6 +241,31 @@ A circular arc, not necessarily tangential to the current point.
---- ----
An involute of a circle of start_radius ending at end_radius
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `CircularInvolute`| | No |
| `start_radius` |[`number`](/docs/kcl/types/number)| The radius of the base circle of the involute | No |
| `end_radius` |[`number`](/docs/kcl/types/number)| The radius that the involute ends at | No |
| `angle` |[`number`](/docs/kcl/types/number)| Angle about which the whole involute is rotated | No |
| `reverse` |`boolean`| If true, the path segment starts at the end radius and goes towards the start radius | No |
| `from` |`[number, number]`| The from point. | No |
| `to` |`[number, number]`| The to point. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
| [`tag`](/docs/kcl/types/tag) |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag of the path. | No |
| `__geoMeta` |[`GeoMeta`](/docs/kcl/types/GeoMeta)| Metadata. | No |
----

View File

@ -1132,6 +1132,19 @@ pub enum Path {
/// True if the arc is counterclockwise. /// True if the arc is counterclockwise.
ccw: bool, ccw: bool,
}, },
/// An involute of a circle of start_radius ending at end_radius
CircularInvolute {
#[serde(flatten)]
base: BasePath,
/// The radius of the base circle of the involute
start_radius: f64,
/// The radius that the involute ends at
end_radius: f64,
/// Angle about which the whole involute is rotated
angle: f64,
/// If true, the path segment starts at the end radius and goes towards the start radius
reverse: bool,
},
} }
/// What kind of path is this? /// What kind of path is this?
@ -1146,6 +1159,7 @@ enum PathType {
Horizontal, Horizontal,
AngledLineTo, AngledLineTo,
Arc, Arc,
CircularInvolute,
} }
impl From<&Path> for PathType { impl From<&Path> for PathType {
@ -1161,6 +1175,7 @@ impl From<&Path> for PathType {
Path::Base { .. } => Self::Base, Path::Base { .. } => Self::Base,
Path::Arc { .. } => Self::Arc, Path::Arc { .. } => Self::Arc,
Path::ArcThreePoint { .. } => Self::Arc, Path::ArcThreePoint { .. } => Self::Arc,
Path::CircularInvolute { .. } => Self::CircularInvolute,
} }
} }
} }
@ -1178,6 +1193,7 @@ impl Path {
Path::CircleThreePoint { base, .. } => base.geo_meta.id, Path::CircleThreePoint { base, .. } => base.geo_meta.id,
Path::Arc { base, .. } => base.geo_meta.id, Path::Arc { base, .. } => base.geo_meta.id,
Path::ArcThreePoint { base, .. } => base.geo_meta.id, Path::ArcThreePoint { base, .. } => base.geo_meta.id,
Path::CircularInvolute { base, .. } => base.geo_meta.id,
} }
} }
@ -1193,6 +1209,7 @@ impl Path {
Path::CircleThreePoint { base, .. } => base.geo_meta.id = id, Path::CircleThreePoint { base, .. } => base.geo_meta.id = id,
Path::Arc { base, .. } => base.geo_meta.id = id, Path::Arc { base, .. } => base.geo_meta.id = id,
Path::ArcThreePoint { base, .. } => base.geo_meta.id = id, Path::ArcThreePoint { base, .. } => base.geo_meta.id = id,
Path::CircularInvolute { base, .. } => base.geo_meta.id = id,
} }
} }
@ -1208,6 +1225,7 @@ impl Path {
Path::CircleThreePoint { base, .. } => base.tag.clone(), Path::CircleThreePoint { base, .. } => base.tag.clone(),
Path::Arc { base, .. } => base.tag.clone(), Path::Arc { base, .. } => base.tag.clone(),
Path::ArcThreePoint { base, .. } => base.tag.clone(), Path::ArcThreePoint { base, .. } => base.tag.clone(),
Path::CircularInvolute { base, .. } => base.tag.clone(),
} }
} }
@ -1223,6 +1241,7 @@ impl Path {
Path::CircleThreePoint { base, .. } => base, Path::CircleThreePoint { base, .. } => base,
Path::Arc { base, .. } => base, Path::Arc { base, .. } => base,
Path::ArcThreePoint { base, .. } => base, Path::ArcThreePoint { base, .. } => base,
Path::CircularInvolute { base, .. } => base,
} }
} }
@ -1284,6 +1303,15 @@ impl Path {
// TODO: Call engine utils to figure this out. // TODO: Call engine utils to figure this out.
linear_distance(&self.get_base().from, &self.get_base().to) linear_distance(&self.get_base().from, &self.get_base().to)
} }
Self::CircularInvolute {
base: _,
start_radius,
end_radius,
..
} => {
let angle = (end_radius * end_radius - start_radius * start_radius).sqrt() / start_radius;
0.5 * start_radius * angle * angle
}
}; };
TyF64::new(n, self.get_base().units.into()) TyF64::new(n, self.get_base().units.into())
} }
@ -1300,6 +1328,7 @@ impl Path {
Path::CircleThreePoint { base, .. } => Some(base), Path::CircleThreePoint { base, .. } => Some(base),
Path::Arc { base, .. } => Some(base), Path::Arc { base, .. } => Some(base),
Path::ArcThreePoint { base, .. } => Some(base), Path::ArcThreePoint { base, .. } => Some(base),
Path::CircularInvolute { base, .. } => Some(base),
} }
} }
@ -1335,7 +1364,11 @@ impl Path {
radius: circle.radius, radius: circle.radius,
} }
} }
Path::ToPoint { .. } | Path::Horizontal { .. } | Path::AngledLineTo { .. } | Path::Base { .. } => { Path::CircularInvolute { .. }
| Path::ToPoint { .. }
| Path::Horizontal { .. }
| Path::AngledLineTo { .. }
| Path::Base { .. } => {
let base = self.get_base(); let base = self.get_base();
GetTangentialInfoFromPathsResult::PreviousPoint(base.from) GetTangentialInfoFromPathsResult::PreviousPoint(base.from)
} }
@ -1362,6 +1395,7 @@ pub enum ExtrudeSurface {
/// An extrude plane. /// An extrude plane.
ExtrudePlane(ExtrudePlane), ExtrudePlane(ExtrudePlane),
ExtrudeArc(ExtrudeArc), ExtrudeArc(ExtrudeArc),
ExtrudeInvolute(ExtrudeInvolute),
Chamfer(ChamferSurface), Chamfer(ChamferSurface),
Fillet(FilletSurface), Fillet(FilletSurface),
} }
@ -1422,11 +1456,26 @@ pub struct ExtrudeArc {
pub geo_meta: GeoMeta, pub geo_meta: GeoMeta,
} }
/// An extruded involute.
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct ExtrudeInvolute {
/// The face id for the extrude surface.
pub face_id: uuid::Uuid,
/// The tag.
pub tag: Option<Node<TagDeclarator>>,
/// Metadata.
#[serde(flatten)]
pub geo_meta: GeoMeta,
}
impl ExtrudeSurface { impl ExtrudeSurface {
pub fn get_id(&self) -> uuid::Uuid { pub fn get_id(&self) -> uuid::Uuid {
match self { match self {
ExtrudeSurface::ExtrudePlane(ep) => ep.geo_meta.id, ExtrudeSurface::ExtrudePlane(ep) => ep.geo_meta.id,
ExtrudeSurface::ExtrudeArc(ea) => ea.geo_meta.id, ExtrudeSurface::ExtrudeArc(ea) => ea.geo_meta.id,
ExtrudeSurface::ExtrudeInvolute(ea) => ea.geo_meta.id,
ExtrudeSurface::Fillet(f) => f.geo_meta.id, ExtrudeSurface::Fillet(f) => f.geo_meta.id,
ExtrudeSurface::Chamfer(c) => c.geo_meta.id, ExtrudeSurface::Chamfer(c) => c.geo_meta.id,
} }
@ -1436,6 +1485,7 @@ impl ExtrudeSurface {
match self { match self {
ExtrudeSurface::ExtrudePlane(ep) => ep.tag.clone(), ExtrudeSurface::ExtrudePlane(ep) => ep.tag.clone(),
ExtrudeSurface::ExtrudeArc(ea) => ea.tag.clone(), ExtrudeSurface::ExtrudeArc(ea) => ea.tag.clone(),
ExtrudeSurface::ExtrudeInvolute(ea) => ea.tag.clone(),
ExtrudeSurface::Fillet(f) => f.tag.clone(), ExtrudeSurface::Fillet(f) => f.tag.clone(),
ExtrudeSurface::Chamfer(c) => c.tag.clone(), ExtrudeSurface::Chamfer(c) => c.tag.clone(),
} }

View File

@ -2600,3 +2600,25 @@ mod multiple_foreign_imports_all_render {
super::execute(TEST_NAME, true).await super::execute(TEST_NAME, true).await
} }
} }
mod involute_fail {
const TEST_NAME: &str = "involute_fail";
/// Test parsing KCL.
#[test]
fn parse() {
super::parse(TEST_NAME)
}
/// Test that parsing and unparsing KCL produces the original KCL input.
#[tokio::test(flavor = "multi_thread")]
async fn unparse() {
super::unparse(TEST_NAME).await
}
/// Test that KCL is executed correctly.
#[tokio::test(flavor = "multi_thread")]
async fn kcl_test_execute() {
super::execute(TEST_NAME, true).await
}
}

View File

@ -713,6 +713,17 @@ impl Args {
None None
} }
} }
ExtrudeSurface::ExtrudeInvolute(extrude_involute) => {
if let Some(involute_tag) = &extrude_involute.tag {
if involute_tag.name == tag.value {
Some(Ok(extrude_involute.face_id))
} else {
None
}
} else {
None
}
}
ExtrudeSurface::Chamfer(chamfer) => { ExtrudeSurface::Chamfer(chamfer) => {
if let Some(chamfer_tag) = &chamfer.tag { if let Some(chamfer_tag) = &chamfer.tag {
if chamfer_tag.name == tag.value { if chamfer_tag.name == tag.value {

View File

@ -399,6 +399,17 @@ pub(crate) async fn do_post_extrude<'a>(
}); });
Some(extrude_surface) Some(extrude_surface)
} }
Path::CircularInvolute { .. } => {
let extrude_surface = ExtrudeSurface::ExtrudeInvolute(crate::execution::ExtrudeInvolute {
face_id: *actual_face_id,
tag: path.get_base().tag.clone(),
geo_meta: GeoMeta {
id: path.get_base().geo_meta.id,
metadata: path.get_base().geo_meta.metadata,
},
});
Some(extrude_surface)
}
} }
} else if no_engine_commands { } else if no_engine_commands {
// Only pre-populate the extrude surface if we are in mock mode. // Only pre-populate the extrude surface if we are in mock mode.

View File

@ -105,6 +105,18 @@ pub async fn involute_circular(exec_state: &mut ExecState, args: Args) -> Result
let angle: TyF64 = args.get_kw_arg_typed("angle", &RuntimeType::angle(), exec_state)?; let angle: TyF64 = args.get_kw_arg_typed("angle", &RuntimeType::angle(), exec_state)?;
let reverse = args.get_kw_arg_opt("reverse")?; let reverse = args.get_kw_arg_opt("reverse")?;
let tag = args.get_kw_arg_opt(NEW_TAG_KW)?; let tag = args.get_kw_arg_opt(NEW_TAG_KW)?;
if end_radius.n < start_radius.n {
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![args.source_range],
message: format!(
"endRadius: {0} cannot be less than startRadius: {1}",
end_radius.n, start_radius.n
)
.to_owned(),
}));
}
let new_sketch = inner_involute_circular( let new_sketch = inner_involute_circular(
sketch, sketch,
start_radius.n, start_radius.n,
@ -198,7 +210,7 @@ async fn inner_involute_circular(
end.x += from.x; end.x += from.x;
end.y += from.y; end.y += from.y;
let current_path = Path::ToPoint { let current_path = Path::CircularInvolute {
base: BasePath { base: BasePath {
from: from.ignore_units(), from: from.ignore_units(),
to: [end.x, end.y], to: [end.x, end.y],
@ -209,6 +221,10 @@ async fn inner_involute_circular(
metadata: args.source_range.into(), metadata: args.source_range.into(),
}, },
}, },
start_radius,
end_radius,
angle: angle.to_degrees(),
reverse: reverse.unwrap_or_default(),
}; };
let mut new_sketch = sketch.clone(); let mut new_sketch = sketch.clone();

View File

@ -0,0 +1,32 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Artifact commands involute_fail.kcl
---
[
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "edge_lines_visible",
"hidden": false
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "object_visible",
"object_id": "[uuid]",
"hidden": true
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "object_visible",
"object_id": "[uuid]",
"hidden": true
}
}
]

View File

@ -0,0 +1,6 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Artifact graph flowchart involute_fail.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,3 @@
```mermaid
flowchart LR
```

View File

@ -0,0 +1,290 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Result of parsing involute_fail.kcl
---
{
"Ok": {
"body": [
{
"commentStart": 0,
"end": 0,
"expression": {
"body": [
{
"arguments": [
{
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "XY",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name",
"type": "Name"
}
],
"callee": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "startSketchOn",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name"
},
"commentStart": 0,
"end": 0,
"start": 0,
"type": "CallExpression",
"type": "CallExpression"
},
{
"arguments": [
{
"commentStart": 0,
"elements": [
{
"commentStart": 0,
"end": 0,
"raw": "0",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 0.0,
"suffix": "None"
}
},
{
"commentStart": 0,
"end": 0,
"raw": "0",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 0.0,
"suffix": "None"
}
}
],
"end": 0,
"start": 0,
"type": "ArrayExpression",
"type": "ArrayExpression"
},
{
"commentStart": 0,
"end": 0,
"start": 0,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
}
],
"callee": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "startProfileAt",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name"
},
"commentStart": 0,
"end": 0,
"start": 0,
"type": "CallExpression",
"type": "CallExpression"
},
{
"arguments": [
{
"type": "LabeledArg",
"label": {
"commentStart": 0,
"end": 0,
"name": "length",
"start": 0,
"type": "Identifier"
},
"arg": {
"commentStart": 0,
"end": 0,
"raw": "5",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 5.0,
"suffix": "None"
}
}
}
],
"callee": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "xLine",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name"
},
"commentStart": 0,
"end": 0,
"start": 0,
"type": "CallExpressionKw",
"type": "CallExpressionKw",
"unlabeled": null
},
{
"arguments": [
{
"type": "LabeledArg",
"label": {
"commentStart": 0,
"end": 0,
"name": "startRadius",
"start": 0,
"type": "Identifier"
},
"arg": {
"commentStart": 0,
"end": 0,
"raw": "12",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 12.0,
"suffix": "None"
}
}
},
{
"type": "LabeledArg",
"label": {
"commentStart": 0,
"end": 0,
"name": "endRadius",
"start": 0,
"type": "Identifier"
},
"arg": {
"commentStart": 0,
"end": 0,
"raw": "10",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 10.0,
"suffix": "None"
}
}
},
{
"type": "LabeledArg",
"label": {
"commentStart": 0,
"end": 0,
"name": "angle",
"start": 0,
"type": "Identifier"
},
"arg": {
"commentStart": 0,
"end": 0,
"raw": "0",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 0.0,
"suffix": "None"
}
}
},
{
"type": "LabeledArg",
"label": {
"commentStart": 0,
"end": 0,
"name": "reverse",
"start": 0,
"type": "Identifier"
},
"arg": {
"commentStart": 0,
"end": 0,
"raw": "true",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": true
}
}
],
"callee": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "involuteCircular",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name"
},
"commentStart": 0,
"end": 0,
"start": 0,
"type": "CallExpressionKw",
"type": "CallExpressionKw",
"unlabeled": null
}
],
"commentStart": 0,
"end": 0,
"start": 0,
"type": "PipeExpression",
"type": "PipeExpression"
},
"start": 0,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
}
],
"commentStart": 0,
"end": 0,
"start": 0
}
}

View File

@ -0,0 +1,13 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Error from executing involute_fail.kcl
---
KCL Semantic error
× semantic: endRadius: 10 cannot be less than startRadius: 12
╭─[4:4]
3 │ |> xLine(length = 5)
4 │ |> involuteCircular(startRadius = 12, endRadius = 10, angle = 0, reverse = true)
· ──────────────────────────────────────┬──────────────────────────────────────
· ╰── tests/involute_fail/input.kcl
╰────

View File

@ -0,0 +1,4 @@
startSketchOn(XY)
|> startProfileAt([0, 0], %)
|> xLine(length = 5)
|> involuteCircular(startRadius = 12, endRadius = 10, angle = 0, reverse = true)

View File

@ -0,0 +1,21 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Operations executed involute_fail.kcl
---
[
{
"labeledArgs": {
"planeOrSolid": {
"value": {
"type": "Plane",
"artifact_id": "[uuid]"
},
"sourceRange": []
}
},
"name": "startSketchOn",
"sourceRange": [],
"type": "StdLibCall",
"unlabeledArg": null
}
]

View File

@ -0,0 +1,13 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Result of unparsing involute_fail.kcl
---
startSketchOn(XY)
|> startProfileAt([0, 0], %)
|> xLine(length = 5)
|> involuteCircular(
startRadius = 12,
endRadius = 10,
angle = 0,
reverse = true,
)

10706
yarn.lock Normal file

File diff suppressed because it is too large Load Diff