diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 21cfac5ade..879269cb00 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -262,7 +262,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-14] - timeout-minutes: 40 + timeout-minutes: 60 runs-on: ${{ matrix.os }} needs: check-rust-changes steps: diff --git a/docs/kcl/angleToMatchLengthX.md b/docs/kcl/angleToMatchLengthX.md index e666283df5..f66c1d9dbb 100644 --- a/docs/kcl/angleToMatchLengthX.md +++ b/docs/kcl/angleToMatchLengthX.md @@ -270,6 +270,26 @@ const extrusion = extrude(5, sketch001) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -479,6 +499,26 @@ const extrusion = extrude(5, sketch001) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], diff --git a/docs/kcl/angleToMatchLengthY.md b/docs/kcl/angleToMatchLengthY.md index 20243dab08..3ebb5278e3 100644 --- a/docs/kcl/angleToMatchLengthY.md +++ b/docs/kcl/angleToMatchLengthY.md @@ -274,6 +274,26 @@ const extrusion = extrude(5, sketch001) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -483,6 +503,26 @@ const extrusion = extrude(5, sketch001) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], diff --git a/docs/kcl/angledLine.md b/docs/kcl/angledLine.md index 432fcb642e..f5db4221da 100644 --- a/docs/kcl/angledLine.md +++ b/docs/kcl/angledLine.md @@ -189,6 +189,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -398,6 +418,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -609,6 +649,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -818,6 +878,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], diff --git a/docs/kcl/angledLineOfXLength.md b/docs/kcl/angledLineOfXLength.md index 2ff7573e98..d34cddd640 100644 --- a/docs/kcl/angledLineOfXLength.md +++ b/docs/kcl/angledLineOfXLength.md @@ -188,6 +188,26 @@ const extrusion = extrude(10, sketch001) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -397,6 +417,26 @@ const extrusion = extrude(10, sketch001) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -608,6 +648,26 @@ const extrusion = extrude(10, sketch001) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -817,6 +877,26 @@ const extrusion = extrude(10, sketch001) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], diff --git a/docs/kcl/angledLineOfYLength.md b/docs/kcl/angledLineOfYLength.md index dd87e73447..5e15b2d4b4 100644 --- a/docs/kcl/angledLineOfYLength.md +++ b/docs/kcl/angledLineOfYLength.md @@ -190,6 +190,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -399,6 +419,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -610,6 +650,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -819,6 +879,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], diff --git a/docs/kcl/angledLineThatIntersects.md b/docs/kcl/angledLineThatIntersects.md index 9e039ef234..92a527010d 100644 --- a/docs/kcl/angledLineThatIntersects.md +++ b/docs/kcl/angledLineThatIntersects.md @@ -282,6 +282,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -491,6 +511,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -702,6 +742,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -911,6 +971,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], diff --git a/docs/kcl/angledLineToX.md b/docs/kcl/angledLineToX.md index 27c299817b..082017d084 100644 --- a/docs/kcl/angledLineToX.md +++ b/docs/kcl/angledLineToX.md @@ -187,6 +187,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -396,6 +416,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -607,6 +647,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -816,6 +876,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], diff --git a/docs/kcl/angledLineToY.md b/docs/kcl/angledLineToY.md index ffc1f0fae0..171822fde5 100644 --- a/docs/kcl/angledLineToY.md +++ b/docs/kcl/angledLineToY.md @@ -187,6 +187,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -396,6 +416,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -607,6 +647,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -816,6 +876,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], diff --git a/docs/kcl/arc.md b/docs/kcl/arc.md index ac9fca5612..faf8f55753 100644 --- a/docs/kcl/arc.md +++ b/docs/kcl/arc.md @@ -200,6 +200,26 @@ const exampleSketch = startSketchOn('XZ') to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -409,6 +429,26 @@ const exampleSketch = startSketchOn('XZ') to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -620,6 +660,26 @@ const exampleSketch = startSketchOn('XZ') to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -829,6 +889,26 @@ const exampleSketch = startSketchOn('XZ') to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], diff --git a/docs/kcl/bezierCurve.md b/docs/kcl/bezierCurve.md index 9db9871a97..49ac9d1496 100644 --- a/docs/kcl/bezierCurve.md +++ b/docs/kcl/bezierCurve.md @@ -193,6 +193,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -402,6 +422,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -613,6 +653,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -822,6 +882,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], diff --git a/docs/kcl/chamfer.md b/docs/kcl/chamfer.md index 29551c4f76..bce8ae9bad 100644 --- a/docs/kcl/chamfer.md +++ b/docs/kcl/chamfer.md @@ -415,6 +415,26 @@ const mountingPlate = extrude(thickness, mountingPlateSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -819,6 +839,26 @@ const mountingPlate = extrude(thickness, mountingPlateSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], diff --git a/e2e/playwright/editor-tests.spec.ts b/e2e/playwright/editor-tests.spec.ts index 82c5e84886..f33e5d190f 100644 --- a/e2e/playwright/editor-tests.spec.ts +++ b/e2e/playwright/editor-tests.spec.ts @@ -558,7 +558,7 @@ test.describe('Editor tests', () => { await page.keyboard.press('ArrowDown') await page.keyboard.press('Enter') await page.keyboard.type(`const extrusion = startSketchOn('XY') - |> circle([0, 0], dia/2, %) + |> circle({ center: [0, 0], radius: dia/2 }, %) |> hole(squareHole(length, width, height), %) |> extrude(height, %)`) diff --git a/e2e/playwright/sketch-tests.spec.ts b/e2e/playwright/sketch-tests.spec.ts index 0d15c7dd14..1b90cbad0e 100644 --- a/e2e/playwright/sketch-tests.spec.ts +++ b/e2e/playwright/sketch-tests.spec.ts @@ -149,14 +149,16 @@ test.describe('Sketch tests', () => { await page.getByRole('button', { name: 'line Line', exact: true }).click() await page.waitForTimeout(100) - await page.mouse.click(700, 200) + await expect(async () => { + await page.mouse.click(700, 200) - await expect.poll(u.normalisedEditorCode) - .toBe(`const sketch001 = startSketchOn('XZ') + await expect.poll(u.normalisedEditorCode, { timeout: 1000 }) + .toBe(`const sketch001 = startSketchOn('XZ') |> startProfileAt([12.34, -12.34], %) |> line([-12.34, 12.34], %) `) + }).toPass({ timeout: 40_000, intervals: [1_000] }) }) test('Can exit selection of face', async ({ page }) => { // Load the app with the code panes @@ -344,6 +346,92 @@ test.describe('Sketch tests', () => { }) }) + test('Can edit a circle center and radius by dragging its handles', async ({ + page, + }) => { + const u = await getUtils(page) + await page.addInitScript(async () => { + localStorage.setItem( + 'persistCode', + `const sketch001 = startSketchOn('XZ') + |> circle({ center: [4.61, -5.01], radius: 8 }, %)` + ) + }) + + await page.setViewportSize({ width: 1200, height: 500 }) + + await u.waitForAuthSkipAppStart() + await expect( + page.getByRole('button', { name: 'Start Sketch' }) + ).not.toBeDisabled() + + await page.waitForTimeout(100) + await u.openAndClearDebugPanel() + await u.sendCustomCmd({ + type: 'modeling_cmd_req', + cmd_id: uuidv4(), + cmd: { + type: 'default_camera_look_at', + vantage: { x: 0, y: -1250, z: 580 }, + center: { x: 0, y: 0, z: 0 }, + up: { x: 0, y: 0, z: 1 }, + }, + }) + await page.waitForTimeout(100) + await u.sendCustomCmd({ + type: 'modeling_cmd_req', + cmd_id: uuidv4(), + cmd: { + type: 'default_camera_get_settings', + }, + }) + await page.waitForTimeout(100) + + const startPX = [667, 325] + + const dragPX = 40 + + await page + .getByText('circle({ center: [4.61, -5.01], radius: 8 }, %)') + .click() + await expect( + page.getByRole('button', { name: 'Edit Sketch' }) + ).toBeVisible() + await page.getByRole('button', { name: 'Edit Sketch' }).click() + await page.waitForTimeout(400) + let prevContent = await page.locator('.cm-content').innerText() + + await expect(page.getByTestId('segment-overlay')).toHaveCount(1) + + await test.step('drag circle center handle', async () => { + await page.dragAndDrop('#stream', '#stream', { + sourcePosition: { x: startPX[0], y: startPX[1] }, + targetPosition: { x: startPX[0] + dragPX, y: startPX[1] - dragPX }, + }) + await page.waitForTimeout(100) + await expect(page.locator('.cm-content')).not.toHaveText(prevContent) + prevContent = await page.locator('.cm-content').innerText() + }) + + await test.step('drag circle radius handle', async () => { + await page.waitForTimeout(100) + + const lineEnd = await u.getBoundingBox('[data-overlay-index="0"]') + await page.waitForTimeout(100) + await page.dragAndDrop('#stream', '#stream', { + sourcePosition: { x: lineEnd.x - 5, y: lineEnd.y }, + targetPosition: { x: lineEnd.x + dragPX, y: lineEnd.y + dragPX }, + }) + await expect(page.locator('.cm-content')).not.toHaveText(prevContent) + prevContent = await page.locator('.cm-content').innerText() + }) + + // expect the code to have changed + await expect(page.locator('.cm-content')) + .toHaveText(`const sketch001 = startSketchOn('XZ') + |> circle({ center: [7.26, -2.37], radius: 11.79 }, %) +`) + }) test('Can edit a sketch that has been extruded in the same pipe', async ({ page, }) => { diff --git a/e2e/playwright/snapshot-tests.spec.ts b/e2e/playwright/snapshot-tests.spec.ts index 9aaeda388b..c153678941 100644 --- a/e2e/playwright/snapshot-tests.spec.ts +++ b/e2e/playwright/snapshot-tests.spec.ts @@ -532,6 +532,64 @@ test( }) } ) +test( + 'Draft circle should look right', + { tag: '@snapshot' }, + async ({ page, context }) => { + // FIXME: Skip on macos its being weird. + // test.skip(process.platform === 'darwin', 'Skip on macos') + + const u = await getUtils(page) + await page.setViewportSize({ width: 1200, height: 500 }) + const PUR = 400 / 37.5 //pixeltoUnitRatio + + await u.waitForAuthSkipAppStart() + await u.openDebugPanel() + + await expect( + page.getByRole('button', { name: 'Start Sketch' }) + ).not.toBeDisabled() + await expect( + page.getByRole('button', { name: 'Start Sketch' }) + ).toBeVisible() + + // click on "Start Sketch" button + await u.clearCommandLogs() + await u.doAndWaitForImageDiff( + () => page.getByRole('button', { name: 'Start Sketch' }).click(), + 200 + ) + + // select a plane + await page.mouse.click(700, 200) + + await expect(page.locator('.cm-content')).toHaveText( + `const sketch001 = startSketchOn('XZ')` + ) + + await page.waitForTimeout(500) // TODO detect animation ending, or disable animation + await u.closeDebugPanel() + + const startXPx = 600 + + // Equip the rectangle tool + // await page.getByRole('button', { name: 'line Line', exact: true }).click() + await page.getByTestId('circle-center').click() + + // Draw the rectangle + await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20) + await page.mouse.move(startXPx + PUR * 10, 500 - PUR * 10, { steps: 5 }) + + // Ensure the draft rectangle looks the same as it usually does + await expect(page).toHaveScreenshot({ + maxDiffPixels: 100, + }) + await expect(page.locator('.cm-content')).toHaveText( + `const sketch001 = startSketchOn('XZ') + |> circle({ center: [14.44, -2.44], radius: 1 }, %)` + ) + } +) test.describe( 'Client side scene scale should match engine scale', diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-circle-should-look-right-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-circle-should-look-right-1-Google-Chrome-linux.png new file mode 100644 index 0000000000..41c6a71bd2 Binary files /dev/null and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-circle-should-look-right-1-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-circle-should-look-right-1-Google-Chrome-win32.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-circle-should-look-right-1-Google-Chrome-win32.png new file mode 100644 index 0000000000..fc059e0497 Binary files /dev/null and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-circle-should-look-right-1-Google-Chrome-win32.png differ diff --git a/e2e/playwright/storageStates.ts b/e2e/playwright/storageStates.ts index 6cc85bfdce..61259fbcbb 100644 --- a/e2e/playwright/storageStates.ts +++ b/e2e/playwright/storageStates.ts @@ -365,10 +365,10 @@ const box = startSketchOn('XY') svg(startSketchOn(keychain, 'end'), [-33, 32], -thickness) startSketchOn(keychain, 'end') - |> circle([ + |> circle({ center: [ width / 2, height - (keychainHoleSize + 1.5) - ], keychainHoleSize, %) + ], radius: keychainHoleSize }, %) |> extrude(-thickness, %)` export const TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR = `const thing = 1` diff --git a/e2e/playwright/testing-segment-overlays.spec.ts b/e2e/playwright/testing-segment-overlays.spec.ts index 2a08b2a9cf..f7cc286eea 100644 --- a/e2e/playwright/testing-segment-overlays.spec.ts +++ b/e2e/playwright/testing-segment-overlays.spec.ts @@ -774,6 +774,80 @@ const part001 = startSketchOn('XZ') locator: '[data-overlay-toolbar-index="12"]', }) }) + test('for segment [circle]', async ({ page }) => { + await page.addInitScript(async () => { + localStorage.setItem( + 'persistCode', + `const part001 = startSketchOn('XZ') + |> circle({ center: [1 + 0, 0], radius: 8 }, %) + ` + ) + localStorage.setItem('disableAxis', 'true') + }) + const u = await getUtils(page) + await page.setViewportSize({ width: 1200, height: 500 }) + + await u.waitForAuthSkipAppStart() + + // wait for execution done + await u.openDebugPanel() + await u.expectCmdLog('[data-message-type="execution-done"]') + await u.closeDebugPanel() + + await page + .getByText('circle({ center: [1 + 0, 0], radius: 8 }, %)') + .click() + await page.waitForTimeout(100) + await page.getByRole('button', { name: 'Edit Sketch' }).click() + await page.waitForTimeout(500) + + await expect(page.getByTestId('segment-overlay')).toHaveCount(1) + + const clickUnconstrained = _clickUnconstrained(page) + const clickConstrained = _clickConstrained(page) + + const hoverPos = { x: 789, y: 114 } as const + let ang = await u.getAngle('[data-overlay-index="0"]') + console.log('angl', ang) + console.log('circle center x') + await clickConstrained({ + hoverPos, + constraintType: 'xAbsolute', + expectBeforeUnconstrained: + 'circle({ center: [1 + 0, 0], radius: 8 }, %)', + expectAfterUnconstrained: 'circle({ center: [1, 0], radius: 8 }, %)', + expectFinal: 'circle({ center: [xAbs001, 0], radius: 8 }, %)', + ang: ang + 105, + steps: 6, + locator: '[data-overlay-toolbar-index="0"]', + }) + console.log('circle center y') + await clickUnconstrained({ + hoverPos, + constraintType: 'yAbsolute', + expectBeforeUnconstrained: + 'circle({ center: [xAbs001, 0], radius: 8 }, %)', + expectAfterUnconstrained: + 'circle({ center: [xAbs001, yAbs001], radius: 8 }, %)', + expectFinal: 'circle({ center: [xAbs001, 0], radius: 8 }, %)', + ang: ang + 105, + steps: 10, + locator: '[data-overlay-toolbar-index="0"]', + }) + console.log('circle radius') + await clickUnconstrained({ + hoverPos, + constraintType: 'radius', + expectBeforeUnconstrained: + 'circle({ center: [xAbs001, 0], radius: 8 }, %)', + expectAfterUnconstrained: + 'circle({ center: [xAbs001, 0], radius: radius001 }, %)', + expectFinal: 'circle({ center: [xAbs001, 0], radius: 8 }, %)', + ang: ang + 105, + steps: 10, + locator: '[data-overlay-toolbar-index="0"]', + }) + }) }) test.describe('Testing deleting a segment', () => { const _deleteSegmentSequence = diff --git a/src/clientSideScene/ClientSideSceneComp.tsx b/src/clientSideScene/ClientSideSceneComp.tsx index 7905eccfe6..c1ddec1e3e 100644 --- a/src/clientSideScene/ClientSideSceneComp.tsx +++ b/src/clientSideScene/ClientSideSceneComp.tsx @@ -124,7 +124,8 @@ export const ClientSideScene = ({ } else if ( state.matches({ Sketch: 'Line tool' }) || state.matches({ Sketch: 'Tangential arc to' }) || - state.matches({ Sketch: 'Rectangle tool' }) + state.matches({ Sketch: 'Rectangle tool' }) || + state.matches({ Sketch: 'Circle tool' }) ) { cursor = 'crosshair' } else { @@ -512,6 +513,11 @@ const ConstraintSymbol = ({ displayName: 'Intersection Offset', iconName: 'intersection-offset', }, + radius: { + varName: 'radius', + displayName: 'Radius', + iconName: 'dimension', + }, // implicit constraints vertical: { diff --git a/src/clientSideScene/sceneEntities.ts b/src/clientSideScene/sceneEntities.ts index 1f16c10d6b..45e09202f1 100644 --- a/src/clientSideScene/sceneEntities.ts +++ b/src/clientSideScene/sceneEntities.ts @@ -62,6 +62,7 @@ import { import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst' import { executeAst } from 'lang/langHelpers' import { + circleSegment, createArcGeometry, dashedStraight, createProfileStartHandle, @@ -72,6 +73,7 @@ import { addCallExpressionsToPipe, addCloseToPipe, addNewSketchLn, + changeCircleArguments, changeSketchArguments, updateStartProfileAtArgs, } from 'lang/std/sketch' @@ -87,6 +89,7 @@ import { createArrayExpression, createCallExpressionStdLib, createLiteral, + createObjectExpression, createPipeExpression, createPipeSubstitution, findUniqueName, @@ -119,10 +122,18 @@ export const TANGENTIAL_ARC_TO__SEGMENT_DASH = 'tangential-arc-to-segment-body-dashed' export const TANGENTIAL_ARC_TO_SEGMENT = 'tangential-arc-to-segment' export const TANGENTIAL_ARC_TO_SEGMENT_BODY = 'tangential-arc-to-segment-body' +export const CIRCLE_SEGMENT = 'circle-segment' +export const CIRCLE_SEGMENT_BODY = 'circle-segment-body' +export const CIRCLE_SEGMENT_DASH = 'circle-segment-body-dashed' +export const CIRCLE_CENTER_HANDLE = 'circle-center-handle' export const SEGMENT_WIDTH_PX = 1.6 export const HIDE_SEGMENT_LENGTH = 75 // in pixels export const HIDE_HOVER_SEGMENT_LENGTH = 60 // in pixels -export const SEGMENT_BODIES = [STRAIGHT_SEGMENT, TANGENTIAL_ARC_TO_SEGMENT] +export const SEGMENT_BODIES = [ + STRAIGHT_SEGMENT, + TANGENTIAL_ARC_TO_SEGMENT, + CIRCLE_SEGMENT, +] export const SEGMENT_BODIES_PLUS_PROFILE_START = [ ...SEGMENT_BODIES, PROFILE_START, @@ -191,6 +202,26 @@ export class SceneEntities { }) ) } + if ( + segment.userData.from && + segment.userData.to && + segment.userData.center && + segment.userData.radius && + segment.userData.type === CIRCLE_SEGMENT + ) { + callbacks.push( + this.updateCircleSegment({ + prevSegment: segment.userData.prevSegment, + from: segment.userData.from, + to: segment.userData.to, + center: segment.userData.center, + radius: segment.userData.radius, + group: segment, + scale: factor, + }) + ) + } + if (segment.name === PROFILE_START) { segment.scale.set(factor, factor, factor) } @@ -427,19 +458,21 @@ export class SceneEntities { maybeModdedAst, sketchGroup.start.__geoMeta.sourceRange ) - const _profileStart = createProfileStartHandle({ - from: sketchGroup.start.from, - id: sketchGroup.start.__geoMeta.id, - pathToNode: segPathToNode, - scale: factor, - theme: sceneInfra._theme, - }) - _profileStart.layers.set(SKETCH_LAYER) - _profileStart.traverse((child) => { - child.layers.set(SKETCH_LAYER) - }) - group.add(_profileStart) - this.activeSegments[JSON.stringify(segPathToNode)] = _profileStart + if (sketchGroup?.value?.[0].type !== 'Circle') { + const _profileStart = createProfileStartHandle({ + from: sketchGroup.start.from, + id: sketchGroup.start.__geoMeta.id, + pathToNode: segPathToNode, + scale: factor, + theme: sceneInfra._theme, + }) + _profileStart.layers.set(SKETCH_LAYER) + _profileStart.traverse((child) => { + child.layers.set(SKETCH_LAYER) + }) + group.add(_profileStart) + this.activeSegments[JSON.stringify(segPathToNode)] = _profileStart + } const callbacks: (() => SegmentOverlayPayload | null)[] = [] sketchGroup.value.forEach((segment, index) => { let segPathToNode = getNodePathFromSourceRange( @@ -504,6 +537,32 @@ export class SceneEntities { scale: factor, }) ) + } else if (segment.type === 'Circle') { + seg = circleSegment({ + prevSegment: sketchGroup.value[index - 1], + from: segment.from, + to: segment.to, + center: segment.center, + radius: segment.radius, + id: segment.__geoMeta.id, + pathToNode: segPathToNode, + isDraftSegment, + scale: factor, + texture: sceneInfra.extraSegmentTexture, + theme: sceneInfra._theme, + isSelected, + }) + callbacks.push( + this.updateCircleSegment({ + prevSegment: sketchGroup.value[index - 1], + from: segment.from, + to: segment.to, + center: segment.center, + radius: segment.radius, + group: seg, + scale: factor, + }) + ) } else { seg = straightSegment({ from: segment.from, @@ -612,7 +671,6 @@ export class SceneEntities { const lastSeg = sg?.value?.slice(-1)[0] || sg.start const index = sg.value.length // because we've added a new segment that's not in the memory yet, no need for `-1` - const mod = addNewSketchLn({ node: _ast, programMemory: kclManager.programMemory, @@ -759,6 +817,11 @@ export class SceneEntities { const startSketchOn = _node1.node?.declarations const startSketchOnInit = startSketchOn?.[0]?.init + const sg = sketchGroupFromKclValue( + kclManager.programMemory.get(variableDeclarationName), + variableDeclarationName + ) + if (err(sg)) return sg const tags: [string, string, string] = [ findUniqueName(_ast, 'rectangleSegmentA'), findUniqueName(_ast, 'rectangleSegmentB'), @@ -816,7 +879,7 @@ export class SceneEntities { programMemory.get(variableDeclarationName), variableDeclarationName ) - if (err(sketchGroup)) return sketchGroup + if (err(sketchGroup)) return Promise.reject(sketchGroup) const sgPaths = sketchGroup.value const orthoFactor = orthoScale(sceneInfra.camControls.camera) @@ -873,7 +936,7 @@ export class SceneEntities { programMemory.get(variableDeclarationName), variableDeclarationName ) - if (err(sketchGroup)) return sketchGroup + if (err(sketchGroup)) return Promise.reject(sketchGroup) const sgPaths = sketchGroup.value const orthoFactor = orthoScale(sceneInfra.camControls.camera) @@ -894,6 +957,151 @@ export class SceneEntities { }, }) } + setupDraftCircle = async ( + sketchPathToNode: PathToNode, + forward: [number, number, number], + up: [number, number, number], + sketchOrigin: [number, number, number], + circleCenter: [x: number, y: number] + ) => { + let _ast = structuredClone(kclManager.ast) + + const _node1 = getNodeFromPath( + _ast, + sketchPathToNode || [], + 'VariableDeclaration' + ) + if (trap(_node1)) return Promise.reject(_node1) + const variableDeclarationName = + _node1.node?.declarations?.[0]?.id?.name || '' + const startSketchOn = _node1.node?.declarations + const startSketchOnInit = startSketchOn?.[0]?.init + + startSketchOn[0].init = createPipeExpression([ + startSketchOnInit, + createCallExpressionStdLib('circle', [ + createObjectExpression({ + center: createArrayExpression([ + createLiteral(roundOff(circleCenter[0])), + createLiteral(roundOff(circleCenter[1])), + ]), + radius: createLiteral(1), + }), + createPipeSubstitution(), + ]), + ]) + + let _recastAst = parse(recast(_ast)) + if (trap(_recastAst)) return Promise.reject(_recastAst) + _ast = _recastAst + + // do a quick mock execution to get the program memory up-to-date + await kclManager.executeAstMock(_ast) + + const { programMemoryOverride, truncatedAst } = await this.setupSketch({ + sketchPathToNode, + forward, + up, + position: sketchOrigin, + maybeModdedAst: _ast, + draftExpressionsIndices: { start: 0, end: 0 }, + }) + + sceneInfra.setCallbacks({ + onMove: async (args) => { + const pathToNodeTwo = structuredClone(sketchPathToNode) + pathToNodeTwo[1][0] = 0 + + const _node = getNodeFromPath( + truncatedAst, + pathToNodeTwo || [], + 'VariableDeclaration' + ) + let modded = structuredClone(truncatedAst) + if (trap(_node)) return Promise.reject(_node) + const sketchInit = _node.node?.declarations?.[0]?.init + + const x = (args.intersectionPoint.twoD.x || 0) - circleCenter[0] + const y = (args.intersectionPoint.twoD.y || 0) - circleCenter[1] + + if (sketchInit.type === 'PipeExpression') { + const moddedResult = changeCircleArguments( + modded, + kclManager.programMemory, + [..._node.deepPath, ['body', 'PipeExpression'], [1, 'index']], + circleCenter, + Math.sqrt(x ** 2 + y ** 2) + ) + if (err(moddedResult)) return Promise.reject(moddedResult) + modded = moddedResult.modifiedAst + } + + const { programMemory } = await executeAst({ + ast: modded, + useFakeExecutor: true, + engineCommandManager: this.engineCommandManager, + programMemoryOverride, + }) + this.sceneProgramMemory = programMemory + const sketchGroup = sketchGroupFromKclValue( + programMemory.get(variableDeclarationName), + variableDeclarationName + ) + if (err(sketchGroup)) return sketchGroup + const sgPaths = sketchGroup.value + const orthoFactor = orthoScale(sceneInfra.camControls.camera) + + this.updateSegment( + sketchGroup.start, + 0, + 0, + _ast, + orthoFactor, + sketchGroup + ) + sgPaths.forEach((seg, index) => + this.updateSegment(seg, index, 0, _ast, orthoFactor, sketchGroup) + ) + }, + onClick: async (args) => { + // Commit the rectangle to the full AST/code and return to sketch.idle + const cornerPoint = args.intersectionPoint?.twoD + if (!cornerPoint || args.mouseEvent.button !== 0) return + + const x = roundOff((cornerPoint.x || 0) - circleCenter[0]) + const y = roundOff((cornerPoint.y || 0) - circleCenter[1]) + + const _node = getNodeFromPath( + _ast, + sketchPathToNode || [], + 'VariableDeclaration' + ) + if (trap(_node)) return Promise.reject(_node) + const sketchInit = _node.node?.declarations?.[0]?.init + + let modded = structuredClone(_ast) + if (sketchInit.type === 'PipeExpression') { + const moddedResult = changeCircleArguments( + modded, + kclManager.programMemory, + [..._node.deepPath, ['body', 'PipeExpression'], [1, 'index']], + circleCenter, + Math.sqrt(x ** 2 + y ** 2) + ) + if (err(moddedResult)) return Promise.reject(moddedResult) + modded = moddedResult.modifiedAst + + let _recastAst = parse(recast(modded)) + if (trap(_recastAst)) return Promise.reject(_recastAst) + _ast = _recastAst + + // Update the primary AST and unequip the rectangle tool + await kclManager.executeAstMock(_ast) + sceneInfra.modelingSend({ type: 'Finish circle' }) + } + }, + }) + } setupSketchIdleCallbacks = ({ pathToNode, up, @@ -1065,11 +1273,8 @@ export class SceneEntities { ? new Vector2(profileStart.position.x, profileStart.position.y) : _intersection2d - const group = getParentGroup(object, [ - STRAIGHT_SEGMENT, - TANGENTIAL_ARC_TO_SEGMENT, - PROFILE_START, - ]) + const group = getParentGroup(object, SEGMENT_BODIES_PLUS_PROFILE_START) + const subGroup = getParentGroup(object, [ARROWHEAD, CIRCLE_CENTER_HANDLE]) if (!group) return const pathToNode: PathToNode = structuredClone(group.userData.pathToNode) const varDecIndex = pathToNode[1][0] @@ -1117,6 +1322,42 @@ export class SceneEntities { }, previousProgramMemory: kclManager.programMemory, }) + } else if ( + group.name === CIRCLE_SEGMENT && + // !subGroup treats grabbing the outer circumference of the circle + // as a drag of the center handle + (!subGroup || subGroup?.name === ARROWHEAD) + ) { + // is dragging the radius handle + modded = changeSketchArguments( + modifiedAst, + kclManager.programMemory, + [node.start, node.end], + { + type: 'arc-segment', + from, + center: group.userData.center, + radius: Math.sqrt( + (group.userData.center[0] - dragTo[0]) ** 2 + + (group.userData.center[0] - dragTo[0]) ** 2 + ), + } + ) + } else if ( + group.name === CIRCLE_SEGMENT && + subGroup?.name === CIRCLE_CENTER_HANDLE + ) { + modded = changeSketchArguments( + modifiedAst, + kclManager.programMemory, + [node.start, node.end], + { + type: 'arc-segment', + from, + center: dragTo, + radius: group.userData.radius, + } + ) } else { modded = changeSketchArguments( modifiedAst, @@ -1244,6 +1485,20 @@ export class SceneEntities { group, scale: factor, }) + } else if ( + type === CIRCLE_SEGMENT && + 'center' in segment && + 'radius' in segment + ) { + return this.updateCircleSegment({ + prevSegment: sgPaths[index - 1], + from: segment.from, + to: segment.to, + center: segment.center, + radius: segment.radius, + group, + scale: factor, + }) } else if (type === PROFILE_START) { group.position.set(segment.from[0], segment.from[1], 0) group.scale.set(factor, factor, factor) @@ -1269,6 +1524,9 @@ export class SceneEntities { group.userData.prevSegment = prevSegment const arrowGroup = group.getObjectByName(ARROWHEAD) as Group const extraSegmentGroup = group.getObjectByName(EXTRA_SEGMENT_HANDLE) + if (!prevSegment) { + console.trace('prevSegment is undefined') + } const previousPoint = prevSegment?.type === 'TangentialArcTo' @@ -1364,6 +1622,111 @@ export class SceneEntities { angle, }) } + updateCircleSegment({ + prevSegment, + from, + to, + center, + radius, + group, + scale = 1, + }: { + prevSegment: SketchGroup['value'][number] + from: [number, number] + to: [number, number] + center: [number, number] + radius: number + group: Group + scale?: number + }): () => SegmentOverlayPayload | null { + group.userData.from = from + group.userData.to = to + group.userData.center = center + group.userData.radius = radius + group.userData.prevSegment = prevSegment + const arrowGroup = group.getObjectByName(ARROWHEAD) as Group + const circleCenterHandle = group.getObjectByName( + CIRCLE_CENTER_HANDLE + ) as Group + + const pxLength = (2 * radius * Math.PI) / scale + const shouldHideIdle = pxLength < HIDE_SEGMENT_LENGTH + const shouldHideHover = pxLength < HIDE_HOVER_SEGMENT_LENGTH + + const hoveredParent = + sceneInfra.hoveredObject && + getParentGroup(sceneInfra.hoveredObject, [CIRCLE_SEGMENT]) + let isHandlesVisible = !shouldHideIdle + if (hoveredParent && hoveredParent?.uuid === group?.uuid) { + isHandlesVisible = !shouldHideHover + } + + if (arrowGroup) { + arrowGroup.position.set( + center[0] + Math.cos(Math.PI / 4) * radius, + center[1] + Math.sin(Math.PI / 4) * radius, + 0 + ) + + const arrowheadAngle = Math.PI / 4 + arrowGroup.quaternion.setFromUnitVectors( + new Vector3(0, 1, 0), + new Vector3(Math.cos(arrowheadAngle), Math.sin(arrowheadAngle), 0) + ) + arrowGroup.scale.set(scale, scale, scale) + arrowGroup.visible = isHandlesVisible + } + + if (circleCenterHandle) { + circleCenterHandle.position.set(center[0], center[1], 0) + circleCenterHandle.scale.set(scale, scale, scale) + circleCenterHandle.visible = isHandlesVisible + } + + const circleSegmentBody = group.children.find( + (child) => child.userData.type === CIRCLE_SEGMENT_BODY + ) as Mesh + + if (circleSegmentBody) { + const newGeo = createArcGeometry({ + radius, + center, + startAngle: 0, + endAngle: Math.PI * 2, + ccw: true, + scale, + }) + circleSegmentBody.geometry = newGeo + } + const circleSegmentBodyDashed = group.children.find( + (child) => child.userData.type === CIRCLE_SEGMENT_DASH + ) as Mesh + if (circleSegmentBodyDashed) { + // consider throttling the whole updateTangentialArcToSegment + // if there are more perf considerations going forward + this.throttledUpdateDashedArcGeo({ + // ...arcInfo, + center, + radius, + ccw: true, + // make the start end where the handle is + startAngle: Math.PI * 0.25, + endAngle: Math.PI * 2.25, + mesh: circleSegmentBodyDashed, + isDashed: true, + scale, + }) + } + return () => + sceneInfra.updateOverlayDetails({ + arrowGroup, + group, + isHandlesVisible, + from: to, + to: [center[0], center[1]], + angle: Math.PI / 4, + }) + } throttledUpdateDashedArcGeo = throttle( ( args: Parameters[0] & { @@ -1517,7 +1880,7 @@ export class SceneEntities { } private _tearDownSketch( callDepth = 0, - resolve: (val: unknown) => void, + resolve: any, reject: () => void, { removeAxis = true }: { removeAxis?: boolean } ) { @@ -1547,7 +1910,7 @@ export class SceneEntities { this._tearDownSketch(callDepth + 1, resolve, reject, { removeAxis }) }, delay) } else { - reject() + resolve(true) } } sceneInfra.camControls.enableRotate = true @@ -1559,7 +1922,7 @@ export class SceneEntities { removeAxis = true, }: { removeAxis?: boolean - } = {}) { + } = {}): Promise { // I think promisifying this is mostly a side effect of not having // "setupSketch" correctly capture a promise when it's done // so we're effectively waiting for to be finished setting up the scene just to tear it down @@ -1577,11 +1940,10 @@ export class SceneEntities { mat.color.set(obj.userData.baseColor) mat.color.offsetHSL(0, 0, 0.5) } - const parent = getParentGroup(selected, [ - STRAIGHT_SEGMENT, - TANGENTIAL_ARC_TO_SEGMENT, - PROFILE_START, - ]) + const parent = getParentGroup( + selected, + SEGMENT_BODIES_PLUS_PROFILE_START + ) if (parent?.userData?.pathToNode) { const updatedAst = parse(recast(kclManager.ast)) if (trap(updatedAst)) return @@ -1625,6 +1987,16 @@ export class SceneEntities { group: parent, scale: factor, }) + } else if (parent.name === CIRCLE_SEGMENT) { + this.updateCircleSegment({ + prevSegment: parent.userData.prevSegment, + from: parent.userData.from, + to: parent.userData.to, + center: parent.userData.center, + radius: parent.userData.radius, + group: parent, + scale: factor, + }) } return } @@ -1632,11 +2004,10 @@ export class SceneEntities { }, onMouseLeave: ({ selected, ...rest }: OnMouseEnterLeaveArgs) => { editorManager.setHighlightRange([[0, 0]]) - const parent = getParentGroup(selected, [ - STRAIGHT_SEGMENT, - TANGENTIAL_ARC_TO_SEGMENT, - PROFILE_START, - ]) + const parent = getParentGroup( + selected, + SEGMENT_BODIES_PLUS_PROFILE_START + ) if (parent) { const orthoFactor = orthoScale(sceneInfra.camControls.camera) @@ -1660,6 +2031,16 @@ export class SceneEntities { group: parent, scale: factor, }) + } else if (parent.name === CIRCLE_SEGMENT) { + this.updateCircleSegment({ + prevSegment: parent.userData.prevSegment, + from: parent.userData.from, + to: parent.userData.to, + center: parent.userData.center, + radius: parent.userData.radius, + group: parent, + scale: factor, + }) } } const isSelected = parent?.userData?.isSelected @@ -1822,7 +2203,7 @@ function prepareTruncatedMemoryAndAst( export function getParentGroup( object: any, - stopAt: string[] = [STRAIGHT_SEGMENT, TANGENTIAL_ARC_TO_SEGMENT] + stopAt: string[] = SEGMENT_BODIES ): Group | null { if (stopAt.includes(object?.userData?.type)) { return object @@ -1869,10 +2250,7 @@ function colorSegment(object: any, color: number) { }) return } - const straightSegmentBody = getParentGroup(object, [ - STRAIGHT_SEGMENT, - TANGENTIAL_ARC_TO_SEGMENT, - ]) + const straightSegmentBody = getParentGroup(object, SEGMENT_BODIES) if (straightSegmentBody) { straightSegmentBody.traverse((child) => { if (child instanceof Mesh && !child.userData.ignoreColorChange) { diff --git a/src/clientSideScene/segments.ts b/src/clientSideScene/segments.ts index 1501d68373..47e50826f1 100644 --- a/src/clientSideScene/segments.ts +++ b/src/clientSideScene/segments.ts @@ -24,6 +24,10 @@ import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer' import { PathToNode, SketchGroup, getTangentialArcToInfo } from 'lang/wasm' import { + CIRCLE_CENTER_HANDLE, + CIRCLE_SEGMENT, + CIRCLE_SEGMENT_BODY, + CIRCLE_SEGMENT_DASH, EXTRA_SEGMENT_HANDLE, EXTRA_SEGMENT_OFFSET_PX, HIDE_SEGMENT_LENGTH, @@ -225,6 +229,28 @@ function createArrowhead(scale = 1, theme: Themes, color?: number): Group { arrowGroup.scale.set(scale, scale, scale) return arrowGroup } +function createCircleCenterHandle( + scale = 1, + theme: Themes, + color?: number +): Group { + const circleCenterGroup = new Group() + + const geometry = new BoxGeometry(12, 12, 12) // in pixels scaled later + const baseColor = getThemeColorForThreeJs(theme) + const body = new MeshBasicMaterial({ color }) + const mesh = new Mesh(geometry, body) + + circleCenterGroup.add(mesh) + + circleCenterGroup.userData = { + type: CIRCLE_CENTER_HANDLE, + baseColor, + } + circleCenterGroup.name = CIRCLE_CENTER_HANDLE + circleCenterGroup.scale.set(scale, scale, scale) + return circleCenterGroup +} function createExtraSegmentHandle( scale: number, @@ -300,6 +326,86 @@ function createLengthIndicator({ return lengthIndicatorGroup } +export function circleSegment({ + prevSegment, + from, + to, + center, + radius, + id, + pathToNode, + isDraftSegment, + scale = 1, + texture, + theme, + isSelected, +}: { + prevSegment: SketchGroup['value'][number] + from: Coords2d + center: Coords2d + radius: number + to: Coords2d + id: string + pathToNode: PathToNode + isDraftSegment?: boolean + scale?: number + texture: Texture + theme: Themes + isSelected?: boolean +}): Group { + const group = new Group() + + const geometry = createArcGeometry({ + center, + radius, + startAngle: 0, + endAngle: Math.PI * 2, + ccw: true, + isDashed: isDraftSegment, + scale, + }) + + const baseColor = getThemeColorForThreeJs(theme) + const color = isSelected ? 0x0000ff : baseColor + const body = new MeshBasicMaterial({ color }) + const mesh = new Mesh(geometry, body) + mesh.userData.type = isDraftSegment + ? CIRCLE_SEGMENT_DASH + : CIRCLE_SEGMENT_BODY + + group.userData = { + type: CIRCLE_SEGMENT, + id, + from, + to, + radius, + center, + ccw: true, + prevSegment, + pathToNode, + isSelected, + baseColor, + } + group.name = CIRCLE_SEGMENT + + const arrowGroup = createArrowhead(scale, theme, color) + arrowGroup.position.set( + center[0] + Math.cos(Math.PI / 4) * radius, + center[1] + Math.sin(Math.PI / 4) * radius, + 0 + ) + + const circleCenterGroup = createCircleCenterHandle(scale, theme, color) + circleCenterGroup.position.set(center[0], center[1], 0) + const arrowheadAngle = Math.PI / 4 + arrowGroup.quaternion.setFromUnitVectors( + new Vector3(0, 1, 0), + new Vector3(Math.cos(arrowheadAngle), Math.sin(arrowheadAngle), 0) + ) + + group.add(mesh, arrowGroup, circleCenterGroup) + return group +} export function tangentialArcToSegment({ prevSegment, from, diff --git a/src/components/ModelingMachineProvider.tsx b/src/components/ModelingMachineProvider.tsx index 533d1121f5..d2ebadfa49 100644 --- a/src/components/ModelingMachineProvider.tsx +++ b/src/components/ModelingMachineProvider.tsx @@ -50,8 +50,7 @@ import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance' import useStateMachineCommands from 'hooks/useStateMachineCommands' import { modelingMachineCommandConfig } from 'lib/commandBarConfigs/modelingCommandConfig' import { - STRAIGHT_SEGMENT, - TANGENTIAL_ARC_TO_SEGMENT, + SEGMENT_BODIES, getParentGroup, getSketchOrientationDetails, } from 'clientSideScene/sceneEntities' @@ -168,10 +167,7 @@ export const ModelingMachineProvider = ({ if (event.type !== 'Set mouse state') return {} const nextSegmentHoverMap = () => { if (event.data.type === 'isHovering') { - const parent = getParentGroup(event.data.on, [ - STRAIGHT_SEGMENT, - TANGENTIAL_ARC_TO_SEGMENT, - ]) + const parent = getParentGroup(event.data.on, SEGMENT_BODIES) const pathToNode = parent?.userData?.pathToNode const pathToNodeString = JSON.stringify(pathToNode) if (!parent || !pathToNode) return context.segmentHoverMap @@ -187,10 +183,10 @@ export const ModelingMachineProvider = ({ event.data.type === 'idle' && context.mouseState.type === 'isHovering' ) { - const mouseOnParent = getParentGroup(context.mouseState.on, [ - STRAIGHT_SEGMENT, - TANGENTIAL_ARC_TO_SEGMENT, - ]) + const mouseOnParent = getParentGroup( + context.mouseState.on, + SEGMENT_BODIES + ) if (!mouseOnParent || !mouseOnParent?.userData?.pathToNode) return context.segmentHoverMap const pathToNodeString = JSON.stringify( @@ -204,8 +200,8 @@ export const ModelingMachineProvider = ({ pathToNodeString, }, }) - // overlay timeout - }, 800) as unknown as number + // overlay timeout is 1s + }, 1000) as unknown as number return { ...context.segmentHoverMap, [pathToNodeString]: timeoutId, diff --git a/src/lang/langHelpers.ts b/src/lang/langHelpers.ts index bd39f4e814..d436a6120a 100644 --- a/src/lang/langHelpers.ts +++ b/src/lang/langHelpers.ts @@ -24,8 +24,9 @@ export type ToolTip = | 'yLineTo' | 'angledLineThatIntersects' | 'tangentialArcTo' + | 'circle' -export const toolTips = [ +export const toolTips: Array = [ 'line', 'lineTo', 'angledLine', diff --git a/src/lang/std/sketch.ts b/src/lang/std/sketch.ts index 755041cbba..7d3827b9e6 100644 --- a/src/lang/std/sketch.ts +++ b/src/lang/std/sketch.ts @@ -17,6 +17,7 @@ import { getNodeFromPath, getNodeFromPathCurry, getNodePathFromSourceRange, + getObjExpProperty, } from 'lang/queryAst' import { isLiteralArrayOrStatic, @@ -57,6 +58,7 @@ import { TagDeclarator } from 'wasm-lib/kcl/bindings/TagDeclarator' const STRAIGHT_SEGMENT_ERR = new Error( 'Invalid input, expected "straight-segment"' ) +const ARC_SEGMENT_ERR = new Error('Invalid input, expected "arc-segment"') export type Coords2d = [number, number] @@ -911,6 +913,171 @@ export const tangentialArcTo: SketchLineHelper = { ] }, } +export const circle: SketchLineHelper = { + add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => { + if (segmentInput.type !== 'arc-segment') return ARC_SEGMENT_ERR + + const { center, radius } = segmentInput + const _node = { ...node } + const nodeMeta = getNodeFromPath( + _node, + pathToNode, + 'PipeExpression' + ) + if (err(nodeMeta)) return nodeMeta + + const { node: pipe } = nodeMeta + + const x = createLiteral(roundOff(center[0], 2)) + const y = createLiteral(roundOff(center[1], 2)) + + const radiusExp = createLiteral(roundOff(radius, 2)) + + if (replaceExistingCallback) { + const { callExp, valueUsedInTransform } = replaceExistingCallback([ + { + type: 'arrayInObject', + index: 0, + key: 'center', + argType: 'xAbsolute', + expr: x, + }, + { + type: 'arrayInObject', + index: 1, + key: 'center', + argType: 'yAbsolute', + expr: y, + }, + { + type: 'objectProperty', + key: 'radius', + argType: 'radius', + expr: radiusExp, + }, + ]) + + const { index: callIndex } = splitPathAtPipeExpression(pathToNode) + pipe.body[callIndex] = callExp + + return { + modifiedAst: _node, + pathToNode, + valueUsedInTransform, + } + } + return new Error('not implemented') + }, + updateArgs: ({ node, pathToNode, input }) => { + if (input.type !== 'arc-segment') return ARC_SEGMENT_ERR + const { center, radius } = input + const _node = { ...node } + const nodeMeta = getNodeFromPath(_node, pathToNode) + if (err(nodeMeta)) return nodeMeta + + const { node: callExpression, shallowPath } = nodeMeta + + const firstArg = callExpression.arguments?.[0] + const newCenter = createArrayExpression([ + createLiteral(roundOff(center[0])), + createLiteral(roundOff(center[1])), + ]) + mutateObjExpProp(firstArg, newCenter, 'center') + const newRadius = createLiteral(roundOff(radius)) + mutateObjExpProp(firstArg, newRadius, 'radius') + return { + modifiedAst: _node, + pathToNode: shallowPath, + } + }, + getTag: getTag(3), + addTag: addTag(3), + getConstraintInfo: (callExp: CallExpression, code, pathToNode) => { + if (callExp.type !== 'CallExpression') return [] + const firstArg = callExp.arguments?.[0] + if (firstArg.type !== 'ObjectExpression') return [] + const centerDetails = getObjExpProperty(firstArg, 'center') + const radiusDetails = getObjExpProperty(firstArg, 'radius') + if (!centerDetails || !radiusDetails) return [] + if (centerDetails.exp.type !== 'ArrayExpression') return [] + + const pathToCenterArrayExpression: PathToNode = [ + ...pathToNode, + ['arguments', 'CallExpression'], + [0, 'index'], + ['properties', 'ObjectExpression'], + [centerDetails.index, 'index'], + ['value', 'Property'], + ['elements', 'ArrayExpression'], + ] + const pathToRadiusLiteral: PathToNode = [ + ...pathToNode, + ['arguments', 'CallExpression'], + [0, 'index'], + ['properties', 'ObjectExpression'], + [radiusDetails.index, 'index'], + ['value', 'Property'], + ] + const pathToXArg: PathToNode = [ + ...pathToCenterArrayExpression, + [0, 'index'], + ] + const pathToYArg: PathToNode = [ + ...pathToCenterArrayExpression, + [1, 'index'], + ] + + return [ + constrainInfo( + 'radius', + isNotLiteralArrayOrStatic(radiusDetails.exp), + code.slice(radiusDetails.exp.start, radiusDetails.exp.end), + 'circle', + 'radius', + [radiusDetails.exp.start, radiusDetails.exp.end], + pathToRadiusLiteral + ), + { + stdLibFnName: 'circle', + type: 'xAbsolute', + isConstrained: isNotLiteralArrayOrStatic(centerDetails.exp.elements[0]), + sourceRange: [ + centerDetails.exp.elements[0].start, + centerDetails.exp.elements[0].end, + ], + pathToNode: pathToXArg, + value: code.slice( + centerDetails.exp.elements[0].start, + centerDetails.exp.elements[0].end + ), + argPosition: { + type: 'arrayInObject', + index: 0, + key: 'center', + }, + }, + { + stdLibFnName: 'circle', + type: 'yAbsolute', + isConstrained: isNotLiteralArrayOrStatic(centerDetails.exp.elements[1]), + sourceRange: [ + centerDetails.exp.elements[1].start, + centerDetails.exp.elements[1].end, + ], + pathToNode: pathToYArg, + value: code.slice( + centerDetails.exp.elements[1].start, + centerDetails.exp.elements[1].end + ), + argPosition: { + type: 'arrayInObject', + index: 1, + key: 'center', + }, + }, + ] + }, +} export const angledLine: SketchLineHelper = { add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => { if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR @@ -1656,8 +1823,44 @@ export const sketchLineHelperMap: { [key: string]: SketchLineHelper } = { angledLineToY, angledLineThatIntersects, tangentialArcTo, + circle, } as const +/** + * @deprecated Use {@link changeSketchArguments} instead + */ +export function changeCircleArguments( + node: Program, + programMemory: ProgramMemory, + pathToNode: PathToNode, + center: [number, number], + radius: number +): { modifiedAst: Program; pathToNode: PathToNode } | Error { + const _node = { ...node } + const nodeMeta = getNodeFromPath(_node, pathToNode) + if (err(nodeMeta)) return nodeMeta + + const { node: callExpression, shallowPath } = nodeMeta + + if (callExpression?.callee?.name === 'circle') { + const newCenter = createArrayExpression([ + createLiteral(roundOff(center[0])), + createLiteral(roundOff(center[1])), + ]) + const newRadius = createLiteral(roundOff(radius)) + callExpression.arguments[0] = createObjectExpression({ + center: newCenter, + radius: newRadius, + }) + return { + modifiedAst: _node, + pathToNode: shallowPath, + } + } + + return new Error(`There was a problem: ${callExpression?.callee?.name}`) +} + export function changeSketchArguments( node: Program, programMemory: ProgramMemory, @@ -1843,7 +2046,7 @@ export function replaceSketchLine({ pathToNode: PathToNode } | Error { - if (![...toolTips, 'intersect'].includes(fnName)) { + if (![...toolTips, 'intersect', 'circle'].includes(fnName)) { return new Error(`The following function name is not tooltip: ${fnName}`) } const _node = { ...node } @@ -2033,6 +2236,32 @@ function getFirstArgValuesForXYLineFns(callExpression: CallExpression): { } } +const getCircle = ( + callExp: CallExpression +): + | { + val: [Expr, Expr, Expr] + tag?: Expr + } + | Error => { + const firstArg = callExp.arguments[0] + if (firstArg.type === 'ObjectExpression') { + const centerDetails = getObjExpProperty(firstArg, 'center') + const radiusDetails = getObjExpProperty(firstArg, 'radius') + const tag = callExp.arguments[2] + if (centerDetails?.exp?.type === 'ArrayExpression' && radiusDetails) { + return { + val: [ + centerDetails?.exp.elements[0], + centerDetails?.exp.elements[1], + radiusDetails.exp, + ], + tag, + } + } + } + return new Error('expected ArrayExpression or ObjectExpression') +} const getAngledLineThatIntersects = ( callExp: CallExpression ): @@ -2092,5 +2321,8 @@ export function getFirstArg(callExp: CallExpression): // TODO probably needs it's own implementation return getFirstArgValuesForXYFns(callExp) } + if (name === 'circle') { + return getCircle(callExp) + } return new Error('unexpected call expression: ' + name) } diff --git a/src/lang/std/sketchcombos.ts b/src/lang/std/sketchcombos.ts index d63097f354..932256be5b 100644 --- a/src/lang/std/sketchcombos.ts +++ b/src/lang/std/sketchcombos.ts @@ -59,6 +59,7 @@ export type LineInputsType = | 'length' | 'intersectionOffset' | 'intersectionTag' + | 'radius' export type ConstraintType = | 'equalLength' @@ -81,7 +82,10 @@ function createCallWrapper( tag?: Expr, valueUsedInTransform?: number ): CreatedSketchExprResult { - const args = [createFirstArg(tooltip, val), createPipeSubstitution()] + const args = + tooltip === 'circle' + ? [] + : [createFirstArg(tooltip, val), createPipeSubstitution()] if (tag) { args.push(tag) } @@ -1711,11 +1715,20 @@ export function transformAstSketchLines({ pathToNode: _pathToNode, referencedSegment, fnName: transformTo || (callExp.node.callee.name as ToolTip), - segmentInput: { - type: 'straight-segment', - to, - from, - }, + segmentInput: + seg.type === 'Circle' + ? { + type: 'arc-segment', + center: seg.center, + radius: seg.radius, + from, + } + : { + type: 'straight-segment', + to, + from, + }, + replaceExistingCallback: (rawArgs) => callBack({ referenceSegName: _referencedSegmentName, @@ -1863,6 +1876,6 @@ function isExprBinaryPart(expr: Expr): expr is BinaryPart { return false } -function getInputOfType(a: InputArgs, b: LineInputsType): InputArg { +function getInputOfType(a: InputArgs, b: LineInputsType | 'radius'): InputArg { return a.find(({ argType }) => argType === b) || a[0] } diff --git a/src/lang/std/stdTypes.ts b/src/lang/std/stdTypes.ts index 60d804b007..1333728014 100644 --- a/src/lang/std/stdTypes.ts +++ b/src/lang/std/stdTypes.ts @@ -34,13 +34,22 @@ interface StraightSegmentInput { to: [number, number] } +/** Inputs for arcs, excluding tangentialArcTo for reasons explain in + * the @straightSegmentInput comment */ +interface ArcSegmentInput { + type: 'arc-segment' + from: [number, number] + center: [number, number] + radius: number +} + /** * SegmentInputs is a union type that can be either a StraightSegmentInput or an ArcSegmentInput. * * - StraightSegmentInput: Represents a straight segment with a starting point (from) and an ending point (to). * - ArcSegmentInput: Represents an arc segment with a starting point (from), a center point, and a radius. */ -export type SegmentInputs = StraightSegmentInput // TODO ArcSegmentInput +export type SegmentInputs = StraightSegmentInput | ArcSegmentInput /** * Interface for adding or replacing a sketch stblib call expression to a sketch. @@ -63,7 +72,14 @@ interface updateArgs extends ModifyAstBase { input: SegmentInputs } -export type InputArgKeys = 'angle' | 'offset' | 'length' | 'to' | 'intersectTag' +export type InputArgKeys = + | 'angle' + | 'offset' + | 'length' + | 'to' + | 'intersectTag' + | 'radius' + | 'center' export interface SingleValueInput { type: 'singleValue' argType: LineInputsType @@ -185,7 +201,12 @@ export type TransformInfo = { export interface ConstrainInfo { stdLibFnName: ToolTip - type: LineInputsType | 'vertical' | 'horizontal' | 'tangentialWithPrevious' + type: + | LineInputsType + | 'vertical' + | 'horizontal' + | 'tangentialWithPrevious' + | 'radius' isConstrained: boolean sourceRange: SourceRange pathToNode: PathToNode diff --git a/src/lib/toolbar.ts b/src/lib/toolbar.ts index bbdc5a9781..5e153fd73e 100644 --- a/src/lib/toolbar.ts +++ b/src/lib/toolbar.ts @@ -2,7 +2,8 @@ import { CustomIconName } from 'components/CustomIcon' import { DEV } from 'env' import { commandBarMachine } from 'machines/commandBarMachine' import { - canRectangleTool, + canRectangleOrCircleTool, + isClosedSketch, isEditingExistingSketch, modelingMachine, } from 'machines/modelingMachine' @@ -301,7 +302,11 @@ export const toolbarConfig: Record = { state.matches('Sketch no face') || state.matches({ Sketch: { 'Rectangle tool': 'Awaiting second corner' }, - }), + }) || + state.matches({ + Sketch: { 'Circle tool': 'Awaiting Radius' }, + }) || + isClosedSketch(state.context), title: 'Line', hotkey: (state) => state.matches({ Sketch: 'Line tool' }) ? ['Esc', 'L'] : 'L', @@ -328,7 +333,11 @@ export const toolbarConfig: Record = { !state.matches({ Sketch: 'Tangential arc to' })) || state.matches({ Sketch: { 'Rectangle tool': 'Awaiting second corner' }, - }), + }) || + state.matches({ + Sketch: { 'Circle tool': 'Awaiting Radius' }, + }) || + isClosedSketch(state.context), title: 'Tangential Arc', hotkey: (state) => state.matches({ Sketch: 'Tangential arc to' }) ? ['Esc', 'A'] : 'A', @@ -366,10 +375,22 @@ export const toolbarConfig: Record = { [ { id: 'circle-center', - onClick: () => console.error('Center circle not yet implemented'), + onClick: ({ modelingState, modelingSend }) => + modelingSend({ + type: 'change tool', + data: { + tool: !modelingState.matches({ Sketch: 'Circle tool' }) + ? 'circle' + : 'none', + }, + }), icon: 'circle', - status: 'unavailable', + status: 'available', title: 'Center circle', + disabled: (state) => + !canRectangleOrCircleTool(state.context) && + !state.matches({ Sketch: 'Circle tool' }), + isActive: (state) => state.matches({ Sketch: 'Circle tool' }), showTitle: false, description: 'Start drawing a circle from its center', links: [ @@ -385,7 +406,6 @@ export const toolbarConfig: Record = { console.error('Three-point circle not yet implemented'), icon: 'circle', status: 'unavailable', - disabled: () => true, title: 'Three-point circle', showTitle: false, description: 'Draw a circle defined by three points', @@ -407,7 +427,7 @@ export const toolbarConfig: Record = { icon: 'rectangle', status: 'available', disabled: (state) => - !canRectangleTool(state.context) && + !canRectangleOrCircleTool(state.context) && !state.matches({ Sketch: 'Rectangle tool' }), title: 'Corner rectangle', hotkey: (state) => diff --git a/src/machines/modelingMachine.ts b/src/machines/modelingMachine.ts index 7bed9d3c0b..836464cbf0 100644 --- a/src/machines/modelingMachine.ts +++ b/src/machines/modelingMachine.ts @@ -58,6 +58,8 @@ import { deleteSegment } from 'clientSideScene/ClientSideSceneComp' import { executeAst } from 'lang/langHelpers' import toast from 'react-hot-toast' import { ToolbarModeName } from 'lib/toolbar' +import { quaternionFromUpNForward } from 'clientSideScene/helpers' +import { Vector3 } from 'three' export const MODELING_PERSIST_KEY = 'MODELING_PERSIST_KEY' @@ -159,7 +161,12 @@ export interface Store { openPanes: SidebarType[] } -export type SketchTool = 'line' | 'tangentialArc' | 'rectangle' | 'none' +export type SketchTool = + | 'line' + | 'tangentialArc' + | 'rectangle' + | 'circle' + | 'none' export type ModelingMachineEvent = | { @@ -211,6 +218,10 @@ export type ModelingMachineEvent = type: 'Add rectangle origin' data: [x: number, y: number] } + | { + type: 'Add circle origin' + data: [x: number, y: number] + } | { type: 'xstate.done.actor.animate-to-face' output: SketchDetails @@ -244,6 +255,7 @@ export type ModelingMachineEvent = } } | { type: 'Finish rectangle' } + | { type: 'Finish circle' } | { type: 'Artifact graph populated' } | { type: 'Artifact graph emptied' } @@ -464,7 +476,10 @@ export const modelingMachine = setup({ isEditingExistingSketch({ sketchDetails }), 'next is rectangle': ({ context: { sketchDetails, currentTool } }) => - currentTool === 'rectangle' && canRectangleTool({ sketchDetails }), + currentTool === 'rectangle' && + canRectangleOrCircleTool({ sketchDetails }), + 'next is circle': ({ context: { sketchDetails, currentTool } }) => + currentTool === 'circle' && canRectangleOrCircleTool({ sketchDetails }), 'next is line': ({ context }) => context.currentTool === 'line', 'next is none': ({ context }) => context.currentTool === 'none', }, @@ -691,6 +706,42 @@ export const modelingMachine = setup({ }, }) }, + 'listen for circle origin': ({ context: { sketchDetails } }) => { + if (!sketchDetails) return + sceneEntitiesManager.createIntersectionPlane() + const quaternion = quaternionFromUpNForward( + new Vector3(...sketchDetails.yAxis), + new Vector3(...sketchDetails.zAxis) + ) + + // Position the click raycast plane + if (sceneEntitiesManager.intersectionPlane) { + sceneEntitiesManager.intersectionPlane.setRotationFromQuaternion( + quaternion + ) + sceneEntitiesManager.intersectionPlane.position.copy( + new Vector3(...(sketchDetails?.origin || [0, 0, 0])) + ) + } + sceneInfra.setCallbacks({ + onClick: async (args) => { + if (!args) return + if (args.mouseEvent.which !== 1) return + const { intersectionPoint } = args + if (!intersectionPoint?.twoD || !sketchDetails?.sketchPathToNode) + return + const twoD = args.intersectionPoint?.twoD + if (twoD) { + sceneInfra.modelingSend({ + type: 'Add circle origin', + data: [twoD.x, twoD.y], + }) + } else { + console.error('No intersection point found') + } + }, + }) + }, 'set up draft rectangle': ({ context: { sketchDetails }, event }) => { if (event.type !== 'Add rectangle origin') return if (!sketchDetails || !event.data) return @@ -703,6 +754,18 @@ export const modelingMachine = setup({ event.data ) }, + 'set up draft circle': ({ context: { sketchDetails }, event }) => { + if (event.type !== 'Add circle origin') return + if (!sketchDetails || !event.data) return + // eslint-disable-next-line @typescript-eslint/no-floating-promises + sceneEntitiesManager.setupDraftCircle( + sketchDetails.sketchPathToNode, + sketchDetails.zAxis, + sketchDetails.yAxis, + sketchDetails.origin, + event.data + ) + }, 'set up draft line without teardown': ({ context: { sketchDetails } }) => { if (!sketchDetails) return // eslint-disable-next-line @typescript-eslint/no-floating-promises @@ -1802,10 +1865,43 @@ export const modelingMachine = setup({ target: 'Tangential arc to', guard: 'next is tangential arc', }, + { + target: 'Circle tool', + guard: 'next is circle', + }, ], entry: 'assign tool in context', }, + 'Circle tool': { + on: { + 'change tool': 'Change Tool', + }, + + states: { + 'Awaiting origin': { + on: { + 'Add circle origin': { + target: 'Awaiting Radius', + actions: 'set up draft circle', + }, + }, + }, + + 'Awaiting Radius': { + on: { + 'Finish circle': 'Finished Circle', + }, + }, + + 'Finished Circle': { + always: '#Modeling.Sketch.SketchIdle', + }, + }, + + initial: 'Awaiting origin', + entry: 'listen for circle origin', + }, }, initial: 'Init', @@ -1943,10 +2039,13 @@ export function isEditingExistingSketch({ (item) => item.type === 'CallExpression' && item.callee.name === 'startProfileAt' ) - return hasStartProfileAt && pipeExpression.body.length > 2 + const hasCircle = pipeExpression.body.some( + (item) => item.type === 'CallExpression' && item.callee.name === 'circle' + ) + return (hasStartProfileAt && pipeExpression.body.length > 2) || hasCircle } -export function canRectangleTool({ +export function canRectangleOrCircleTool({ sketchDetails, }: { sketchDetails: SketchDetails | null @@ -1961,3 +2060,25 @@ export function canRectangleTool({ if (err(node)) return false return node.node?.declarations?.[0]?.init.type !== 'PipeExpression' } + +/** If the sketch contains `close` or `circle` stdlib functions it must be closed */ +export function isClosedSketch({ + sketchDetails, +}: { + sketchDetails: SketchDetails | null +}): boolean { + const node = getNodeFromPath( + kclManager.ast, + sketchDetails?.sketchPathToNode || [], + 'VariableDeclaration' + ) + // This should not be returning false, and it should be caught + // but we need to simulate old behavior to move on. + if (err(node)) return false + if (node.node?.declarations?.[0]?.init.type !== 'PipeExpression') return false + return node.node.declarations[0].init.body.some( + (yo) => + yo.type === 'CallExpression' && + (yo.callee.name === 'close' || yo.callee.name === 'circle') + ) +} diff --git a/src/wasm-lib/kcl/src/docs.rs b/src/wasm-lib/kcl/src/docs.rs index 760c11f526..d2bb9f7678 100644 --- a/src/wasm-lib/kcl/src/docs.rs +++ b/src/wasm-lib/kcl/src/docs.rs @@ -937,6 +937,9 @@ mod tests { fn get_autocomplete_snippet_circle() { let circle_fn: Box = Box::new(crate::std::shapes::Circle); let snippet = circle_fn.to_autocomplete_snippet().unwrap(); - assert_eq!(snippet, r#"circle([${0:3.14}, ${1:3.14}], ${2:3.14}, ${3:%})${}"#); + assert_eq!( + snippet, + r#"circle({ center: [${0:3.14}, ${1:3.14}], radius: ${2:3.14} }, ${3:%})${}"# + ); } } diff --git a/src/wasm-lib/kcl/src/executor.rs b/src/wasm-lib/kcl/src/executor.rs index fb94b739d3..ddae77ccd7 100644 --- a/src/wasm-lib/kcl/src/executor.rs +++ b/src/wasm-lib/kcl/src/executor.rs @@ -1416,6 +1416,19 @@ pub enum Path { /// arc's direction ccw: bool, }, + /// a complete arc + Circle { + #[serde(flatten)] + base: BasePath, + /// the arc's center + #[ts(type = "[number, number]")] + center: [f64; 2], + /// the arc's radius + radius: f64, + /// arc's direction + // Maybe this one's not needed since it's a full revolution? + ccw: bool, + }, /// A path that is horizontal. Horizontal { #[serde(flatten)] @@ -1448,6 +1461,7 @@ impl Path { Path::Base { base } => base.geo_meta.id, Path::TangentialArcTo { base, .. } => base.geo_meta.id, Path::TangentialArc { base, .. } => base.geo_meta.id, + Path::Circle { base, .. } => base.geo_meta.id, } } @@ -1459,6 +1473,7 @@ impl Path { Path::Base { base } => base.tag.clone(), Path::TangentialArcTo { base, .. } => base.tag.clone(), Path::TangentialArc { base, .. } => base.tag.clone(), + Path::Circle { base, .. } => base.tag.clone(), } } @@ -1470,6 +1485,7 @@ impl Path { Path::Base { base } => base, Path::TangentialArcTo { base, .. } => base, Path::TangentialArc { base, .. } => base, + Path::Circle { base, .. } => base, } } @@ -1481,6 +1497,7 @@ impl Path { Path::Base { base } => Some(base), Path::TangentialArcTo { base, .. } => Some(base), Path::TangentialArc { base, .. } => Some(base), + Path::Circle { base, .. } => Some(base), } } } @@ -2572,7 +2589,7 @@ fn transform = (replicaId) => { fn layer = () => { return startSketchOn("XY") - |> circle([0, 0], 1, %, $tag1) + |> circle({ center: [0, 0], radius: 1 }, %, $tag1) |> extrude(10, %) } @@ -2700,7 +2717,7 @@ fn transform = (replicaId) => { fn layer = () => { return startSketchOn("XY") - |> circle([0, 0], 1, %, $tag1) + |> circle({ center: [0, 0], radius: 1 }, %, $tag1) |> extrude(10, %) } diff --git a/src/wasm-lib/kcl/src/std/args.rs b/src/wasm-lib/kcl/src/std/args.rs index 130d5173f1..7013c09090 100644 --- a/src/wasm-lib/kcl/src/std/args.rs +++ b/src/wasm-lib/kcl/src/std/args.rs @@ -261,8 +261,7 @@ impl Args { &self, ) -> Result< ( - [f64; 2], - f64, + crate::std::shapes::CircleData, crate::std::shapes::SketchSurfaceOrGroup, Option, ), @@ -619,6 +618,7 @@ fn from_user_val(arg: &KclValue) -> Option { impl_from_arg_via_json!(super::sketch::AngledLineData); impl_from_arg_via_json!(super::sketch::AngledLineToData); impl_from_arg_via_json!(super::sketch::AngledLineThatIntersectsData); +impl_from_arg_via_json!(super::shapes::CircleData); impl_from_arg_via_json!(super::sketch::ArcData); impl_from_arg_via_json!(super::sketch::TangentialArcData); impl_from_arg_via_json!(super::sketch::BezierData); diff --git a/src/wasm-lib/kcl/src/std/convert.rs b/src/wasm-lib/kcl/src/std/convert.rs index 8ce353b4fe..5c2e5bfb1d 100644 --- a/src/wasm-lib/kcl/src/std/convert.rs +++ b/src/wasm-lib/kcl/src/std/convert.rs @@ -50,7 +50,7 @@ pub async fn int(args: Args) -> Result { /// /// ```no_run /// const sketch001 = startSketchOn('XZ') -/// |> circle([0, 0], 2, %) +/// |> circle({ center: [0, 0], radius: 2 }, %) /// const extrude001 = extrude(5, sketch001) /// /// const pattern01 = patternTransform(int(ceil(5 / 2)), (id) => { diff --git a/src/wasm-lib/kcl/src/std/extrude.rs b/src/wasm-lib/kcl/src/std/extrude.rs index b62e9075ec..b5a43760d0 100644 --- a/src/wasm-lib/kcl/src/std/extrude.rs +++ b/src/wasm-lib/kcl/src/std/extrude.rs @@ -231,7 +231,7 @@ pub(crate) async fn do_post_extrude( .flat_map(|path| { if let Some(Some(actual_face_id)) = face_id_map.get(&path.get_base().geo_meta.id) { match path { - Path::TangentialArc { .. } | Path::TangentialArcTo { .. } => { + Path::TangentialArc { .. } | Path::TangentialArcTo { .. } | Path::Circle { .. } => { let extrude_surface = ExtrudeSurface::ExtrudeArc(crate::executor::ExtrudeArc { face_id: *actual_face_id, tag: path.get_base().tag.clone(), diff --git a/src/wasm-lib/kcl/src/std/helix.rs b/src/wasm-lib/kcl/src/std/helix.rs index 220bf650b2..b0a9074070 100644 --- a/src/wasm-lib/kcl/src/std/helix.rs +++ b/src/wasm-lib/kcl/src/std/helix.rs @@ -42,7 +42,7 @@ pub async fn helix(args: Args) -> Result { /// /// ```no_run /// const part001 = startSketchOn('XY') -/// |> circle([5, 5], 10, %) +/// |> circle({ center: [5, 5], radius: 10 }, %) /// |> extrude(10, %) /// |> helix({ /// angleStart: 0, diff --git a/src/wasm-lib/kcl/src/std/loft.rs b/src/wasm-lib/kcl/src/std/loft.rs index 57cf8f16db..885e7797f8 100644 --- a/src/wasm-lib/kcl/src/std/loft.rs +++ b/src/wasm-lib/kcl/src/std/loft.rs @@ -91,10 +91,10 @@ pub async fn loft(args: Args) -> Result { /// |> close(%) /// /// const circleSketch0 = startSketchOn(offsetPlane('XY', 75)) -/// |> circle([0, 100], 50, %) +/// |> circle({ center: [0, 100], radius: 50 }, %) /// /// const circleSketch1 = startSketchOn(offsetPlane('XY', 150)) -/// |> circle([0, 100], 20, %) +/// |> circle({ center: [0, 100], radius: 20 }, %) /// /// loft([squareSketch, circleSketch0, circleSketch1]) /// ``` @@ -110,10 +110,10 @@ pub async fn loft(args: Args) -> Result { /// |> close(%) /// /// const circleSketch0 = startSketchOn(offsetPlane('XY', 75)) -/// |> circle([0, 100], 50, %) +/// |> circle({ center: [0, 100], radius: 50 }, %) /// /// const circleSketch1 = startSketchOn(offsetPlane('XY', 150)) -/// |> circle([0, 100], 20, %) +/// |> circle({ center: [0, 100], radius: 20 }, %) /// /// loft([squareSketch, circleSketch0, circleSketch1], { /// // This can be set to override the automatically determined diff --git a/src/wasm-lib/kcl/src/std/math.rs b/src/wasm-lib/kcl/src/std/math.rs index d53307c171..e833c5b09d 100644 --- a/src/wasm-lib/kcl/src/std/math.rs +++ b/src/wasm-lib/kcl/src/std/math.rs @@ -113,7 +113,7 @@ pub async fn pi(args: Args) -> Result { /// const circumference = 70 /// /// const exampleSketch = startSketchOn("XZ") -/// |> circle([0, 0], circumference/ (2 * pi()), %) +/// |> circle({ center: [0, 0], radius: circumference/ (2 * pi()) }, %) /// /// const example = extrude(5, exampleSketch) /// ``` diff --git a/src/wasm-lib/kcl/src/std/mod.rs b/src/wasm-lib/kcl/src/std/mod.rs index 97e6f3c28e..534039c277 100644 --- a/src/wasm-lib/kcl/src/std/mod.rs +++ b/src/wasm-lib/kcl/src/std/mod.rs @@ -37,7 +37,7 @@ use crate::{ ast::types::FunctionExpression, docs::StdLibFn, errors::KclError, - executor::{KclValue, ProgramMemory, SketchGroup, SketchSurface}, + executor::{KclValue, ProgramMemory}, std::kcl_stdlib::KclStdLibFn, }; diff --git a/src/wasm-lib/kcl/src/std/patterns.rs b/src/wasm-lib/kcl/src/std/patterns.rs index 5929e1568f..f5b1c3475e 100644 --- a/src/wasm-lib/kcl/src/std/patterns.rs +++ b/src/wasm-lib/kcl/src/std/patterns.rs @@ -117,7 +117,7 @@ pub async fn pattern_transform(args: Args) -> Result { /// // Each layer is just a pretty thin cylinder. /// fn layer = () => { /// return startSketchOn("XY") // or some other plane idk -/// |> circle([0, 0], 1, %, $tag1) +/// |> circle({ center: [0, 0], radius: 1 }, %, $tag1) /// |> extrude(h, %) /// } /// // The vase is 100 layers tall. @@ -318,7 +318,7 @@ pub async fn pattern_linear_2d(args: Args) -> Result { /// /// ```no_run /// const exampleSketch = startSketchOn('XZ') -/// |> circle([0, 0], 1, %) +/// |> circle({ center: [0, 0], radius: 1 }, %) /// |> patternLinear2d({ /// axis: [1, 0], /// repetitions: 6, @@ -651,7 +651,7 @@ pub async fn pattern_circular_3d(args: Args) -> Result { /// /// ```no_run /// const exampleSketch = startSketchOn('XZ') -/// |> circle([0, 0], 1, %) +/// |> circle({ center: [0, 0], radius: 1 }, %) /// /// const example = extrude(-5, exampleSketch) /// |> patternCircular3d({ diff --git a/src/wasm-lib/kcl/src/std/planes.rs b/src/wasm-lib/kcl/src/std/planes.rs index 47ad5c3795..cb41f7c26e 100644 --- a/src/wasm-lib/kcl/src/std/planes.rs +++ b/src/wasm-lib/kcl/src/std/planes.rs @@ -77,7 +77,7 @@ pub async fn offset_plane(args: Args) -> Result { /// |> close(%) /// /// const circleSketch = startSketchOn(offsetPlane('XY', 150)) -/// |> circle([0, 100], 50, %) +/// |> circle({ center: [0, 100], radius: 50 }, %) /// /// loft([squareSketch, circleSketch]) /// ``` @@ -93,7 +93,7 @@ pub async fn offset_plane(args: Args) -> Result { /// |> close(%) /// /// const circleSketch = startSketchOn(offsetPlane('XZ', 150)) -/// |> circle([0, 100], 50, %) +/// |> circle({ center: [0, 100], radius: 50 }, %) /// /// loft([squareSketch, circleSketch]) /// ``` @@ -109,7 +109,7 @@ pub async fn offset_plane(args: Args) -> Result { /// |> close(%) /// /// const circleSketch = startSketchOn(offsetPlane('YZ', 150)) -/// |> circle([0, 100], 50, %) +/// |> circle({ center: [0, 100], radius: 50 }, %) /// /// loft([squareSketch, circleSketch]) /// ``` @@ -125,7 +125,7 @@ pub async fn offset_plane(args: Args) -> Result { /// |> close(%) /// /// const circleSketch = startSketchOn(offsetPlane('-XZ', -150)) -/// |> circle([0, 100], 50, %) +/// |> circle({ center: [0, 100], radius: 50 }, %) /// /// loft([squareSketch, circleSketch]) /// ``` diff --git a/src/wasm-lib/kcl/src/std/revolve.rs b/src/wasm-lib/kcl/src/std/revolve.rs index ffdffc7a4c..8727838db7 100644 --- a/src/wasm-lib/kcl/src/std/revolve.rs +++ b/src/wasm-lib/kcl/src/std/revolve.rs @@ -133,7 +133,7 @@ pub async fn revolve(args: Args) -> Result { /// ```no_run /// // A donut shape. /// const sketch001 = startSketchOn('XY') -/// |> circle([15, 0], 5, %) +/// |> circle({ center: [15, 0], radius: 5 }, %) /// |> revolve({ /// angle: 360, /// axis: 'y' @@ -185,7 +185,7 @@ pub async fn revolve(args: Args) -> Result { /// |> extrude(20, %) /// /// const sketch001 = startSketchOn(box, "END") -/// |> circle([10,10], 4, %) +/// |> circle({ center: [10,10], radius: 4 }, %) /// |> revolve({ /// angle: -90, /// axis: 'y' @@ -202,7 +202,7 @@ pub async fn revolve(args: Args) -> Result { /// |> extrude(20, %) /// /// const sketch001 = startSketchOn(box, "END") -/// |> circle([10,10], 4, %) +/// |> circle({ center: [10,10], radius: 4 }, %) /// |> revolve({ /// angle: 90, /// axis: getOppositeEdge(revolveAxis) @@ -219,7 +219,7 @@ pub async fn revolve(args: Args) -> Result { /// |> extrude(20, %) /// /// const sketch001 = startSketchOn(box, "END") -/// |> circle([10,10], 4, %) +/// |> circle({ center: [10,10], radius: 4 }, %) /// |> revolve({ /// angle: 90, /// axis: getOppositeEdge(revolveAxis), diff --git a/src/wasm-lib/kcl/src/std/shapes.rs b/src/wasm-lib/kcl/src/std/shapes.rs index 1c80c2d9d4..7807ff378f 100644 --- a/src/wasm-lib/kcl/src/std/shapes.rs +++ b/src/wasm-lib/kcl/src/std/shapes.rs @@ -2,14 +2,15 @@ use anyhow::Result; use derive_docs::stdlib; +use kittycad::types::{Angle, ModelingCmd}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use crate::{ ast::types::TagDeclarator, - errors::KclError, - executor::KclValue, - std::{Args, SketchGroup, SketchSurface}, + errors::{KclError, KclErrorDetails}, + executor::{BasePath, GeoMeta, KclValue, Path, SketchGroup, SketchSurface}, + std::Args, }; /// A sketch surface or a sketch group. @@ -21,12 +22,24 @@ pub enum SketchSurfaceOrGroup { SketchGroup(Box), } +/// Data for drawing an angled line that intersects with a given line. +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] +#[ts(export)] +#[serde(rename_all = "camelCase")] +// TODO: make sure the docs on the args below are correct. +pub struct CircleData { + /// The center of the circle. + pub center: [f64; 2], + /// The circle radius + pub radius: f64, +} + /// Sketch a circle. pub async fn circle(args: Args) -> Result { - let (center, radius, sketch_surface_or_group, tag): ([f64; 2], f64, SketchSurfaceOrGroup, Option) = + let (data, sketch_surface_or_group, tag): (CircleData, SketchSurfaceOrGroup, Option) = args.get_circle_args()?; - let sketch_group = inner_circle(center, radius, sketch_surface_or_group, tag, args).await?; + let sketch_group = inner_circle(data, sketch_surface_or_group, tag, args).await?; Ok(KclValue::new_user_val(sketch_group.meta.clone(), sketch_group)) } @@ -35,7 +48,7 @@ pub async fn circle(args: Args) -> Result { /// /// ```no_run /// const exampleSketch = startSketchOn("-XZ") -/// |> circle([0, 0], 10, %) +/// |> circle({ center: [0, 0], radius: 10 }, %) /// /// const example = extrude(5, exampleSketch) /// ``` @@ -47,7 +60,7 @@ pub async fn circle(args: Args) -> Result { /// |> line([0, 30], %) /// |> line([-30, 0], %) /// |> close(%) -/// |> hole(circle([0, 15], 5, %), %) +/// |> hole(circle({ center: [0, 15], radius: 5 }, %), %) /// /// const example = extrude(5, exampleSketch) /// ``` @@ -55,8 +68,7 @@ pub async fn circle(args: Args) -> Result { name = "circle", }] async fn inner_circle( - center: [f64; 2], - radius: f64, + data: CircleData, sketch_surface_or_group: SketchSurfaceOrGroup, tag: Option, args: Args, @@ -65,23 +77,70 @@ async fn inner_circle( SketchSurfaceOrGroup::SketchSurface(surface) => surface, SketchSurfaceOrGroup::SketchGroup(group) => group.on, }; - let mut sketch_group = - crate::std::sketch::inner_start_profile_at([center[0] + radius, center[1]], sketch_surface, None, args.clone()) - .await?; - - // Call arc. - sketch_group = crate::std::sketch::inner_arc( - crate::std::sketch::ArcData::AnglesAndRadius { - angle_start: 0.0, - angle_end: 360.0, - radius, - }, - sketch_group, - tag, + let sketch_group = crate::std::sketch::inner_start_profile_at( + [data.center[0] + data.radius, data.center[1]], + sketch_surface, + None, args.clone(), ) .await?; - // Call close. - crate::std::sketch::inner_close(sketch_group, None, args).await + let angle_start = Angle::from_degrees(0.0); + let angle_end = Angle::from_degrees(360.0); + + if angle_start == angle_end { + return Err(KclError::Type(KclErrorDetails { + message: "Arc start and end angles must be different".to_string(), + source_ranges: vec![args.source_range], + })); + } + + let id = uuid::Uuid::new_v4(); + + args.batch_modeling_cmd( + id, + ModelingCmd::ExtendPath { + path: sketch_group.id, + segment: kittycad::types::PathSegment::Arc { + start: angle_start, + end: angle_end, + center: data.center.into(), + radius: data.radius, + relative: false, + }, + }, + ) + .await?; + + let current_path = Path::Circle { + base: BasePath { + from: data.center, + to: data.center, + tag: tag.clone(), + geo_meta: GeoMeta { + id, + metadata: args.source_range.into(), + }, + }, + radius: data.radius, + center: data.center, + ccw: angle_start.degrees() < angle_end.degrees(), + }; + + let mut new_sketch_group = sketch_group.clone(); + if let Some(tag) = &tag { + new_sketch_group.add_tag(tag, ¤t_path); + } + + new_sketch_group.value.push(current_path); + + args.batch_modeling_cmd( + id, + ModelingCmd::ClosePath { + path_id: new_sketch_group.id, + }, + ) + .await?; + + Ok(new_sketch_group) } diff --git a/src/wasm-lib/kcl/src/std/sketch.rs b/src/wasm-lib/kcl/src/std/sketch.rs index e62cc719e2..d65ead8743 100644 --- a/src/wasm-lib/kcl/src/std/sketch.rs +++ b/src/wasm-lib/kcl/src/std/sketch.rs @@ -2059,8 +2059,8 @@ pub async fn hole(args: Args) -> Result { /// |> line([5, 0], %) /// |> line([0, -5], %) /// |> close(%) -/// |> hole(circle([1, 1], .25, %), %) -/// |> hole(circle([1, 4], .25, %), %) +/// |> hole(circle({ center: [1, 1], radius: .25 }, %), %) +/// |> hole(circle({ center: [1, 4], radius: .25 }, %), %) /// /// const example = extrude(1, exampleSketch) /// ``` @@ -2077,7 +2077,7 @@ pub async fn hole(args: Args) -> Result { /// } /// /// const exampleSketch = startSketchOn('-XZ') -/// |> circle([0, 0], 3, %) +/// |> circle({ center: [0, 0], radius: 3 }, %) /// |> hole(squareHoleSketch(), %) /// const example = extrude(1, exampleSketch) /// ``` diff --git a/src/wasm-lib/kcl/src/unparser.rs b/src/wasm-lib/kcl/src/unparser.rs index a401d273ff..c13c9c1d25 100644 --- a/src/wasm-lib/kcl/src/unparser.rs +++ b/src/wasm-lib/kcl/src/unparser.rs @@ -1028,10 +1028,10 @@ const tabs_r = startSketchOn({ |> line([0, -10], %) |> line([-10, -5], %) |> close(%) - |> hole(circle([ + |> hole(circle({ center: [ width / 2 + thk + hole_diam, length / 2 - hole_diam - ], hole_diam / 2, %), %) + ], radius: hole_diam / 2 }, %), %) |> extrude(-thk, %) |> patternLinear3d({ axis: [0, -1, 0], @@ -1052,10 +1052,10 @@ const tabs_l = startSketchOn({ |> line([0, -10], %) |> line([10, -5], %) |> close(%) - |> hole(circle([ + |> hole(circle({ center: [ -width / 2 - thk - hole_diam, length / 2 - hole_diam - ], hole_diam / 2, %), %) + ], radius: hole_diam / 2 }, %), %) |> extrude(-thk, %) |> patternLinear3d({ axis: [0, -1, 0], @@ -1148,10 +1148,10 @@ const tabs_r = startSketchOn({ |> line([0, -10], %) |> line([-10, -5], %) |> close(%) - |> hole(circle([ + |> hole(circle({ center: [ width / 2 + thk + hole_diam, length / 2 - hole_diam - ], hole_diam / 2, %), %) + ], radius: hole_diam / 2 }, %), %) |> extrude(-thk, %) |> patternLinear3d({ axis: [0, -1, 0], @@ -1172,10 +1172,10 @@ const tabs_l = startSketchOn({ |> line([0, -10], %) |> line([10, -5], %) |> close(%) - |> hole(circle([ + |> hole(circle({ center: [ -width / 2 - thk - hole_diam, length / 2 - hole_diam - ], hole_diam / 2, %), %) + ], radius: hole_diam / 2 }, %), %) |> extrude(-thk, %) |> patternLinear3d({ axis: [0, -1, 0], diff --git a/src/wasm-lib/kcl/tests/outputs/serial_test_example_get_next_adjacent_edge0.png b/src/wasm-lib/kcl/tests/outputs/serial_test_example_get_next_adjacent_edge0.png index a61f6b8af8..debdd74470 100644 Binary files a/src/wasm-lib/kcl/tests/outputs/serial_test_example_get_next_adjacent_edge0.png and b/src/wasm-lib/kcl/tests/outputs/serial_test_example_get_next_adjacent_edge0.png differ diff --git a/src/wasm-lib/kcl/tests/outputs/serial_test_example_get_previous_adjacent_edge0.png b/src/wasm-lib/kcl/tests/outputs/serial_test_example_get_previous_adjacent_edge0.png index debdd74470..a61f6b8af8 100644 Binary files a/src/wasm-lib/kcl/tests/outputs/serial_test_example_get_previous_adjacent_edge0.png and b/src/wasm-lib/kcl/tests/outputs/serial_test_example_get_previous_adjacent_edge0.png differ diff --git a/src/wasm-lib/kcl/tests/outputs/serial_test_example_loft1.png b/src/wasm-lib/kcl/tests/outputs/serial_test_example_loft1.png index a44145b368..25dc6517cc 100644 Binary files a/src/wasm-lib/kcl/tests/outputs/serial_test_example_loft1.png and b/src/wasm-lib/kcl/tests/outputs/serial_test_example_loft1.png differ diff --git a/src/wasm-lib/kcl/tests/outputs/serial_test_example_loft2.png b/src/wasm-lib/kcl/tests/outputs/serial_test_example_loft2.png index a44145b368..25dc6517cc 100644 Binary files a/src/wasm-lib/kcl/tests/outputs/serial_test_example_loft2.png and b/src/wasm-lib/kcl/tests/outputs/serial_test_example_loft2.png differ diff --git a/src/wasm-lib/kcl/tests/outputs/serial_test_example_offset_plane0.png b/src/wasm-lib/kcl/tests/outputs/serial_test_example_offset_plane0.png index 35e17abd94..37ec6fb46f 100644 Binary files a/src/wasm-lib/kcl/tests/outputs/serial_test_example_offset_plane0.png and b/src/wasm-lib/kcl/tests/outputs/serial_test_example_offset_plane0.png differ diff --git a/src/wasm-lib/tests/executor/inputs/cylinder.kcl b/src/wasm-lib/tests/executor/inputs/cylinder.kcl index 6304c62133..f3800be1c7 100644 --- a/src/wasm-lib/tests/executor/inputs/cylinder.kcl +++ b/src/wasm-lib/tests/executor/inputs/cylinder.kcl @@ -1,3 +1,3 @@ const cylinder = startSketchOn('XY') - |> circle([0,0], 22, %) + |> circle({ center: [0, 0], radius: 22 }, %) |> extrude(14, %) diff --git a/src/wasm-lib/tests/executor/inputs/fillet-and-shell.kcl b/src/wasm-lib/tests/executor/inputs/fillet-and-shell.kcl index cd7dc2c5db..295f959b09 100644 --- a/src/wasm-lib/tests/executor/inputs/fillet-and-shell.kcl +++ b/src/wasm-lib/tests/executor/inputs/fillet-and-shell.kcl @@ -61,8 +61,8 @@ const case = startSketchOn('XY') fn m25Screw = (x, y, height) => { const screw = startSketchOn("XY") |> startProfileAt([0, 0], %) - |> circle([x, y], 2.5, %) - |> hole(circle([x, y], 1.25, %), %) + |> circle({ center: [x, y], radius: 2.5 }, %) + |> hole(circle({ center: [x, y], radius: 1.25 }, %), %) |> extrude(height, %) return screw } diff --git a/src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl b/src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl index c6d2b9a0c3..047127d433 100644 --- a/src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl +++ b/src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl @@ -1,6 +1,7 @@ // A mounting bracket for the Focusrite Scarlett Solo audio interface // This is a bracket that holds an audio device underneath a desk or shelf. The audio device has dimensions of 144mm wide, 80mm length and 45mm depth with fillets of 6mm. This mounting bracket is designed to be 3D printed with PLA material + // define constants in mm const radius = 6.0 const width = 144.0 @@ -79,10 +80,13 @@ const tabsR = startSketchOn(tabPlane) |> line([0, -tabLength / 3 * 2], %, $edge12) |> line([-tabWidth, -tabLength / 3], %, $edge13) |> close(%, $edge14) - |> hole(circle([ - width / 2 + thk + tabWidth / 2, - length / 2 + thk - (tabLength / (3 / 2)) - ], holeDiam / 2, %), %) + |> hole(circle({ + center: [ + width / 2 + thk + tabWidth / 2, + length / 2 + thk - (tabLength / (3 / 2)) + ], + radius: holeDiam / 2 + }, %), %) |> extrude(-tabThk, %) |> fillet({ radius: holeDiam / 2, @@ -104,10 +108,13 @@ const tabsL = startSketchOn(tabPlane) |> line([0, -tabLength / 3 * 2], %, $edge22) |> line([tabWidth, -tabLength / 3], %, $edge23) |> close(%, $edge24) - |> hole(circle([ - -width / 2 - thk - (tabWidth / 2), - length / 2 + thk - (tabLength / (3 / 2)) - ], holeDiam / 2, %), %) + |> hole(circle({ + center: [ + -width / 2 - thk - (tabWidth / 2), + length / 2 + thk - (tabLength / (3 / 2)) + ], + radius: holeDiam / 2 + }, %), %) |> extrude(-tabThk, %) |> fillet({ radius: holeDiam / 2, diff --git a/src/wasm-lib/tests/executor/inputs/global-tags.kcl b/src/wasm-lib/tests/executor/inputs/global-tags.kcl index 9781831705..31dcd9105a 100644 --- a/src/wasm-lib/tests/executor/inputs/global-tags.kcl +++ b/src/wasm-lib/tests/executor/inputs/global-tags.kcl @@ -80,10 +80,13 @@ const tabsR = startSketchOn(tabPlane) |> line([0, -tabLength / 3 * 2], %, $edge12) |> line([-tabWidth, -tabLength / 3], %, $edge13) |> close(%, $edge14) - |> hole(circle([ - width / 2 + thk + tabWidth / 2, - length / 2 + thk - (tabLength / (3 / 2)) - ], holeDiam / 2, %), %) + |> hole(circle({ + center: [ + width / 2 + thk + tabWidth / 2, + length / 2 + thk - (tabLength / (3 / 2)) + ], + radius: holeDiam / 2 + }, %), %) |> extrude(-tabThk, %) |> fillet({ radius: holeDiam / 2, @@ -105,10 +108,13 @@ const tabsL = startSketchOn(tabPlane) |> line([0, -tabLength / 3 * 2], %, $edge22) |> line([tabWidth, -tabLength / 3], %, $edge23) |> close(%, $edge24) - |> hole(circle([ - -width / 2 - thk - (tabWidth / 2), - length / 2 + thk - (tabLength / (3 / 2)) - ], holeDiam / 2, %), %) + |> hole(circle({ + center: [ + -width / 2 - thk - (tabWidth / 2), + length / 2 + thk - (tabLength / (3 / 2)) + ], + radius: holeDiam / 2 + }, %), %) |> extrude(-tabThk, %) |> fillet({ radius: holeDiam / 2, diff --git a/src/wasm-lib/tests/executor/inputs/helix_ccw.kcl b/src/wasm-lib/tests/executor/inputs/helix_ccw.kcl index 6975ab7b93..76f31c6df3 100644 --- a/src/wasm-lib/tests/executor/inputs/helix_ccw.kcl +++ b/src/wasm-lib/tests/executor/inputs/helix_ccw.kcl @@ -1,4 +1,4 @@ const part001 = startSketchOn('XY') - |> circle([5, 5], 10, %) + |> circle({ center: [5, 5], radius: 10 }, %) |> extrude(10, %) |> helix({revolutions: 16, angle_start: 0, ccw: true}, %) diff --git a/src/wasm-lib/tests/executor/inputs/helix_defaults.kcl b/src/wasm-lib/tests/executor/inputs/helix_defaults.kcl index e85eab8de5..0c66510377 100644 --- a/src/wasm-lib/tests/executor/inputs/helix_defaults.kcl +++ b/src/wasm-lib/tests/executor/inputs/helix_defaults.kcl @@ -1,4 +1,4 @@ const part001 = startSketchOn('XY') - |> circle([5, 5], 10, %) + |> circle({ center: [5, 5], radius: 10 }, %) |> extrude(10, %) |> helix({revolutions: 16, angle_start: 0}, %) diff --git a/src/wasm-lib/tests/executor/inputs/helix_defaults_negative_extrude.kcl b/src/wasm-lib/tests/executor/inputs/helix_defaults_negative_extrude.kcl index 9a2f3efc1b..ab26043c8e 100644 --- a/src/wasm-lib/tests/executor/inputs/helix_defaults_negative_extrude.kcl +++ b/src/wasm-lib/tests/executor/inputs/helix_defaults_negative_extrude.kcl @@ -1,4 +1,4 @@ const part001 = startSketchOn('XY') - |> circle([5, 5], 10, %) + |> circle({ center: [5, 5], radius: 10 }, %) |> extrude(-10, %) |> helix({revolutions: 16, angle_start: 0}, %) diff --git a/src/wasm-lib/tests/executor/inputs/helix_with_length.kcl b/src/wasm-lib/tests/executor/inputs/helix_with_length.kcl index 1847d4ad05..7818e3c5d8 100644 --- a/src/wasm-lib/tests/executor/inputs/helix_with_length.kcl +++ b/src/wasm-lib/tests/executor/inputs/helix_with_length.kcl @@ -1,4 +1,4 @@ const part001 = startSketchOn('XY') - |> circle([5, 5], 10, %) + |> circle({ center: [5, 5], radius: 10 }, %) |> extrude(10, %) |> helix({revolutions: 16, angle_start: 0, length: 3}, %) diff --git a/src/wasm-lib/tests/executor/inputs/lego.kcl b/src/wasm-lib/tests/executor/inputs/lego.kcl index 26099987e9..88a5b8d799 100644 --- a/src/wasm-lib/tests/executor/inputs/lego.kcl +++ b/src/wasm-lib/tests/executor/inputs/lego.kcl @@ -39,10 +39,10 @@ const shellExtrude = startSketchOn(s, "start") |> extrude(-(height - t), %) const peg = startSketchOn(s, "end") - |> circle([ + |> circle({ center: [ -(total_width / 2 - wSegments), -(total_length / 2 - lSegments) - ], bumpDiam / 2, %) + ], radius: bumpDiam / 2 }, %) |> patternLinear2d({ axis: [1, 0], repetitions: 5, diff --git a/src/wasm-lib/tests/executor/inputs/pattern_vase.kcl b/src/wasm-lib/tests/executor/inputs/pattern_vase.kcl index 24f52bffba..c6edfd9862 100644 --- a/src/wasm-lib/tests/executor/inputs/pattern_vase.kcl +++ b/src/wasm-lib/tests/executor/inputs/pattern_vase.kcl @@ -14,7 +14,7 @@ fn transform = (replicaId) => { // Each layer is just a pretty thin cylinder with a fillet. fn layer = () => { return startSketchOn("XY") // or some other plane idk - |> circle([0, 0], 1, %, $tag1) + |> circle({ center: [0, 0], radius: 1 }, %, $tag1) |> extrude(h, %) // |> fillet({ // radius: h / 2.01, diff --git a/src/wasm-lib/tests/executor/inputs/server-rack-heavy.kcl b/src/wasm-lib/tests/executor/inputs/server-rack-heavy.kcl index b16c9c075d..1170bc2940 100644 --- a/src/wasm-lib/tests/executor/inputs/server-rack-heavy.kcl +++ b/src/wasm-lib/tests/executor/inputs/server-rack-heavy.kcl @@ -30,22 +30,22 @@ fn caster = (originStart) => { |> xLine(-3.543, %) |> lineTo([profileStartX(%), profileStartY(%)], %) |> close(%) - |> hole(circle([ + |> hole(circle({ center: [ (3.543 - 2.756) / 2, (3.543 - 2.756) / 2 - ], 8.8 / 2 / 25.4, %), %) - |> hole(circle([ + ], radius: 8.8 / 2 / 25.4}, %), %) + |> hole(circle({ center: [ (3.543 - 2.756) / 2 + 2.756, (3.543 - 2.756) / 2 - ], 8.8 / 2 / 25.4, %), %) - |> hole(circle([ + ], radius: 8.8 / 2 / 25.4 }, %), %) + |> hole(circle({ center: [ (3.543 - 2.756) / 2, (3.543 - 2.756) / 2 + 2.756 - ], 8.8 / 2 / 25.4, %), %) - |> hole(circle([ + ], radius: 8.8 / 2 / 25.4 }, %), %) + |> hole(circle({ center: [ (3.543 - 2.756) / 2 + 2.756, (3.543 - 2.756) / 2 + 2.756 - ], 8.8 / 2 / 25.4, %), %) + ], radius: 8.8 / 2 / 25.4 }, %), %) |> extrude(-.25, %) const sketch002c = startSketchOn(sketch001c, 'START') @@ -71,7 +71,7 @@ fn caster = (originStart) => { } } const sketch003c = startSketchOn(plane002c) - |> circle([0, 1.2], 2.48 / 2, %) + |> circle({ center: [0, 1.2], radius 2.48 / 2 }, %) const examplec = extrude(-1 - (3 / 16), sketch003c) return examplec } diff --git a/src/wasm-lib/tests/executor/inputs/server-rack-lite.kcl b/src/wasm-lib/tests/executor/inputs/server-rack-lite.kcl index 46f0ca6509..4a0b4c8640 100644 --- a/src/wasm-lib/tests/executor/inputs/server-rack-lite.kcl +++ b/src/wasm-lib/tests/executor/inputs/server-rack-lite.kcl @@ -28,22 +28,22 @@ fn caster = (originStart) => { |> xLine(-3.543, %) |> lineTo([profileStartX(%), profileStartY(%)], %) |> close(%) - |> hole(circle([ + |> hole(circle({ center: [ (3.543 - 2.756) / 2, (3.543 - 2.756) / 2 - ], 8.8 / 2 / 25.4, %), %) - |> hole(circle([ + ], radius: 8.8 / 2 / 25.4 }, %), %) + |> hole(circle({ center: [ (3.543 - 2.756) / 2 + 2.756, (3.543 - 2.756) / 2 - ], 8.8 / 2 / 25.4, %), %) - |> hole(circle([ + ], radius: 8.8 / 2 / 25.4 }, %), %) + |> hole(circle({ center: [ (3.543 - 2.756) / 2, (3.543 - 2.756) / 2 + 2.756 - ], 8.8 / 2 / 25.4, %), %) - |> hole(circle([ + ], radius: 8.8 / 2 / 25.4 }, %), %) + |> hole(circle({ center: [ (3.543 - 2.756) / 2 + 2.756, (3.543 - 2.756) / 2 + 2.756 - ], 8.8 / 2 / 25.4, %), %) + ], radius: 8.8 / 2 / 25.4 }, %), %) |> extrude(-.25, %) const sketch002c = startSketchOn(sketch001c, 'START') @@ -69,7 +69,7 @@ fn caster = (originStart) => { } } const sketch003c = startSketchOn(plane002c) - |> circle([0, 1.2], 2.48 / 2, %) + |> circle({ center: [0, 1.2], radisu: 2.48 / 2 }, %) const examplec = extrude(-1 - (3 / 16), sketch003c) return examplec } diff --git a/src/wasm-lib/tests/executor/inputs/sketch_on_face_circle_tagged.kcl b/src/wasm-lib/tests/executor/inputs/sketch_on_face_circle_tagged.kcl index c4df4848b6..9b37a5d1ef 100644 --- a/src/wasm-lib/tests/executor/inputs/sketch_on_face_circle_tagged.kcl +++ b/src/wasm-lib/tests/executor/inputs/sketch_on_face_circle_tagged.kcl @@ -12,5 +12,5 @@ const part001 = cube([0,0], 20) |> extrude(20, %) const part002 = startSketchOn(part001, "end") - |> circle([0, 0], 5, %, $myCircle) + |> circle({ center: [0, 0], radisu: 5 }, %, $myCircle) |> extrude(5, %) diff --git a/src/wasm-lib/tests/executor/inputs/slow_lego.kcl.tmpl b/src/wasm-lib/tests/executor/inputs/slow_lego.kcl.tmpl index a8aae57f25..8c1b291562 100644 --- a/src/wasm-lib/tests/executor/inputs/slow_lego.kcl.tmpl +++ b/src/wasm-lib/tests/executor/inputs/slow_lego.kcl.tmpl @@ -62,10 +62,10 @@ fn tr = (i) => { // Create the pegs on the top of the base const totalBumps = (wbumps * lbumps)-1 const peg = startSketchOn(s, 'end') - |> circle([ + |> circle({ center: [ -(pitch*(wbumps-1)/2), -(pitch*(lbumps-1)/2) - ], bumpDiam / 2, %) + ], radius: bumpDiam / 2 }, %) |> patternLinear2d({ axis: [1, 0], repetitions: wbumps-1, diff --git a/src/wasm-lib/tests/executor/main.rs b/src/wasm-lib/tests/executor/main.rs index 66f3c18e41..8fa6025839 100644 --- a/src/wasm-lib/tests/executor/main.rs +++ b/src/wasm-lib/tests/executor/main.rs @@ -301,8 +301,8 @@ async fn kcl_test_holes() { |> line([10, 0], %) |> line([0, -10], %) |> close(%) - |> hole(circle([2, 2], .5, %), %) - |> hole(circle([2, 8], .5, %), %) + |> hole(circle({ center: [2, 2], radius: .5 }, %), %) + |> hole(circle({ center: [2, 8], radius: .5 }, %), %) |> extrude(2, %) "#; @@ -354,10 +354,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({ center: [-holeIndex, holeIndex], radius: holeRadius }, %), %) + |> hole(circle({ center: [holeIndex, holeIndex], radius: holeRadius }, %), %) + |> hole(circle({ center: [-holeIndex, -holeIndex], radius: holeRadius }, %), %) + |> hole(circle({ center: [holeIndex, -holeIndex], radius: holeRadius }, %), %) |> extrude(2, %) "#; @@ -367,7 +367,7 @@ const part = roundedRectangle([0, 0], 20, 20, 4) #[tokio::test(flavor = "multi_thread")] async fn kcl_test_top_level_expression() { - let code = r#"startSketchOn('XY') |> circle([0,0], 22, %) |> extrude(14, %)"#; + let code = r#"startSketchOn('XY') |> circle({ center: [0,0], radius: 22 }, %) |> extrude(14, %)"#; let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); assert_out("top_level_expression", &result); @@ -378,7 +378,7 @@ async fn kcl_test_patterns_linear_basic_with_math() { let code = r#"const num = 12 const distance = 5 const part = startSketchOn('XY') - |> circle([0,0], 2, %) + |> circle({ center: [0,0], radius: 2 }, %) |> patternLinear2d({axis: [0,1], repetitions: num -1, distance: distance - 1}, %) |> extrude(1, %) "#; @@ -390,7 +390,7 @@ const part = startSketchOn('XY') #[tokio::test(flavor = "multi_thread")] async fn kcl_test_patterns_linear_basic() { let code = r#"const part = startSketchOn('XY') - |> circle([0,0], 2, %) + |> circle({ center: [0,0], radius: 2 }, %) |> patternLinear2d({axis: [0,1], repetitions: 12, distance: 4}, %) |> extrude(1, %) "#; @@ -418,7 +418,7 @@ async fn kcl_test_patterns_linear_basic_3d() { #[tokio::test(flavor = "multi_thread")] async fn kcl_test_patterns_linear_basic_negative_distance() { let code = r#"const part = startSketchOn('XY') - |> circle([0,0], 2, %) + |> circle({ center: [0,0], radius: 2 }, %) |> patternLinear2d({axis: [0,1], repetitions: 12, distance: -2}, %) |> extrude(1, %) "#; @@ -430,7 +430,7 @@ async fn kcl_test_patterns_linear_basic_negative_distance() { #[tokio::test(flavor = "multi_thread")] async fn kcl_test_patterns_linear_basic_negative_axis() { let code = r#"const part = startSketchOn('XY') - |> circle([0,0], 2, %) + |> circle({ center: [0,0], radius: 2 }, %) |> patternLinear2d({axis: [0,-1], repetitions: 12, distance: 2}, %) |> extrude(1, %) "#; @@ -442,7 +442,7 @@ async fn kcl_test_patterns_linear_basic_negative_axis() { #[tokio::test(flavor = "multi_thread")] async fn kcl_test_patterns_linear_basic_holes() { let code = r#"const circles = startSketchOn('XY') - |> circle([5, 5], 1, %) + |> circle({ center: [5, 5], radius: 1 }, %) |> patternLinear2d({axis: [1,1], repetitions: 12, distance: 3}, %) const rectangle = startSketchOn('XY') @@ -463,7 +463,7 @@ const rectangle = startSketchOn('XY') #[tokio::test(flavor = "multi_thread")] async fn kcl_test_patterns_circular_basic_2d() { let code = r#"const part = startSketchOn('XY') - |> circle([0,0], 2, %) + |> circle({ center: [0,0], radius: 2 }, %) |> patternCircular2d({center: [20, 20], repetitions: 12, arcDegrees: 210, rotateDuplicates: true}, %) |> extrude(1, %) "#; @@ -787,8 +787,8 @@ async fn kcl_test_stdlib_kcl_error_right_code_path() { |> line([10, 0], %) |> line([0, -10], %) |> close(%) - |> hole(circle([2, 2], .5), %) - |> hole(circle([2, 8], .5, %), %) + |> hole(circle({ center: [2, 2], radius: .5 }), %) + |> hole(circle({ center: [2, 8], radius: .5 }, %), %) |> extrude(2, %) "#; @@ -816,7 +816,7 @@ const part001 = cube([0,0], 20) |> extrude(20, %) const part002 = startSketchOn(part001, "end") - |> circle([0, 0], 5, %) + |> circle({ center: [0, 0], radius: 5 }, %) |> extrude(5, %) "#; @@ -1085,7 +1085,7 @@ async fn kcl_test_revolve_on_face_circle_edge() { |> extrude(20, %) const sketch001 = startSketchOn(box, "END") - |> circle([10,10], 4, %) + |> circle({ center: [10,10], radius: 4 }, %) |> revolve({ angle: 90, axis: getOppositeEdge(revolveAxis) @@ -1107,7 +1107,7 @@ async fn kcl_test_revolve_on_face_circle() { |> extrude(20, %) const sketch001 = startSketchOn(box, "END") - |> circle([10,10], 4, %) + |> circle({ center: [10,10], radius: 4 }, %) |> revolve({ angle: -90, axis: 'y' @@ -1147,7 +1147,7 @@ const sketch001 = startSketchOn(box, "end") #[tokio::test(flavor = "multi_thread")] async fn kcl_test_basic_revolve_circle() { let code = r#"const sketch001 = startSketchOn('XY') - |> circle([15, 0], 5, %) + |> circle({ center: [15, 0], radius: 5 }, %) |> revolve({ angle: 360, axis: 'y' @@ -1271,10 +1271,10 @@ async fn kcl_test_member_expression_in_params() { z_axis: { x: 0, y: 1, z: 0 } } }) - |> circle([0, 0], capDia / 2, %) + |> circle({ center: [0, 0], radius: capDia / 2 }, %) |> extrude(capHeadLength, %) const screw = startSketchOn(screwHead, "start") - |> circle([0, 0], dia / 2, %) + |> circle({ center: [0, 0], radius: dia / 2 }, %) |> extrude(length, %) return screw } @@ -1343,7 +1343,7 @@ async fn kcl_test_error_empty_start_sketch_on_string() { |> extrude(100, %) const secondSketch = startSketchOn(part001, '') - |> circle([-20, 50], 40, %) + |> circle({ center: [-20, 50], radius: 40 }, %) |> extrude(20, %) "#; @@ -1373,7 +1373,7 @@ fn squareHole = (l, w) => { } const extrusion = startSketchOn('XY') - |> circle([0, 0], dia/2, %) + |> circle({ center: [0, 0], radius: dia/2 }, %) |> hole(squareHole(length, width, height), %) |> extrude(height, %) "#;