make kcl std lib first class (#1603)

* make kcl std lib first class

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

* fix tests

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

* u[dates

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
2024-03-01 14:23:30 -08:00
committed by GitHub
parent 9d8a7064da
commit b81c9d04cc
10 changed files with 1864 additions and 194 deletions

File diff suppressed because it is too large Load Diff

View File

@ -20,6 +20,7 @@
* [`atan`](#atan)
* [`bezierCurve`](#bezierCurve)
* [`ceil`](#ceil)
* [`circle`](#circle)
* [`close`](#close)
* [`cos`](#cos)
* [`e`](#e)
@ -3438,6 +3439,290 @@ ceil(num: number) -> number
### circle
Sketch a circle on the given plane
```
circle(plane: SketchData, center: [number, number], radius: number) -> SketchGroup
```
#### Arguments
* `plane`: `SketchData` - Data for start sketch on. You can start a sketch on a plane or an extrude group.
```
"XY" |
"-XY" |
"XZ" |
"-XZ" |
"YZ" |
"-YZ" |
{
plane: {
// Origin of the plane.
origin: {
x: number,
y: number,
z: number,
},
// What should the planes X axis be?
x_axis: {
x: number,
y: number,
z: number,
},
// What should the planes Y axis be?
y_axis: {
x: number,
y: number,
z: number,
},
// The z-axis (normal).
z_axis: {
x: number,
y: number,
z: number,
},
},
} |
{
// The id of the extrusion end cap
endCapId: uuid,
// The height of the extrude group.
height: number,
// The id of the extrude group.
id: uuid,
// The position of the extrude group.
position: [number, number, number],
// The rotation of the extrude group.
rotation: [number, number, number, number],
// The id of the extrusion start cap
startCapId: uuid,
// The extrude surfaces.
value: [{
// The face id for the extrude plane.
faceId: uuid,
// The id of the geometry.
id: uuid,
// The name.
name: string,
// The position.
position: [number, number, number],
// The rotation.
rotation: [number, number, number, number],
// The source range.
sourceRange: [number, number],
type: "extrudePlane",
} |
{
// The face id for the extrude plane.
faceId: uuid,
// The id of the geometry.
id: uuid,
// The name.
name: string,
// The position.
position: [number, number, number],
// The rotation.
rotation: [number, number, number, number],
// The source range.
sourceRange: [number, number],
type: "extrudeArc",
}],
// The x-axis of the extrude group base plane in the 3D space
xAxis: {
x: number,
y: number,
z: number,
},
// The y-axis of the extrude group base plane in the 3D space
yAxis: {
x: number,
y: number,
z: number,
},
// The z-axis of the extrude group base plane in the 3D space
zAxis: {
x: number,
y: number,
z: number,
},
}
```
* `center`: `[number, number]`
* `radius`: `number`
#### Returns
* `SketchGroup` - A sketch group is a collection of paths.
```
{
// The plane id or face id of the sketch group.
entityId: uuid,
// The id of the sketch group.
id: uuid,
// What the sketch is on (can be a plane or a face).
on: {
// The id of the plane.
id: uuid,
// Origin of the plane.
origin: {
x: number,
y: number,
z: number,
},
type: "plane",
// Type for a plane.
value: "XY" | "XZ" | "YZ" | "Custom",
// What should the planes X axis be?
xAxis: {
x: number,
y: number,
z: number,
},
// What should the planes Y axis be?
yAxis: {
x: number,
y: number,
z: number,
},
// The z-axis (normal).
zAxis: {
x: number,
y: number,
z: number,
},
} |
{
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.
sketchGroupId: uuid,
type: "face",
// The tag of the face.
value: string,
// What should the faces X axis be?
xAxis: {
x: number,
y: number,
z: number,
},
// What should the faces Y axis be?
yAxis: {
x: number,
y: number,
z: number,
},
// The z-axis (normal).
zAxis: {
x: number,
y: number,
z: number,
},
},
// The position of the sketch group.
position: [number, number, number],
// The rotation of the sketch group base plane.
rotation: [number, number, number, number],
// The starting path.
start: {
// The from point.
from: [number, number],
// The name of the path.
name: string,
// The to point.
to: [number, number],
},
// The paths in the sketch group.
value: [{
// The from point.
from: [number, number],
// The name of the path.
name: string,
// The to point.
to: [number, number],
type: "ToPoint",
} |
{
// arc's direction
ccw: string,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// The name of the path.
name: string,
// The to point.
to: [number, number],
type: "TangentialArcTo",
} |
{
// The from point.
from: [number, number],
// The name of the path.
name: string,
// The to point.
to: [number, number],
type: "TangentialArc",
} |
{
// The from point.
from: [number, number],
// The name of the path.
name: string,
// The to point.
to: [number, number],
type: "Horizontal",
// The x coordinate.
x: number,
} |
{
// The from point.
from: [number, number],
// The name of the path.
name: string,
// The to point.
to: [number, number],
type: "AngledLineTo",
// The x coordinate.
x: number,
// The y coordinate.
y: number,
} |
{
// The from point.
from: [number, number],
// The name of the path.
name: string,
// The to point.
to: [number, number],
type: "Base",
}],
// The x-axis of the sketch group base plane in the 3D space
xAxis: {
x: number,
y: number,
z: number,
},
// The y-axis of the sketch group base plane in the 3D space
yAxis: {
x: number,
y: number,
z: number,
},
// The z-axis of the sketch group base plane in the 3D space
zAxis: {
x: number,
y: number,
z: number,
},
}
```
### close
Close the current sketch.

View File

@ -636,8 +636,9 @@ impl LanguageServer for Backend {
/// Get completions from our stdlib.
pub fn get_completions_from_stdlib(stdlib: &crate::std::StdLib) -> Result<HashMap<String, CompletionItem>> {
let mut completions = HashMap::new();
let combined = stdlib.combined();
for internal_fn in stdlib.fns.values() {
for internal_fn in combined.values() {
completions.insert(internal_fn.name(), internal_fn.to_completion_item());
}
@ -652,8 +653,9 @@ pub fn get_completions_from_stdlib(stdlib: &crate::std::StdLib) -> Result<HashMa
/// Get signatures from our stdlib.
pub fn get_signatures_from_stdlib(stdlib: &crate::std::StdLib) -> Result<HashMap<String, SignatureHelp>> {
let mut signatures = HashMap::new();
let combined = stdlib.combined();
for internal_fn in stdlib.fns.values() {
for internal_fn in combined.values() {
signatures.insert(internal_fn.name(), internal_fn.to_signature_help());
}

View File

@ -4,18 +4,18 @@ expression: actual
---
{
"start": 0,
"end": 90,
"end": 74,
"body": [
{
"type": "VariableDeclaration",
"type": "VariableDeclaration",
"start": 0,
"end": 74,
"end": 58,
"declarations": [
{
"type": "VariableDeclarator",
"start": 6,
"end": 74,
"end": 58,
"id": {
"type": "Identifier",
"start": 6,
@ -26,47 +26,47 @@ expression: actual
"type": "PipeExpression",
"type": "PipeExpression",
"start": 17,
"end": 74,
"end": 58,
"body": [
{
"type": "CallExpression",
"type": "CallExpression",
"start": 17,
"end": 56,
"end": 40,
"callee": {
"type": "Identifier",
"start": 17,
"end": 39,
"name": "unstable_stdlib_circle"
"end": 23,
"name": "circle"
},
"arguments": [
{
"type": "Literal",
"type": "Literal",
"start": 40,
"end": 44,
"start": 24,
"end": 28,
"value": "XY",
"raw": "'XY'"
},
{
"type": "ArrayExpression",
"type": "ArrayExpression",
"start": 46,
"end": 51,
"start": 30,
"end": 35,
"elements": [
{
"type": "Literal",
"type": "Literal",
"start": 47,
"end": 48,
"start": 31,
"end": 32,
"value": 0,
"raw": "0"
},
{
"type": "Literal",
"type": "Literal",
"start": 49,
"end": 50,
"start": 33,
"end": 34,
"value": 0,
"raw": "0"
}
@ -75,8 +75,8 @@ expression: actual
{
"type": "Literal",
"type": "Literal",
"start": 53,
"end": 55,
"start": 37,
"end": 39,
"value": 22,
"raw": "22"
}
@ -86,28 +86,28 @@ expression: actual
{
"type": "CallExpression",
"type": "CallExpression",
"start": 60,
"end": 74,
"start": 44,
"end": 58,
"callee": {
"type": "Identifier",
"start": 60,
"end": 67,
"start": 44,
"end": 51,
"name": "extrude"
},
"arguments": [
{
"type": "Literal",
"type": "Literal",
"start": 68,
"end": 70,
"start": 52,
"end": 54,
"value": 14,
"raw": "14"
},
{
"type": "PipeSubstitution",
"type": "PipeSubstitution",
"start": 72,
"end": 73
"start": 56,
"end": 57
}
],
"optional": false
@ -125,25 +125,25 @@ expression: actual
{
"type": "ExpressionStatement",
"type": "ExpressionStatement",
"start": 75,
"end": 89,
"start": 59,
"end": 73,
"expression": {
"type": "CallExpression",
"type": "CallExpression",
"start": 75,
"end": 89,
"start": 59,
"end": 73,
"callee": {
"type": "Identifier",
"start": 75,
"end": 79,
"start": 59,
"end": 63,
"name": "show"
},
"arguments": [
{
"type": "Identifier",
"type": "Identifier",
"start": 80,
"end": 88,
"start": 64,
"end": 72,
"name": "cylinder"
}
],

View File

@ -11,6 +11,9 @@ pub trait KclStdLibFn: StdLibFn {
fn kcl_clone_box(&self) -> Box<dyn KclStdLibFn>;
fn function(&self) -> &FunctionExpression;
fn program(&self) -> &Program;
fn std_lib(&self) -> Box<dyn StdLibFn> {
self.clone_box()
}
}
impl ts_rs::TS for dyn KclStdLibFn {

View File

@ -133,6 +133,15 @@ impl StdLib {
Self { fns, kcl_fns }
}
// Get the combined hashmaps.
pub fn combined(&self) -> HashMap<String, Box<dyn StdLibFn>> {
let mut combined = self.fns.clone();
for (k, v) in self.kcl_fns.clone() {
combined.insert(k, v.std_lib());
}
combined
}
pub fn get(&self, name: &str) -> Option<Box<dyn StdLibFn>> {
self.fns.get(name).cloned()
}
@ -789,6 +798,7 @@ mod tests {
#[test]
fn test_generate_stdlib_markdown_docs() {
let stdlib = StdLib::new();
let combined = stdlib.combined();
let mut buf = String::new();
buf.push_str("<!--- DO NOT EDIT THIS FILE. IT IS AUTOMATICALLY GENERATED. -->\n\n");
@ -800,8 +810,8 @@ mod tests {
buf.push_str("* [Functions](#functions)\n");
for key in stdlib.fns.keys().sorted() {
let internal_fn = stdlib.fns.get(key).unwrap();
for key in combined.keys().sorted() {
let internal_fn = combined.get(key).unwrap();
if internal_fn.unpublished() || internal_fn.deprecated() {
continue;
}
@ -813,8 +823,8 @@ mod tests {
buf.push_str("## Functions\n\n");
for key in stdlib.fns.keys().sorted() {
let internal_fn = stdlib.fns.get(key).unwrap();
for key in combined.keys().sorted() {
let internal_fn = combined.get(key).unwrap();
if internal_fn.unpublished() {
continue;
}
@ -874,11 +884,12 @@ mod tests {
#[test]
fn test_generate_stdlib_json_schema() {
let stdlib = StdLib::new();
let combined = stdlib.combined();
let mut json_data = vec![];
for key in stdlib.fns.keys().sorted() {
let internal_fn = stdlib.fns.get(key).unwrap();
for key in combined.keys().sorted() {
let internal_fn = combined.get(key).unwrap();
json_data.push(internal_fn.to_json().unwrap());
}
expectorate::assert_contents(

View File

@ -48,7 +48,7 @@ impl std::fmt::Debug for Circle {
/// TODO: Parse the KCL in a macro and generate these
impl StdLibFn for Circle {
fn name(&self) -> String {
"unstable_stdlib_circle".to_owned()
"circle".to_owned()
}
fn summary(&self) -> String {
@ -64,15 +64,56 @@ impl StdLibFn for Circle {
}
fn args(&self) -> Vec<crate::docs::StdLibFnArg> {
Vec::new() // TODO
let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = true;
let mut generator = schemars::gen::SchemaGenerator::new(settings);
let mut args = Vec::new();
for parameter in &self.function.params {
match parameter.identifier.name.as_str() {
"plane" => {
args.push(crate::docs::StdLibFnArg {
name: parameter.identifier.name.to_owned(),
type_: "SketchData".to_string(),
schema: <crate::std::sketch::SketchData>::json_schema(&mut generator),
required: true,
});
}
"center" => {
args.push(crate::docs::StdLibFnArg {
name: parameter.identifier.name.to_owned(),
type_: "[number, number]".to_string(),
schema: <[f64; 2]>::json_schema(&mut generator),
required: true,
});
}
"radius" => {
args.push(crate::docs::StdLibFnArg {
name: parameter.identifier.name.to_owned(),
type_: "number".to_string(),
schema: <f64>::json_schema(&mut generator),
required: true,
});
}
_ => panic!("Unknown parameter: {:?}", parameter.identifier.name),
}
}
args
}
fn return_value(&self) -> Option<crate::docs::StdLibFnArg> {
None
let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = true;
let mut generator = schemars::gen::SchemaGenerator::new(settings);
Some(crate::docs::StdLibFnArg {
name: "SketchGroup".to_owned(),
type_: "SketchGroup".to_string(),
schema: <crate::executor::SketchGroup>::json_schema(&mut generator),
required: true,
})
}
fn unpublished(&self) -> bool {
true
false
}
fn deprecated(&self) -> bool {

View File

@ -1,2 +1,2 @@
const cylinder = unstable_stdlib_circle('XY', [0,0], 22) |> extrude(14, %)
const cylinder = circle('XY', [0,0], 22) |> extrude(14, %)
show(cylinder)

View File

@ -602,23 +602,14 @@ const part004 = startSketchOn('YZ')
#[tokio::test(flavor = "multi_thread")]
async fn serial_test_holes() {
let code = r#"fn circle = (pos, radius) => {
const sg = startSketchOn('XY')
|> startProfileAt(pos, %)
|> arc({angle_end: 360, angle_start: 0, radius: radius}, %)
|> close(%)
return sg
}
const square = startSketchOn('XY')
let code = r#"const square = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line([0, 10], %)
|> line([10, 0], %)
|> line([0, -10], %)
|> close(%)
|> hole(circle([2, 2], .5), %)
|> hole(circle([2, 8], .5), %)
|> hole(circle('XY', [2, 2], .5), %)
|> hole(circle('XY', [2, 8], .5), %)
|> extrude(2, %)
"#;
@ -631,7 +622,7 @@ const square = startSketchOn('XY')
#[tokio::test(flavor = "multi_thread")]
async fn optional_params() {
let code = r#"
fn circle = (pos, radius, tag?) => {
fn other_circle = (pos, radius, tag?) => {
const sg = startSketchOn('XY')
|> startProfileAt(pos, %)
|> arc({angle_end: 360, angle_start: 0, radius: radius}, %)
@ -640,7 +631,7 @@ async fn optional_params() {
return sg
}
const thing = circle([2, 2], 20)
const thing = other_circle([2, 2], 20)
"#;
let result = execute_and_snapshot(code, kittycad::types::UnitLength::Mm)
.await
@ -650,19 +641,7 @@ const thing = circle([2, 2], 20)
#[tokio::test(flavor = "multi_thread")]
async fn serial_test_rounded_with_holes() {
let code = r#"fn circle = (pos, radius) => {
const sg = startSketchOn('XY')
|> startProfileAt([pos[0] + radius, pos[1]], %)
|> arc({
angle_end: 360,
angle_start: 0,
radius: radius
}, %)
|> close(%)
return sg
}
fn tarc = (to, sketchGroup, tag?) => {
let code = r#"fn tarc = (to, sketchGroup, tag?) => {
return tangentialArcTo(to, sketchGroup, tag)
}
@ -685,10 +664,10 @@ const holeRadius = 1
const holeIndex = 6
const part = roundedRectangle([0, 0], 20, 20, 4)
|> hole(circle([-holeIndex, holeIndex], holeRadius), %)
|> hole(circle([holeIndex, holeIndex], holeRadius), %)
|> hole(circle([-holeIndex, -holeIndex], holeRadius), %)
|> hole(circle([holeIndex, -holeIndex], holeRadius), %)
|> hole(circle('XY', [-holeIndex, holeIndex], holeRadius), %)
|> hole(circle('XY', [holeIndex, holeIndex], holeRadius), %)
|> hole(circle('XY', [-holeIndex, -holeIndex], holeRadius), %)
|> hole(circle('XY', [holeIndex, -holeIndex], holeRadius), %)
|> extrude(2, %)
"#;
@ -700,19 +679,7 @@ const part = roundedRectangle([0, 0], 20, 20, 4)
#[tokio::test(flavor = "multi_thread")]
async fn serial_test_top_level_expression() {
let code = r#"fn circle = (pos, radius) => {
const sg = startSketchOn('XY')
|> startProfileAt([pos[0] + radius, pos[1]], %)
|> arc({
angle_end: 360,
angle_start: 0,
radius: radius
}, %)
|> close(%)
return sg
}
circle([0,0], 22) |> extrude(14, %)"#;
let code = r#"circle('XY', [0,0], 22) |> extrude(14, %)"#;
let result = execute_and_snapshot(code, kittycad::types::UnitLength::Mm)
.await
@ -722,19 +689,7 @@ circle([0,0], 22) |> extrude(14, %)"#;
#[tokio::test(flavor = "multi_thread")]
async fn serial_test_patterns_linear_basic() {
let code = r#"fn circle = (pos, radius) => {
const sg = startSketchOn('XY')
|> startProfileAt([pos[0] + radius, pos[1]], %)
|> arc({
angle_end: 360,
angle_start: 0,
radius: radius
}, %)
|> close(%)
return sg
}
const part = circle([0,0], 2)
let code = r#"const part = circle('XY', [0,0], 2)
|> patternLinear({axis: [0,1], repetitions: 12, distance: 2}, %)
"#;
@ -746,19 +701,7 @@ const part = circle([0,0], 2)
#[tokio::test(flavor = "multi_thread")]
async fn serial_test_patterns_linear_basic_3d() {
let code = r#"fn circle = (pos, radius) => {
const sg = startSketchOn('XY')
|> startProfileAt([pos[0] + radius, pos[1]], %)
|> arc({
angle_end: 360,
angle_start: 0,
radius: radius
}, %)
|> close(%)
return sg
}
const part = startSketchOn('XY')
let code = r#"const part = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line([0,1], %)
|> line([1, 0], %)
@ -776,19 +719,7 @@ const part = startSketchOn('XY')
#[tokio::test(flavor = "multi_thread")]
async fn serial_test_patterns_linear_basic_negative_distance() {
let code = r#"fn circle = (pos, radius) => {
const sg = startSketchOn('XY')
|> startProfileAt([pos[0] + radius, pos[1]], %)
|> arc({
angle_end: 360,
angle_start: 0,
radius: radius
}, %)
|> close(%)
return sg
}
const part = circle([0,0], 2)
let code = r#"const part = circle('XY', [0,0], 2)
|> patternLinear({axis: [0,1], repetitions: 12, distance: -2}, %)
"#;
@ -804,19 +735,7 @@ const part = circle([0,0], 2)
#[tokio::test(flavor = "multi_thread")]
async fn serial_test_patterns_linear_basic_negative_axis() {
let code = r#"fn circle = (pos, radius) => {
const sg = startSketchOn('XY')
|> startProfileAt([pos[0] + radius, pos[1]], %)
|> arc({
angle_end: 360,
angle_start: 0,
radius: radius
}, %)
|> close(%)
return sg
}
const part = circle([0,0], 2)
let code = r#"const part = circle('XY', [0,0], 2)
|> patternLinear({axis: [0,-1], repetitions: 12, distance: 2}, %)
"#;
@ -832,19 +751,7 @@ const part = circle([0,0], 2)
#[tokio::test(flavor = "multi_thread")]
async fn serial_test_patterns_linear_basic_holes() {
let code = r#"fn circle = (pos, radius) => {
const sg = startSketchOn('XY')
|> startProfileAt([pos[0] + radius, pos[1]], %)
|> arc({
angle_end: 360,
angle_start: 0,
radius: radius
}, %)
|> close(%)
return sg
}
const circles = circle([5, 5], 1)
let code = r#"const circles = circle('XY', [5, 5], 1)
|> patternLinear({axis: [1,1], repetitions: 12, distance: 3}, %)
const rectangle = startSketchOn('XY')
@ -865,19 +772,7 @@ const rectangle = startSketchOn('XY')
#[tokio::test(flavor = "multi_thread")]
async fn serial_test_patterns_circular_basic_2d() {
let code = r#"fn circle = (pos, radius) => {
const sg = startSketchOn('XY')
|> startProfileAt([pos[0] + radius, pos[1]], %)
|> arc({
angle_end: 360,
angle_start: 0,
radius: radius
}, %)
|> close(%)
return sg
}
const part = circle([0,0], 2)
let code = r#"const part = circle('XY', [0,0], 2)
|> patternCircular({axis: [0,1], center: [20, 20, 20], repetitions: 12, arcDegrees: 210, rotateDuplicates: true}, %)
"#;
@ -889,19 +784,7 @@ const part = circle([0,0], 2)
#[tokio::test(flavor = "multi_thread")]
async fn serial_test_patterns_circular_basic_3d() {
let code = r#"fn circle = (pos, radius) => {
const sg = startSketchOn('XY')
|> startProfileAt([pos[0] + radius, pos[1]], %)
|> arc({
angle_end: 360,
angle_start: 0,
radius: radius
}, %)
|> close(%)
return sg
}
const part = startSketchOn('XY')
let code = r#"const part = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line([0,1], %)
|> line([1, 0], %)
@ -919,19 +802,7 @@ const part = startSketchOn('XY')
#[tokio::test(flavor = "multi_thread")]
async fn serial_test_patterns_circular_3d_tilted_axis() {
let code = r#"fn circle = (pos, radius) => {
const sg = startSketchOn('XY')
|> startProfileAt([pos[0] + radius, pos[1]], %)
|> arc({
angle_end: 360,
angle_start: 0,
radius: radius
}, %)
|> close(%)
return sg
}
const part = startSketchOn('XY')
let code = r#"const part = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line([0,1], %)
|> line([1, 0], %)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB