Compare commits

..

1 Commits

Author SHA1 Message Date
71b7f27bce Revert "Revert "Remove Create with Text-to-CAD from toolbar, make commands in…"
This reverts commit 447069a97b.
2025-05-19 14:09:01 -04:00
425 changed files with 28824 additions and 81059 deletions

View File

@ -6,7 +6,6 @@ if [ -z "${TAB_API_URL:-}" ] || [ -z "${TAB_API_KEY:-}" ]; then
fi fi
project="https://github.com/KittyCAD/modeling-app" project="https://github.com/KittyCAD/modeling-app"
suite="${CI_SUITE:-unit}"
branch="${GITHUB_HEAD_REF:-${GITHUB_REF_NAME:-}}" branch="${GITHUB_HEAD_REF:-${GITHUB_REF_NAME:-}}"
commit="${CI_COMMIT_SHA:-${GITHUB_SHA:-}}" commit="${CI_COMMIT_SHA:-${GITHUB_SHA:-}}"
@ -14,7 +13,6 @@ echo "Uploading batch results"
curl --silent --request POST \ curl --silent --request POST \
--header "X-API-Key: ${TAB_API_KEY}" \ --header "X-API-Key: ${TAB_API_KEY}" \
--form "project=${project}" \ --form "project=${project}" \
--form "suite=${suite}" \
--form "branch=${branch}" \ --form "branch=${branch}" \
--form "commit=${commit}" \ --form "commit=${commit}" \
--form "tests=@test-results/junit.xml" \ --form "tests=@test-results/junit.xml" \

View File

@ -1,22 +1,16 @@
name: cargo test
on: on:
push: push:
branches: branches:
- main - main
pull_request: pull_request:
workflow_dispatch: workflow_dispatch:
schedule:
- cron: 0 * * * * # hourly
permissions: permissions:
contents: read contents: read
pull-requests: write pull-requests: write
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true cancel-in-progress: true
name: cargo test
jobs: jobs:
build-test-artifacts: build-test-artifacts:
name: Build test artifacts name: Build test artifacts
@ -94,7 +88,6 @@ jobs:
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}} KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}}
ZOO_HOST: https://api.dev.zoo.dev ZOO_HOST: https://api.dev.zoo.dev
RUST_BACKTRACE: full RUST_BACKTRACE: full
RUST_MIN_STACK: 10485760000
- name: Commit differences - name: Commit differences
if: steps.path-changes.outputs.outside-kcl-samples == 'false' && steps.cargo-test-kcl-samples.outcome == 'failure' if: steps.path-changes.outputs.outside-kcl-samples == 'false' && steps.cargo-test-kcl-samples.outcome == 'failure'
shell: bash shell: bash
@ -126,7 +119,6 @@ jobs:
# Configure nextest when it's run by insta (via just). # Configure nextest when it's run by insta (via just).
NEXTEST_PROFILE: ci NEXTEST_PROFILE: ci
RUST_BACKTRACE: full RUST_BACKTRACE: full
RUST_MIN_STACK: 10485760000
- name: Build and archive tests - name: Build and archive tests
run: | run: |
cd rust cd rust
@ -190,7 +182,6 @@ jobs:
env: env:
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}} KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}}
ZOO_HOST: https://api.dev.zoo.dev ZOO_HOST: https://api.dev.zoo.dev
RUST_MIN_STACK: 10485760000
- name: Upload results - name: Upload results
if: always() if: always()
run: .github/ci-cd-scripts/upload-results.sh run: .github/ci-cd-scripts/upload-results.sh
@ -199,7 +190,6 @@ jobs:
TAB_API_KEY: ${{ secrets.TAB_API_KEY }} TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }} CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
CI_PR_NUMBER: ${{ github.event.pull_request.number }} CI_PR_NUMBER: ${{ github.event.pull_request.number }}
CI_SUITE: e2e:kcl
run-internal-kcl-samples: run-internal-kcl-samples:
name: cargo test (internal-kcl-samples) name: cargo test (internal-kcl-samples)
runs-on: runs-on:
@ -248,7 +238,6 @@ jobs:
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}} KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}}
ZOO_HOST: https://api.dev.zoo.dev ZOO_HOST: https://api.dev.zoo.dev
MODELING_APP_INTERNAL_SAMPLES_SECRET: ${{secrets.MODELING_APP_INTERNAL_SAMPLES_SECRET}} MODELING_APP_INTERNAL_SAMPLES_SECRET: ${{secrets.MODELING_APP_INTERNAL_SAMPLES_SECRET}}
RUST_MIN_STACK: 10485760000
run-wasm-tests: run-wasm-tests:
name: Run wasm tests name: Run wasm tests
strategy: strategy:

View File

@ -1,5 +1,4 @@
name: E2E Tests name: E2E Tests
on: on:
push: push:
branches: branches:
@ -144,7 +143,7 @@ jobs:
- name: Install browsers - name: Install browsers
run: npm run playwright install --with-deps run: npm run playwright install --with-deps
- name: Test snapshots - name: Capture snapshots
uses: nick-fields/retry@v3.0.2 uses: nick-fields/retry@v3.0.2
with: with:
shell: bash shell: bash
@ -157,19 +156,6 @@ jobs:
TAB_API_KEY: ${{ secrets.TAB_API_KEY }} TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }} CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
CI_PR_NUMBER: ${{ github.event.pull_request.number }} CI_PR_NUMBER: ${{ github.event.pull_request.number }}
CI_SUITE: e2e:snapshots
TARGET: web
- name: Update snapshots
if: always()
run: npm run test:snapshots -- --last-failed --update-snapshots
env:
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
TAB_API_URL: ${{ secrets.TAB_API_URL }}
TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
CI_PR_NUMBER: ${{ github.event.pull_request.number }}
CI_SUITE: e2e:snapshots
TARGET: web TARGET: web
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
@ -187,7 +173,7 @@ jobs:
id: git-check id: git-check
run: | run: |
git add e2e/playwright/snapshot-tests.spec.ts-snapshots e2e/playwright/snapshots git add e2e/playwright/snapshot-tests.spec.ts-snapshots e2e/playwright/snapshots
if git status | grep --quiet "Changes to be committed" if git status | grep -q "Changes to be committed"
then echo "modified=true" >> $GITHUB_OUTPUT then echo "modified=true" >> $GITHUB_OUTPUT
else echo "modified=false" >> $GITHUB_OUTPUT else echo "modified=false" >> $GITHUB_OUTPUT
fi fi
@ -320,7 +306,6 @@ jobs:
TAB_API_KEY: ${{ secrets.TAB_API_KEY }} TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }} CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
CI_PR_NUMBER: ${{ github.event.pull_request.number }} CI_PR_NUMBER: ${{ github.event.pull_request.number }}
CI_SUITE: e2e:desktop
TARGET: desktop TARGET: desktop
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4

View File

@ -122,11 +122,12 @@ https://github.com/KittyCAD/modeling-app/issues/new
#### 2. Push a new tag #### 2. Push a new tag
Decide on a `v`-prefixed semver `VERSION` (eg. `v1.2.3`) with the team and tag the repo, eg. on latest main: Create a new tag and push it to the repo. The `semantic-release.sh` script will automatically bump the minor part, which we use the most. For instance going from `v0.27.0` to `v0.28.0`.
``` ```
VERSION=$(./scripts/semantic-release.sh)
git tag $VERSION git tag $VERSION
git push origin $VERSION git push origin --tags
``` ```
This will trigger the `build-apps` workflow, set the version, build & sign the apps, and generate release files. This will trigger the `build-apps` workflow, set the version, build & sign the apps, and generate release files.

View File

@ -4,7 +4,7 @@ Compared to other CAD software, getting Zoo Design Studio up and running is quic
## Windows ## Windows
1. Download the [Zoo Design Studio installer](https://zoo.dev/design-studio/download) for Windows and for your processor type. 1. Download the [Zoo Design Studio installer](https://zoo.dev/modeling-app/download) for Windows and for your processor type.
2. Once downloaded, run the installer `Zoo Design Studio-{version}-{arch}-win.exe` which should take a few seconds. 2. Once downloaded, run the installer `Zoo Design Studio-{version}-{arch}-win.exe` which should take a few seconds.
@ -12,7 +12,7 @@ Compared to other CAD software, getting Zoo Design Studio up and running is quic
## macOS ## macOS
1. Download the [Zoo Design Studio installer](https://zoo.dev/design-studio/download) for macOS and for your processor type. 1. Download the [Zoo Design Studio installer](https://zoo.dev/modeling-app/download) for macOS and for your processor type.
2. Once downloaded, open the disk image `Zoo Design Studio-{version}-{arch}-mac.dmg` and drag the applications to your `Applications` directory. 2. Once downloaded, open the disk image `Zoo Design Studio-{version}-{arch}-mac.dmg` and drag the applications to your `Applications` directory.
@ -21,7 +21,7 @@ Compared to other CAD software, getting Zoo Design Studio up and running is quic
## Linux ## Linux
1. Download the [Zoo Design Studio installer](https://zoo.dev/design-studio/download) for Linux and for your processor type. 1. Download the [Zoo Design Studio installer](https://zoo.dev/modeling-app/download) for Linux and for your processor type.
2. Install the dependencies needed to run the [AppImage format](https://appimage.org/). 2. Install the dependencies needed to run the [AppImage format](https://appimage.org/).
- On Ubuntu, install the FUSE library with these commands in a terminal. - On Ubuntu, install the FUSE library with these commands in a terminal.

View File

@ -2,7 +2,7 @@
# Zoo Design Studio # Zoo Design Studio
[zoo.dev/design-studio](https://zoo.dev/design-studio) [zoo.dev/modeling-app](https://zoo.dev/modeling-app)
A CAD application from the future, brought to you by the [Zoo team](https://zoo.dev). A CAD application from the future, brought to you by the [Zoo team](https://zoo.dev).
@ -40,8 +40,12 @@ The 3D view in Design Studio is just a video stream from our hosted geometry eng
## Get Started ## Get Started
We recommend downloading the latest application binary from our [website](https://zoo.dev/design-studio/download). If you don't see your platform or architecture supported there, please file an issue. See the [installation guide](INSTALL.md) for additional instructions. We recommend downloading the latest application binary from our [releases](https://github.com/KittyCAD/modeling-app/releases) page. If you don't see your platform or architecture supported there, please file an issue.
## Developing ## Developing
Finally, if you'd like to run a development build or contribute to the project, please visit our [contributor guide](CONTRIBUTING.md) to get started. To contribute to the KittyCAD Language, see the dedicated [readme](rust/kcl-lib/README.md) for KCL. Finally, if you'd like to run a development build or contribute to the project, please visit our [contributor guide](CONTRIBUTING.md) to get started.
## KCL
To contribute to the KittyCAD Language, see the [README](https://github.com/KittyCAD/modeling-app/tree/main/rust/kcl-lib) for KCL.

View File

@ -4,7 +4,7 @@ excerpt: "Documentation of the KCL language for the Zoo Design Studio."
layout: manual layout: manual
--- ---
This is a reference for KCL. If you are learning KCL, you may prefer the [guide](https://zoo.dev/docs/kcl-book/intro.html) which explains This is a reference for KCL. If you are learning KCL, you may prefer the [guide]() which explains
things in a more tutorial fashion. See also our documentation of the [standard library](/docs/kcl-std). things in a more tutorial fashion. See also our documentation of the [standard library](/docs/kcl-std).
## Topics ## Topics

View File

@ -27,6 +27,9 @@ import increment from "util.kcl"
answer = increment(41) answer = increment(41)
``` ```
Imported files _must_ be in the same project so that units are uniform across
modules. This means that it must be in the same directory.
Import statements must be at the top-level of a file. It is not allowed to have Import statements must be at the top-level of a file. It is not allowed to have
an `import` statement inside a function or in the body of an ifelse. an `import` statement inside a function or in the body of an ifelse.
@ -55,9 +58,6 @@ Imported symbols can be renamed for convenience or to avoid name collisions.
import increment as inc, decrement as dec from "util.kcl" import increment as inc, decrement as dec from "util.kcl"
``` ```
You can import files from the current directory or from subdirectories, but if importing from a
subdirectory you can only import `main.kcl`.
--- ---
## Functions vs `clone` ## Functions vs `clone`
@ -229,19 +229,6 @@ The final statement is what's important because it's the return value of the
entire module. The module is expected to return a single object that can be used entire module. The module is expected to return a single object that can be used
as a variable by the file that imports it. as a variable by the file that imports it.
The name of the file or subdirectory is used as the name of the variable within the importing program.
If you want to use a different name, you can do so by using the `as` keyword:
```kcl,norun
import "cube.kcl" // Introduces a new variable called `cube`.
import "cube.kcl" as block // Introduces a new variable called `block`.
import "cube/main.kcl" // Introduces a new variable called `cube`.
import "cube/main.kcl" as block // Introduces a new variable called `block`.
```
If the filename includes hyphens (`-`) or starts with an underscore (`_`), then you must specify a
variable name.
--- ---
## Multiple instances of the same import ## Multiple instances of the same import

File diff suppressed because one or more lines are too long

View File

@ -11,8 +11,7 @@ layout: manual
circle( circle(
@sketch_or_surface: Sketch | Plane | Face, @sketch_or_surface: Sketch | Plane | Face,
center: Point2d, center: Point2d,
radius?: number(Length), radius: number(Length),
diameter?: number(Length),
tag?: tag, tag?: tag,
): Sketch ): Sketch
``` ```
@ -26,8 +25,7 @@ the provided (x, y) origin point.
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `sketch_or_surface` | [`Sketch`](/docs/kcl-std/types/std-types-Sketch) or [`Plane`](/docs/kcl-std/types/std-types-Plane) or [`Face`](/docs/kcl-std/types/std-types-Face) | Sketch to extend, or plane or surface to sketch on. | Yes | | `sketch_or_surface` | [`Sketch`](/docs/kcl-std/types/std-types-Sketch) or [`Plane`](/docs/kcl-std/types/std-types-Plane) or [`Face`](/docs/kcl-std/types/std-types-Face) | Sketch to extend, or plane or surface to sketch on. | Yes |
| `center` | [`Point2d`](/docs/kcl-std/types/std-types-Point2d) | The center of the circle. | Yes | | `center` | [`Point2d`](/docs/kcl-std/types/std-types-Point2d) | The center of the circle. | Yes |
| `radius` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The radius of the circle. Incompatible with `diameter`. | No | | `radius` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The radius of the circle. | Yes |
| `diameter` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The diameter of the circle. Incompatible with `radius`. | No |
| [`tag`](/docs/kcl-std/types/std-types-tag) | [`tag`](/docs/kcl-std/types/std-types-tag) | Create a new tag which refers to this circle. | No | | [`tag`](/docs/kcl-std/types/std-types-tag) | [`tag`](/docs/kcl-std/types/std-types-tag) | Create a new tag which refers to this circle. | No |
### Returns ### Returns
@ -53,7 +51,7 @@ exampleSketch = startSketchOn(XZ)
|> line(end = [0, 30]) |> line(end = [0, 30])
|> line(end = [-30, 0]) |> line(end = [-30, 0])
|> close() |> close()
|> subtract2d(tool = circle(center = [0, 15], diameter = 10)) |> subtract2d(tool = circle(center = [0, 15], radius = 5))
example = extrude(exampleSketch, length = 5) example = extrude(exampleSketch, length = 5)
``` ```

File diff suppressed because one or more lines are too long

View File

@ -65,7 +65,7 @@ layout: manual
* [`line`](/docs/kcl-std/line) * [`line`](/docs/kcl-std/line)
* [`loft`](/docs/kcl-std/loft) * [`loft`](/docs/kcl-std/loft)
* [`patternCircular2d`](/docs/kcl-std/patternCircular2d) * [`patternCircular2d`](/docs/kcl-std/patternCircular2d)
* [`patternTransform2d`](/docs/kcl-std/functions/std-sketch-patternTransform2d) * [`patternTransform2d`](/docs/kcl-std/patternTransform2d)
* [`polygon`](/docs/kcl-std/polygon) * [`polygon`](/docs/kcl-std/polygon)
* [`profileStart`](/docs/kcl-std/profileStart) * [`profileStart`](/docs/kcl-std/profileStart)
* [`profileStartX`](/docs/kcl-std/profileStartX) * [`profileStartX`](/docs/kcl-std/profileStartX)
@ -94,7 +94,7 @@ layout: manual
* [`intersect`](/docs/kcl-std/intersect) * [`intersect`](/docs/kcl-std/intersect)
* [`patternCircular3d`](/docs/kcl-std/patternCircular3d) * [`patternCircular3d`](/docs/kcl-std/patternCircular3d)
* [`patternLinear3d`](/docs/kcl-std/patternLinear3d) * [`patternLinear3d`](/docs/kcl-std/patternLinear3d)
* [`patternTransform`](/docs/kcl-std/functions/std-solid-patternTransform) * [`patternTransform`](/docs/kcl-std/patternTransform)
* [`shell`](/docs/kcl-std/functions/std-solid-shell) * [`shell`](/docs/kcl-std/functions/std-solid-shell)
* [`subtract`](/docs/kcl-std/subtract) * [`subtract`](/docs/kcl-std/subtract)
* [`union`](/docs/kcl-std/union) * [`union`](/docs/kcl-std/union)

View File

@ -30,7 +30,7 @@ This module contains functions for creating and manipulating sketches, and makin
* [`line`](/docs/kcl-std/line) * [`line`](/docs/kcl-std/line)
* [`loft`](/docs/kcl-std/loft) * [`loft`](/docs/kcl-std/loft)
* [`patternCircular2d`](/docs/kcl-std/patternCircular2d) * [`patternCircular2d`](/docs/kcl-std/patternCircular2d)
* [`patternTransform2d`](/docs/kcl-std/functions/std-sketch-patternTransform2d) * [`patternTransform2d`](/docs/kcl-std/patternTransform2d)
* [`polygon`](/docs/kcl-std/polygon) * [`polygon`](/docs/kcl-std/polygon)
* [`profileStart`](/docs/kcl-std/profileStart) * [`profileStart`](/docs/kcl-std/profileStart)
* [`profileStartX`](/docs/kcl-std/profileStartX) * [`profileStartX`](/docs/kcl-std/profileStartX)

View File

@ -18,7 +18,7 @@ This module contains functions for modifying solids, e.g., by adding a fillet or
* [`intersect`](/docs/kcl-std/intersect) * [`intersect`](/docs/kcl-std/intersect)
* [`patternCircular3d`](/docs/kcl-std/patternCircular3d) * [`patternCircular3d`](/docs/kcl-std/patternCircular3d)
* [`patternLinear3d`](/docs/kcl-std/patternLinear3d) * [`patternLinear3d`](/docs/kcl-std/patternLinear3d)
* [`patternTransform`](/docs/kcl-std/functions/std-solid-patternTransform) * [`patternTransform`](/docs/kcl-std/patternTransform)
* [`shell`](/docs/kcl-std/functions/std-solid-shell) * [`shell`](/docs/kcl-std/functions/std-solid-shell)
* [`subtract`](/docs/kcl-std/subtract) * [`subtract`](/docs/kcl-std/subtract)
* [`union`](/docs/kcl-std/union) * [`union`](/docs/kcl-std/union)

View File

@ -11,7 +11,7 @@ Contains frequently used constants, functions for interacting with the KittyCAD
The standard library is organised into modules (listed below), but most things are always available in KCL programs. The standard library is organised into modules (listed below), but most things are always available in KCL programs.
You might also want the [KCL language reference](/docs/kcl-lang) or the [KCL guide](https://zoo.dev/docs/kcl-book/intro.html). You might also want the [KCL language reference](/docs/kcl-lang) or the [KCL guide]().
## Modules ## Modules

File diff suppressed because one or more lines are too long

View File

@ -26,7 +26,7 @@ patternLinear3d(
| `solids` | [`[Solid]`](/docs/kcl-std/types/std-types-Solid) | The solid(s) to duplicate | Yes | | `solids` | [`[Solid]`](/docs/kcl-std/types/std-types-Solid) | The solid(s) to duplicate | Yes |
| `instances` | [`number`](/docs/kcl-std/types/std-types-number) | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes | | `instances` | [`number`](/docs/kcl-std/types/std-types-number) | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
| `distance` | [`number`](/docs/kcl-std/types/std-types-number) | Distance between each repetition. Also known as 'spacing'. | Yes | | `distance` | [`number`](/docs/kcl-std/types/std-types-number) | Distance between each repetition. Also known as 'spacing'. | Yes |
| `axis` | [`Point3d`](/docs/kcl-std/types/std-types-Point3d) | The axis of the pattern. A 3D vector. | Yes | | `axis` | [`Point3d`](/docs/kcl-std/types/std-types-Point3d) | The axis of the pattern. A 2D vector. | Yes |
| `useOriginal` | [`bool`](/docs/kcl-std/types/std-types-bool) | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No | | `useOriginal` | [`bool`](/docs/kcl-std/types/std-types-bool) | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |
### Returns ### Returns

File diff suppressed because one or more lines are too long

View File

@ -1,19 +1,19 @@
--- ---
title: "patternTransform2d" title: "patternTransform2d"
subtitle: "Function in std::sketch" subtitle: "Function in std::sketch"
excerpt: "Just like `patternTransform`, but works on 2D sketches not 3D solids." excerpt: "Just like patternTransform, but works on 2D sketches not 3D solids."
layout: manual layout: manual
--- ---
Just like `patternTransform`, but works on 2D sketches not 3D solids. Just like patternTransform, but works on 2D sketches not 3D solids.
```kcl ```kcl
patternTransform2d( patternTransform2d(
@sketches: [Sketch; 1+], @sketches: [Sketch],
instances: number(_), instances: number,
transform: fn(number(_)): { }, transform: FunctionSource,
useOriginal?: boolean, useOriginal?: bool,
): [Sketch; 1+] ): [Sketch]
``` ```
@ -22,14 +22,14 @@ patternTransform2d(
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `sketches` | [`[Sketch; 1+]`](/docs/kcl-std/types/std-types-Sketch) | The sketch(es) to duplicate. | Yes | | `sketches` | [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) | The sketch(es) to duplicate | Yes |
| `instances` | [`number(_)`](/docs/kcl-std/types/std-types-number) | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes | | `instances` | [`number`](/docs/kcl-std/types/std-types-number) | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
| `transform` | [`fn(number(_)): { }`](/docs/kcl-std/types/std-types-fn) | How each replica should be transformed. The transform function takes a single parameter: an integer representing which number replication the transform is for. E.g. the first replica to be transformed will be passed the argument `1`. This simplifies your math: the transform function can rely on id `0` being the original instance passed into the `patternTransform`. See the examples. | Yes | | `transform` | `FunctionSource` | How each replica should be transformed. The transform function takes a single parameter: an integer representing which number replication the transform is for. E.g. the first replica to be transformed will be passed the argument `1`. This simplifies your math: the transform function can rely on id `0` being the original instance passed into the `patternTransform`. See the examples. | Yes |
| `useOriginal` | `boolean` | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. | No | | `useOriginal` | [`bool`](/docs/kcl-std/types/std-types-bool) | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |
### Returns ### Returns
[`[Sketch; 1+]`](/docs/kcl-std/types/std-types-Sketch) [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch)
### Examples ### Examples

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -78,10 +78,11 @@ extrude001 = extrude(sketch001, length = 5)`
// Delete a character to break the KCL // Delete a character to break the KCL
await editor.openPane() await editor.openPane()
await editor.scrollToText('extrude(%, length = width)') await editor.scrollToText('bracketLeg1Sketch, length = thickness)')
await page.getByText('extrude(%, length = width)').click() await page
.getByText('extrude(bracketLeg1Sketch, length = thickness)')
await page.keyboard.press(')') .click()
await page.keyboard.press('Backspace')
// Ensure that a badge appears on the button // Ensure that a badge appears on the button
await expect(codePaneButtonHolder).toContainText('notification') await expect(codePaneButtonHolder).toContainText('notification')
@ -98,11 +99,16 @@ extrude001 = extrude(sketch001, length = 5)`
await page.waitForTimeout(500) await page.waitForTimeout(500)
// Ensure that a badge appears on the button
await expect(codePaneButtonHolder).toContainText('notification')
// Ensure we have no errors in the gutter.
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
// Open the code pane // Open the code pane
await editor.openPane() await editor.openPane()
// Go to our problematic code again // Go to our problematic code again (missing closing paren!)
await editor.scrollToText('extrude(%, length = w') await editor.scrollToText('extrude(bracketLeg1Sketch, length = thickness')
// Ensure that a badge appears on the button // Ensure that a badge appears on the button
await expect(codePaneButtonHolder).toContainText('notification') await expect(codePaneButtonHolder).toContainText('notification')
@ -229,48 +235,6 @@ extrude001 = extrude(sketch001, length = 5)`
.first() .first()
).toBeVisible() ).toBeVisible()
}) })
test('KCL errors with functions show hints for the entire backtrace', async ({
page,
homePage,
scene,
cmdBar,
editor,
toolbar,
}) => {
await homePage.goToModelingScene()
await scene.settled(cmdBar)
const code = `fn check(@x) {
return assert(x, isGreaterThan = 0)
}
fn middle(@x) {
return check(x)
}
middle(1)
middle(0)
`
await test.step('Set the code with a KCL error', async () => {
await toolbar.openPane('code')
await editor.replaceCode('', code)
})
// This shows all the diagnostics in a way that doesn't require the mouse
// pointer hovering over a coordinate, which would be brittle.
await test.step('Open CodeMirror diagnostics list', async () => {
// Ensure keyboard focus is in the editor.
await page.getByText('fn check(').click()
await page.keyboard.press('ControlOrMeta+Shift+M')
})
await expect(
page.getByText(`assert failed: Expected 0 to be greater than 0 but it wasn't
check()
middle()`)
).toBeVisible()
// There should be one hint inside middle() and one at the top level.
await expect(page.getByText('Part of the error backtrace')).toHaveCount(2)
})
}) })
test( test(

View File

@ -45,16 +45,15 @@ test.describe('Command bar tests', () => {
await cmdBar.expectState({ await cmdBar.expectState({
stage: 'arguments', stage: 'arguments',
commandName: 'Extrude', commandName: 'Extrude',
currentArgKey: 'sketches', currentArgKey: 'length',
currentArgValue: '', currentArgValue: '5',
headerArguments: { headerArguments: {
Profiles: '', Profiles: '1 profile',
Length: '', Length: '',
}, },
highlightedHeaderArg: 'Profiles', highlightedHeaderArg: 'length',
}) })
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
await cmdBar.expectState({ await cmdBar.expectState({
stage: 'review', stage: 'review',
commandName: 'Extrude', commandName: 'Extrude',
@ -685,33 +684,4 @@ c = 3 + a`
highlightedHeaderArg: 'value', highlightedHeaderArg: 'value',
}) })
}) })
test('Text-to-CAD command can be closed with escape while in prompt', async ({
page,
homePage,
cmdBar,
}) => {
await homePage.expectState({
projectCards: [],
sortBy: 'last-modified-desc',
})
await homePage.textToCadBtn.click()
await cmdBar.expectState({
stage: 'arguments',
commandName: 'Text-to-CAD Create',
currentArgKey: 'prompt',
currentArgValue: '',
headerArguments: {
Method: 'New project',
NewProjectName: 'untitled',
Prompt: '',
},
highlightedHeaderArg: 'prompt',
})
await page.keyboard.press('Escape')
await cmdBar.toBeClosed()
await cmdBar.expectState({
stage: 'commandBarClosed',
})
})
}) })

View File

@ -1001,7 +1001,7 @@ a1 = startSketchOn(offsetPlane(XY, offset = 10))
await expect(page.locator('.cm-content')).toHaveText( await expect(page.locator('.cm-content')).toHaveText(
`@settings(defaultLengthUnit = in) `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ) sketch001 = startSketchOn(XZ)
|> startProfile(%, at = [0, 12]) |> startProfile(%, at = [3.14, 12])
|> xLine(%, length = 5) // lin`.replaceAll('\n', '') |> xLine(%, length = 5) // lin`.replaceAll('\n', '')
) )
@ -1076,7 +1076,7 @@ sketch001 = startSketchOn(XZ)
await expect(page.locator('.cm-content')).toHaveText( await expect(page.locator('.cm-content')).toHaveText(
`@settings(defaultLengthUnit = in) `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ) sketch001 = startSketchOn(XZ)
|> startProfile(%, at = [0, 12]) |> startProfile(%, at = [3.14, 12])
|> xLine(%, length = 5) // lin`.replaceAll('\n', '') |> xLine(%, length = 5) // lin`.replaceAll('\n', '')
) )
}) })
@ -1134,7 +1134,6 @@ sketch001 = startSketchOn(XZ)
// Wait for the selection to register (TODO: we need a definitive way to wait for this) // Wait for the selection to register (TODO: we need a definitive way to wait for this)
await page.waitForTimeout(200) await page.waitForTimeout(200)
await toolbar.extrudeButton.click() await toolbar.extrudeButton.click()
await cmdBar.progressCmdBar()
await cmdBar.expectState({ await cmdBar.expectState({
stage: 'arguments', stage: 'arguments',
currentArgKey: 'length', currentArgKey: 'length',
@ -1356,7 +1355,9 @@ sketch001 = startSketchOn(XZ)
const u = await getUtils(page) const u = await getUtils(page)
const projectLink = page.getByRole('link', { name: 'cube' }) const projectLink = page.getByRole('link', { name: 'cube' })
const gizmo = page.locator('[aria-label*=gizmo]') const gizmo = page.locator('[aria-label*=gizmo]')
const resetCameraButton = page.getByRole('button', { name: 'Reset view' }) const resetCameraButton = page.getByRole('button', {
name: 'Reset view',
})
const locationToHaveColor = async ( const locationToHaveColor = async (
position: { x: number; y: number }, position: { x: number; y: number },
color: [number, number, number] color: [number, number, number]
@ -1590,38 +1591,4 @@ sketch001 = startSketchOn(XZ)
await expect(page.getByTestId('center-rectangle')).toBeVisible() await expect(page.getByTestId('center-rectangle')).toBeVisible()
}) })
}) })
test('syntax errors still show when reopening KCL pane', async ({
page,
homePage,
scene,
cmdBar,
}) => {
const u = await getUtils(page)
await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene()
// Wait for connection, this is especially important for this test, because safeParse is invoked when
// connection is established which would interfere with the test if it happened during later steps.
await scene.connectionEstablished()
await scene.settled(cmdBar)
// Code with no error
await u.codeLocator.fill(`x = 7`)
await page.waitForTimeout(200) // allow some time for the error to show potentially
await expect(page.locator('.cm-lint-marker-error')).toHaveCount(0)
// Code with error
await u.codeLocator.fill(`x 7`)
await expect(page.locator('.cm-lint-marker-error')).toHaveCount(1)
// Close and reopen KCL code panel
await u.closeKclCodePanel()
await expect(page.locator('.cm-lint-marker-error')).toHaveCount(0) // error disappears on close
await u.openKclCodePanel()
// Verify error is still visible
await expect(page.locator('.cm-lint-marker-error')).toHaveCount(1)
})
}) })

View File

@ -238,26 +238,6 @@ test.describe('when using the file tree to', () => {
} }
) )
test(
`create new folders and that doesn't trigger a navigation`,
{ tag: ['@electron', '@macos', '@windows'] },
async ({ page, homePage, scene, toolbar, cmdBar }) => {
await homePage.goToModelingScene()
await scene.settled(cmdBar)
await toolbar.openPane('files')
const { createNewFolder } = await getUtils(page, test)
await createNewFolder('folder')
await createNewFolder('folder.kcl')
await test.step(`Postcondition: folders are created and we didn't navigate`, async () => {
await toolbar.expectFileTreeState(['folder', 'folder.kcl', 'main.kcl'])
await expect(toolbar.fileName).toHaveText('main.kcl')
})
}
)
test( test(
'deleting all files recreates a default main.kcl with no code', 'deleting all files recreates a default main.kcl with no code',
{ tag: '@electron' }, { tag: '@electron' },

View File

@ -105,19 +105,14 @@ export class CmdBarFixture {
expectState = async (expected: CmdBarSerialised) => { expectState = async (expected: CmdBarSerialised) => {
return expect.poll(() => this._serialiseCmdBar()).toEqual(expected) return expect.poll(() => this._serialiseCmdBar()).toEqual(expected)
} }
/** /** The method will use buttons OR press enter randomly to progress the cmdbar,
* This method is used to progress the command bar to the next step, defaulting to clicking the next button. * this could have unexpected results depending on what's focused
* Optionally, with the `shouldUseKeyboard` parameter, it will hit `Enter` to progress. *
* * TODO: This method assumes the user has a valid input to the current stage, * TODO: This method assumes the user has a valid input to the current stage,
* and assumes we are past the `pickCommand` step. * and assumes we are past the `pickCommand` step.
*/ */
progressCmdBar = async (shouldUseKeyboard = false) => { progressCmdBar = async (shouldFuzzProgressMethod = true) => {
await this.page.waitForTimeout(2000) await this.page.waitForTimeout(2000)
if (shouldUseKeyboard) {
await this.page.keyboard.press('Enter')
return
}
const arrowButton = this.page.getByRole('button', { const arrowButton = this.page.getByRole('button', {
name: 'arrow right Continue', name: 'arrow right Continue',
}) })
@ -313,11 +308,6 @@ export class CmdBarFixture {
await expect(this.cmdBarElement).toBeVisible({ timeout: 10_000 }) await expect(this.cmdBarElement).toBeVisible({ timeout: 10_000 })
} }
async toBeClosed() {
// Check that the command bar is closed
await expect(this.cmdBarElement).not.toBeVisible({ timeout: 10_000 })
}
async expectArgValue(value: string) { async expectArgValue(value: string) {
// Check the placeholder project name exists // Check the placeholder project name exists
const actualArgument = await this.cmdBarElement const actualArgument = await this.cmdBarElement

View File

@ -26,7 +26,6 @@ export class HomePageFixture {
sortByNameBtn!: Locator sortByNameBtn!: Locator
appHeader!: Locator appHeader!: Locator
tutorialBtn!: Locator tutorialBtn!: Locator
textToCadBtn!: Locator
constructor(page: Page) { constructor(page: Page) {
this.page = page this.page = page
@ -48,7 +47,6 @@ export class HomePageFixture {
this.sortByNameBtn = this.page.getByTestId('home-sort-by-name') this.sortByNameBtn = this.page.getByTestId('home-sort-by-name')
this.appHeader = this.page.getByTestId('app-header') this.appHeader = this.page.getByTestId('app-header')
this.tutorialBtn = this.page.getByTestId('home-tutorial-button') this.tutorialBtn = this.page.getByTestId('home-tutorial-button')
this.textToCadBtn = this.page.getByTestId('home-text-to-cad')
} }
private _serialiseSortBy = async (): Promise< private _serialiseSortBy = async (): Promise<

View File

@ -61,7 +61,6 @@ class MyAPIReporter implements Reporter {
const payload = { const payload = {
// Required information // Required information
project: 'https://github.com/KittyCAD/modeling-app', project: 'https://github.com/KittyCAD/modeling-app',
suite: process.env.CI_SUITE || 'e2e',
branch: process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME || '', branch: process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME || '',
commit: process.env.CI_COMMIT_SHA || process.env.GITHUB_SHA || '', commit: process.env.CI_COMMIT_SHA || process.env.GITHUB_SHA || '',
test: test.titlePath().slice(2).join(' '), test: test.titlePath().slice(2).join(' '),

View File

@ -70,28 +70,22 @@ test.describe('Point-and-click assemblies tests', () => {
await test.step('Setup parts and expect empty assembly scene', async () => { await test.step('Setup parts and expect empty assembly scene', async () => {
const projectName = 'assembly' const projectName = 'assembly'
await context.folderSetupFn(async (dir) => { await context.folderSetupFn(async (dir) => {
const projDir = path.join(dir, projectName) const bracketDir = path.join(dir, projectName)
const nestedProjDir = path.join(dir, projectName, 'nested', 'twice') await fsp.mkdir(bracketDir, { recursive: true })
await fsp.mkdir(projDir, { recursive: true })
await fsp.mkdir(nestedProjDir, { recursive: true })
await Promise.all([ await Promise.all([
fsp.copyFile( fsp.copyFile(
executorInputPath('cylinder.kcl'), executorInputPath('cylinder.kcl'),
path.join(projDir, 'cylinder.kcl') path.join(bracketDir, 'cylinder.kcl')
),
fsp.copyFile(
executorInputPath('cylinder.kcl'),
path.join(nestedProjDir, 'main.kcl')
), ),
fsp.copyFile( fsp.copyFile(
executorInputPath('e2e-can-sketch-on-chamfer.kcl'), executorInputPath('e2e-can-sketch-on-chamfer.kcl'),
path.join(projDir, 'bracket.kcl') path.join(bracketDir, 'bracket.kcl')
), ),
fsp.copyFile( fsp.copyFile(
testsInputPath('cube.step'), testsInputPath('cube.step'),
path.join(projDir, 'cube.step') path.join(bracketDir, 'cube.step')
), ),
fsp.writeFile(path.join(projDir, 'main.kcl'), ''), fsp.writeFile(path.join(bracketDir, 'main.kcl'), ''),
]) ])
}) })
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1000, height: 500 })
@ -173,25 +167,6 @@ test.describe('Point-and-click assemblies tests', () => {
await expect( await expect(
page.getByText('This file is already imported') page.getByText('This file is already imported')
).toBeVisible() ).toBeVisible()
await cmdBar.closeCmdBar()
})
await test.step('Insert a nested kcl part', async () => {
await insertPartIntoAssembly(
'nested/twice/main.kcl',
'main',
toolbar,
cmdBar,
page
)
await toolbar.openPane('code')
await page.waitForTimeout(10000)
await editor.expectEditor.toContain(
`
import "nested/twice/main.kcl" as main
`,
{ shouldNormalise: true }
)
}) })
} }
) )

View File

@ -74,15 +74,6 @@ test.describe('Point-and-click tests', () => {
await test.step('do extrude flow and check extrude code is added to editor', async () => { await test.step('do extrude flow and check extrude code is added to editor', async () => {
await toolbar.extrudeButton.click() await toolbar.extrudeButton.click()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'sketches',
currentArgValue: '',
headerArguments: { Profiles: '', Length: '' },
highlightedHeaderArg: 'Profiles',
commandName: 'Extrude',
})
await cmdBar.progressCmdBar()
await cmdBar.expectState({ await cmdBar.expectState({
stage: 'arguments', stage: 'arguments',
currentArgKey: 'length', currentArgKey: 'length',
@ -1654,15 +1645,6 @@ sketch002 = startSketchOn(plane001)
await test.step(`Go through the command bar flow with preselected sketches`, async () => { await test.step(`Go through the command bar flow with preselected sketches`, async () => {
await toolbar.loftButton.click() await toolbar.loftButton.click()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'sketches',
currentArgValue: '',
headerArguments: { Profiles: '' },
highlightedHeaderArg: 'Profiles',
commandName: 'Loft',
})
await cmdBar.progressCmdBar()
await cmdBar.expectState({ await cmdBar.expectState({
stage: 'review', stage: 'review',
headerArguments: { Profiles: '2 profiles' }, headerArguments: { Profiles: '2 profiles' },
@ -1873,11 +1855,7 @@ sketch002 = startSketchOn(XZ)
}, },
stage: 'review', stage: 'review',
}) })
// Confirm we can submit from the review step with just `Enter` await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar(true)
await cmdBar.expectState({
stage: 'commandBarClosed',
})
}) })
await test.step(`Confirm code is added to the editor, scene has changed`, async () => { await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
@ -2017,7 +1995,7 @@ profile001 = ${circleCode}`
}, },
stage: 'review', stage: 'review',
}) })
await cmdBar.progressCmdBar(true) await cmdBar.progressCmdBar()
await editor.expectEditor.toContain(sweepDeclaration) await editor.expectEditor.toContain(sweepDeclaration)
}) })
@ -2110,18 +2088,6 @@ extrude001 = extrude(sketch001, length = -12)
await test.step(`Apply fillet to the preselected edge`, async () => { await test.step(`Apply fillet to the preselected edge`, async () => {
await page.waitForTimeout(100) await page.waitForTimeout(100)
await toolbar.filletButton.click() await toolbar.filletButton.click()
await cmdBar.expectState({
commandName: 'Fillet',
highlightedHeaderArg: 'selection',
currentArgKey: 'selection',
currentArgValue: '',
headerArguments: {
Selection: '',
Radius: '',
},
stage: 'arguments',
})
await cmdBar.progressCmdBar()
await cmdBar.expectState({ await cmdBar.expectState({
commandName: 'Fillet', commandName: 'Fillet',
highlightedHeaderArg: 'radius', highlightedHeaderArg: 'radius',
@ -2651,18 +2617,6 @@ extrude001 = extrude(profile001, length = 5)
await test.step(`Apply fillet`, async () => { await test.step(`Apply fillet`, async () => {
await page.waitForTimeout(100) await page.waitForTimeout(100)
await toolbar.filletButton.click() await toolbar.filletButton.click()
await cmdBar.expectState({
commandName: 'Fillet',
highlightedHeaderArg: 'selection',
currentArgKey: 'selection',
currentArgValue: '',
headerArguments: {
Selection: '',
Radius: '',
},
stage: 'arguments',
})
await cmdBar.progressCmdBar()
await cmdBar.expectState({ await cmdBar.expectState({
commandName: 'Fillet', commandName: 'Fillet',
highlightedHeaderArg: 'radius', highlightedHeaderArg: 'radius',
@ -2768,19 +2722,6 @@ extrude001 = extrude(sketch001, length = -12)
await test.step(`Apply chamfer to the preselected edge`, async () => { await test.step(`Apply chamfer to the preselected edge`, async () => {
await page.waitForTimeout(100) await page.waitForTimeout(100)
await toolbar.chamferButton.click() await toolbar.chamferButton.click()
await cmdBar.expectState({
commandName: 'Chamfer',
highlightedHeaderArg: 'selection',
currentArgKey: 'selection',
currentArgValue: '',
headerArguments: {
Selection: '',
Length: '',
},
stage: 'arguments',
})
await cmdBar.progressCmdBar()
await page.waitForTimeout(1000)
await cmdBar.expectState({ await cmdBar.expectState({
commandName: 'Chamfer', commandName: 'Chamfer',
highlightedHeaderArg: 'length', highlightedHeaderArg: 'length',
@ -3264,8 +3205,6 @@ extrude001 = extrude(sketch001, length = 30)
await test.step(`Go through the command bar flow with a preselected face (cap)`, async () => { await test.step(`Go through the command bar flow with a preselected face (cap)`, async () => {
await toolbar.shellButton.click() await toolbar.shellButton.click()
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
await page.waitForTimeout(500)
await cmdBar.progressCmdBar()
await cmdBar.expectState({ await cmdBar.expectState({
stage: 'review', stage: 'review',
headerArguments: { headerArguments: {
@ -3699,12 +3638,13 @@ tag=$rectangleSegmentC002,
// revolve // revolve
await editor.scrollToText(codeToSelection) await editor.scrollToText(codeToSelection)
await page.getByText(codeToSelection).click() await page.getByText(codeToSelection).click()
// Wait for the selection to register (TODO: we need a definitive way to wait for this)
await page.waitForTimeout(200)
await toolbar.revolveButton.click() await toolbar.revolveButton.click()
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
const newCodeToFind = `revolve001 = revolve(sketch002, angle = 360, axis = X)` const newCodeToFind = `revolve001 = revolve(sketch002, angle = 360, axis = X)`
expect(editor.expectEditor.toContain(newCodeToFind)).toBeTruthy() expect(editor.expectEditor.toContain(newCodeToFind)).toBeTruthy()
@ -4633,18 +4573,6 @@ path001 = startProfile(sketch001, at = [0, 0])
await test.step('Go through command bar flow', async () => { await test.step('Go through command bar flow', async () => {
await toolbar.extrudeButton.click() await toolbar.extrudeButton.click()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'sketches',
currentArgValue: '',
headerArguments: {
Profiles: '',
Length: '',
},
highlightedHeaderArg: 'Profiles',
commandName: 'Extrude',
})
await cmdBar.progressCmdBar()
await cmdBar.expectState({ await cmdBar.expectState({
stage: 'arguments', stage: 'arguments',
currentArgKey: 'length', currentArgKey: 'length',
@ -4727,19 +4655,6 @@ path001 = startProfile(sketch001, at = [0, 0])
await test.step('Go through command bar flow', async () => { await test.step('Go through command bar flow', async () => {
await toolbar.sweepButton.click() await toolbar.sweepButton.click()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'sketches',
currentArgValue: '',
headerArguments: {
Profiles: '',
Path: '',
Sectional: '',
},
highlightedHeaderArg: 'Profiles',
commandName: 'Sweep',
})
await cmdBar.progressCmdBar()
await cmdBar.expectState({ await cmdBar.expectState({
stage: 'arguments', stage: 'arguments',
currentArgKey: 'path', currentArgKey: 'path',
@ -4824,19 +4739,6 @@ path001 = startProfile(sketch001, at = [0, 0])
await test.step('Go through command bar flow', async () => { await test.step('Go through command bar flow', async () => {
await toolbar.closePane('code') await toolbar.closePane('code')
await toolbar.revolveButton.click() await toolbar.revolveButton.click()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'sketches',
currentArgValue: '',
headerArguments: {
Profiles: '',
AxisOrEdge: '',
Angle: '',
},
highlightedHeaderArg: 'Profiles',
commandName: 'Revolve',
})
await cmdBar.progressCmdBar()
await cmdBar.expectState({ await cmdBar.expectState({
stage: 'arguments', stage: 'arguments',
currentArgKey: 'axisOrEdge', currentArgKey: 'axisOrEdge',

View File

@ -11,7 +11,6 @@ import {
getPlaywrightDownloadDir, getPlaywrightDownloadDir,
getUtils, getUtils,
isOutOfViewInScrollContainer, isOutOfViewInScrollContainer,
runningOnWindows,
} from '@e2e/playwright/test-utils' } from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test' import { expect, test } from '@e2e/playwright/zoo-test'
@ -1980,6 +1979,7 @@ test(
} }
) )
// Flaky
test( test(
'Original project name persist after onboarding', 'Original project name persist after onboarding',
{ tag: '@electron' }, { tag: '@electron' },
@ -2064,55 +2064,3 @@ test(
}) })
} }
) )
test(
'import from nested directory',
{ tag: ['@electron', '@windows', '@macos'] },
async ({ scene, cmdBar, context, page }) => {
await context.folderSetupFn(async (dir) => {
const bracketDir = path.join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
const nestedDir = path.join(bracketDir, 'nested')
await fsp.mkdir(nestedDir, { recursive: true })
await fsp.copyFile(
executorInputPath('cylinder-inches.kcl'),
path.join(nestedDir, 'main.kcl')
)
await fsp.writeFile(
path.join(bracketDir, 'main.kcl'),
runningOnWindows()
? `import 'nested\\main.kcl' as thing\n\nthing`
: `import 'nested/main.kcl' as thing\n\nthing`
)
})
await page.setBodyDimensions({ width: 1200, height: 500 })
const u = await getUtils(page)
const pointOnModel = { x: 630, y: 280 }
await test.step('Opening the bracket project should load the stream', async () => {
// expect to see the text bracket
await expect(page.getByText('bracket')).toBeVisible()
await page.getByText('bracket').click()
await scene.settled(cmdBar)
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).toBeEnabled({
timeout: 20_000,
})
// gray at this pixel means the stream has loaded in the most
// user way we can verify it (pixel color)
await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [125, 125, 125]), {
timeout: 10_000,
})
.toBeLessThan(15)
})
}
)

View File

@ -1016,7 +1016,6 @@ profile001 = startProfile(sketch001, at = [${roundOff(scale * 69.6)}, ${roundOff
// sketch selection should already have been made. // sketch selection should already have been made.
// otherwise the cmdbar would be waiting for a selection. // otherwise the cmdbar would be waiting for a selection.
await cmdBar.progressCmdBar()
await cmdBar.expectState({ await cmdBar.expectState({
stage: 'arguments', stage: 'arguments',
currentArgKey: 'length', currentArgKey: 'length',

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 65 KiB

View File

@ -557,14 +557,6 @@ export async function getUtils(page: Page, test_?: typeof test) {
}) })
}, },
createNewFolder: async (name: string) => {
return test?.step(`Create a folder named ${name}`, async () => {
await page.getByTestId('create-folder-button').click()
await page.getByTestId('tree-input-field').fill(name)
await page.keyboard.press('Enter')
})
},
cloneFile: async (name: string) => { cloneFile: async (name: string) => {
return test?.step(`Cloning file '${name}'`, async () => { return test?.step(`Cloning file '${name}'`, async () => {
await page await page

View File

@ -103,8 +103,6 @@ test.describe('Testing loading external models', () => {
file: 'ball-bearing' + FILE_EXT, file: 'ball-bearing' + FILE_EXT,
title: 'Ball Bearing', title: 'Ball Bearing',
file1: 'ball-bearing-1' + FILE_EXT, file1: 'ball-bearing-1' + FILE_EXT,
folderName: 'ball-bearing',
folderName1: 'ball-bearing-1',
} }
const projectCard = page.getByRole('link', { name: 'bracket' }) const projectCard = page.getByRole('link', { name: 'bracket' })
const overwriteWarning = page.getByText( const overwriteWarning = page.getByText(
@ -156,10 +154,8 @@ test.describe('Testing loading external models', () => {
await test.step(`Ensure we made and opened a new file`, async () => { await test.step(`Ensure we made and opened a new file`, async () => {
await editor.expectEditor.toContain('// ' + sampleOne.title) await editor.expectEditor.toContain('// ' + sampleOne.title)
await expect( await expect(newlyCreatedFile(sampleOne.file)).toBeVisible()
page.getByTestId('file-tree-item').getByText(sampleOne.folderName) await expect(projectMenuButton).toContainText(sampleOne.file)
).toBeVisible()
await expect(projectMenuButton).toContainText('main.kcl')
}) })
await test.step(`Load a KCL sample with the command palette`, async () => { await test.step(`Load a KCL sample with the command palette`, async () => {
@ -173,10 +169,8 @@ test.describe('Testing loading external models', () => {
await test.step(`Ensure we made and opened a new file with a unique name`, async () => { await test.step(`Ensure we made and opened a new file with a unique name`, async () => {
await editor.expectEditor.toContain('// ' + sampleOne.title) await editor.expectEditor.toContain('// ' + sampleOne.title)
await expect( await expect(newlyCreatedFile(sampleOne.file1)).toBeVisible()
page.getByTestId('file-tree-item').getByText(sampleOne.folderName1) await expect(projectMenuButton).toContainText(sampleOne.file1)
).toBeVisible()
await expect(projectMenuButton).toContainText('main.kcl')
}) })
} }
) )

View File

@ -1,3 +1,5 @@
import fs from 'fs'
import { join } from 'path'
import type { Page } from '@playwright/test' import type { Page } from '@playwright/test'
import { createProject, getUtils } from '@e2e/playwright/test-utils' import { createProject, getUtils } from '@e2e/playwright/test-utils'
@ -401,6 +403,106 @@ test.describe('Text-to-CAD tests', () => {
await expect(page.getByText(promptWithNewline)).toBeVisible() await expect(page.getByText(promptWithNewline)).toBeVisible()
}) })
// This will be fine once greg makes prompt at top of file deterministic
test('can do many at once and get many prompts back, and interact with many', async ({
page,
homePage,
cmdBar,
}) => {
// Let this test run longer since we've seen it timeout.
test.setTimeout(180_000)
const u = await getUtils(page)
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene()
await u.waitForPageLoad()
await sendPromptFromCommandBarAndSetExistingProject(
page,
'a 2x4 lego',
cmdBar
)
await sendPromptFromCommandBarAndSetExistingProject(
page,
'a 2x8 lego',
cmdBar
)
await sendPromptFromCommandBarAndSetExistingProject(
page,
'a 2x10 lego',
cmdBar
)
// Find the toast.
// Look out for the toast message
const submittingToastMessage = page.getByText(
`Submitting to Text-to-CAD API...`
)
await expect(submittingToastMessage.first()).toBeVisible()
const generatingToastMessage = page.getByText(
`Generating parametric model...`
)
await expect(generatingToastMessage.first()).toBeVisible({
timeout: 10_000,
})
const successToastMessage = page.getByText(`Text-to-CAD successful`)
// We should have three success toasts.
await expect(successToastMessage).toHaveCount(3, { timeout: 25_000 })
await expect(page.getByText(`a 2x4 lego`)).toBeVisible()
await expect(page.getByText(`a 2x8 lego`)).toBeVisible()
await expect(page.getByText(`a 2x10 lego`)).toBeVisible()
// Ensure if you reject one, the others stay.
const rejectButton = page.getByRole('button', { name: 'Reject' })
await expect(rejectButton.first()).toBeVisible()
// Click the reject button on the first toast.
await rejectButton.first().click()
// The first toast should disappear, but not the others.
await expect(page.getByText(`a 2x10 lego`)).not.toBeVisible()
await expect(page.getByText(`a 2x8 lego`)).toBeVisible()
await expect(page.getByText(`a 2x4 lego`)).toBeVisible()
// Ensure you can copy the code for one of the models remaining.
const copyToClipboardButton = page.getByRole('button', {
name: 'Accept',
})
await expect(copyToClipboardButton.first()).toBeVisible()
// Click the button.
await copyToClipboardButton.first().click()
// Do NOT do AI tests like this: "Expect the code to be pasted."
// Reason: AI tests are NONDETERMINISTIC. Thus we need to be as most
// general as we can for the assertion.
// We can use Kolmogorov complexity as a measurement of the
// "probably most minimal version of this program" to have a lower
// bound to work with. It is completely by feel because there are
// no proofs that any program is its smallest self.
const code2x8 = await page.locator('.cm-content').innerText()
await expect(code2x8.length).toBeGreaterThan(249)
// Ensure the final toast remains.
await expect(page.getByText(`a 2x10 lego`)).not.toBeVisible()
await expect(page.getByText(`Prompt: "a 2x8 lego`)).not.toBeVisible()
await expect(page.getByText(`a 2x4 lego`)).toBeVisible()
// Ensure you can copy the code for the final model.
await expect(copyToClipboardButton).toBeVisible()
// Click the button.
await copyToClipboardButton.click()
// Expect the code to be pasted.
const code2x4 = await page.locator('.cm-content').innerText()
await expect(code2x4.length).toBeGreaterThan(249)
})
test('can do many at once with errors, clicking dismiss error does not dismiss all', async ({ test('can do many at once with errors, clicking dismiss error does not dismiss all', async ({
page, page,
homePage, homePage,
@ -573,6 +675,82 @@ async function sendPromptFromCommandBarAndSetExistingProject(
}) })
} }
test(
'Text-to-CAD functionality',
{ tag: '@electron' },
async ({ context, page, cmdBar }, testInfo) => {
const projectName = 'project-000'
const prompt = 'lego 2x4'
const textToCadFileName = 'lego-2x4.kcl'
const { dir } = await context.folderSetupFn(async () => {})
const fileExists = () =>
fs.existsSync(join(dir, projectName, textToCadFileName))
const { openFilePanel, openKclCodePanel, waitForPageLoad } = await getUtils(
page,
test
)
await page.setBodyDimensions({ width: 1200, height: 500 })
// Locators
const projectMenuButton = page
.getByTestId('project-sidebar-toggle')
.filter({ hasText: projectName })
const textToCadFileButton = page.getByRole('listitem').filter({
has: page.getByRole('button', { name: textToCadFileName }),
})
const textToCadComment = page.getByText(
`// Generated by Text-to-CAD: ${prompt}`
)
// Create and navigate to the project
await createProject({ name: 'project-000', page })
// Wait for Start Sketch otherwise you will not have access Text-to-CAD command
await waitForPageLoad()
await openFilePanel()
await openKclCodePanel()
await test.step(`Test file creation`, async () => {
await sendPromptFromCommandBarAndSetExistingProject(
page,
prompt,
cmdBar,
projectName
)
// File is considered created if it shows up in the Project Files pane
await expect(textToCadFileButton).toBeVisible({ timeout: 20_000 })
expect(fileExists()).toBeTruthy()
})
await test.step(`Test file navigation`, async () => {
await expect(projectMenuButton).toContainText('main.kcl')
await textToCadFileButton.click()
// File can be navigated and loaded assuming a specific KCL comment is loaded into the KCL code pane
await expect(textToCadComment).toBeVisible({ timeout: 20_000 })
await expect(projectMenuButton).toContainText(textToCadFileName)
})
await test.step(`Test file deletion on rejection`, async () => {
const rejectButton = page.getByRole('button', { name: 'Reject' })
// A file is created and can be navigated to while this prompt is still opened
// Click the "Reject" button within the prompt and it will delete the file.
await rejectButton.click()
const submittingToastMessage = page.getByText(
`Successfully deleted file "lego-2x4.kcl"`
)
await expect(submittingToastMessage).toBeVisible()
expect(fileExists()).toBeFalsy()
// Confirm we've navigated back to the main.kcl file after deletion
await expect(projectMenuButton).toContainText('main.kcl')
})
}
)
/** /**
* Below there are twelve (12) tests for testing the navigation and file creation * Below there are twelve (12) tests for testing the navigation and file creation
* logic around text to cad. The Text to CAD command is now globally available * logic around text to cad. The Text to CAD command is now globally available
@ -806,12 +984,12 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
) )
await expect(page.getByTestId('app-header-file-name')).toBeVisible() await expect(page.getByTestId('app-header-file-name')).toBeVisible()
await expect(page.getByTestId('app-header-file-name')).toContainText( await expect(page.getByTestId('app-header-file-name')).toContainText(
'main.kcl' '2x2x2-cube.kcl'
) )
await u.openFilePanel() await u.openFilePanel()
await expect( await expect(
page.getByTestId('file-tree-item').getByText('2x2x2-cube') page.getByTestId('file-tree-item').getByText('2x2x2-cube.kcl')
).toBeVisible() ).toBeVisible()
} }
) )
@ -1006,13 +1184,13 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
) )
await expect(page.getByTestId('app-header-file-name')).toBeVisible() await expect(page.getByTestId('app-header-file-name')).toBeVisible()
await expect(page.getByTestId('app-header-file-name')).toContainText( await expect(page.getByTestId('app-header-file-name')).toContainText(
'main.kcl' '2x2x2-cube.kcl'
) )
// Check file is created // Check file is created
await u.openFilePanel() await u.openFilePanel()
await expect( await expect(
page.getByTestId('file-tree-item').getByText('2x2x2-cube') page.getByTestId('file-tree-item').getByText('2x2x2-cube.kcl')
).toBeVisible() ).toBeVisible()
} }
) )
@ -1298,13 +1476,13 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
) )
await expect(page.getByTestId('app-header-file-name')).toBeVisible() await expect(page.getByTestId('app-header-file-name')).toBeVisible()
await expect(page.getByTestId('app-header-file-name')).toContainText( await expect(page.getByTestId('app-header-file-name')).toContainText(
'main.kcl' '2x2x2-cube.kcl'
) )
// Check file is created // Check file is created
await u.openFilePanel() await u.openFilePanel()
await expect( await expect(
page.getByTestId('file-tree-item').getByText('2x2x2-cube') page.getByTestId('file-tree-item').getByText('2x2x2-cube.kcl')
).toBeVisible() ).toBeVisible()
await expect( await expect(
page.getByTestId('file-tree-item').getByText('main.kcl') page.getByTestId('file-tree-item').getByText('main.kcl')

View File

@ -573,7 +573,6 @@ profile001 = startProfile(sketch002, at = [-12.34, 12.34])
await expect(page.getByTestId('command-bar')).toBeVisible() await expect(page.getByTestId('command-bar')).toBeVisible()
await page.waitForTimeout(100) await page.waitForTimeout(100)
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
await expect(page.getByText('Confirm Extrude')).toBeVisible() await expect(page.getByText('Confirm Extrude')).toBeVisible()
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()

View File

@ -16,6 +16,7 @@
<link rel="apple-touch-icon" href="/logo192.png" /> <link rel="apple-touch-icon" href="/logo192.png" />
<link rel="manifest" href="/manifest.json" /> <link rel="manifest" href="/manifest.json" />
<link rel="stylesheet" href="./inter/inter.css" /> <link rel="stylesheet" href="./inter/inter.css" />
<link rel="stylesheet" href="https://use.typekit.net/zzv8rvm.css" />
<script <script
defer defer
data-domain="app.zoo.dev" data-domain="app.zoo.dev"

1
package-lock.json generated
View File

@ -2492,7 +2492,6 @@
}, },
"node_modules/@clack/prompts/node_modules/is-unicode-supported": { "node_modules/@clack/prompts/node_modules/is-unicode-supported": {
"version": "1.3.0", "version": "1.3.0",
"extraneous": true,
"inBundle": true, "inBundle": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {

View File

@ -27,7 +27,7 @@ if len(modified_release_body) > max_length:
# Message to send to Discord # Message to send to Discord
data = { data = {
"content": textwrap.dedent(f''' "content": textwrap.dedent(f'''
**{release_version}** is now available! Check out the latest features and improvements here: <https://zoo.dev/design-studio> **{release_version}** is now available! Check out the latest features and improvements here: <https://zoo.dev/modeling-app/download>
{modified_release_body} {modified_release_body}
'''), '''),

View File

@ -37,8 +37,6 @@ When you submit a PR to add or modify KCL samples, images will be generated and
[![bottle](screenshots/bottle.png)](bottle/main.kcl) [![bottle](screenshots/bottle.png)](bottle/main.kcl)
#### [bracket](bracket/main.kcl) ([screenshot](screenshots/bracket.png)) #### [bracket](bracket/main.kcl) ([screenshot](screenshots/bracket.png))
[![bracket](screenshots/bracket.png)](bracket/main.kcl) [![bracket](screenshots/bracket.png)](bracket/main.kcl)
#### [brake-rotor](brake-rotor/main.kcl) ([screenshot](screenshots/brake-rotor.png))
[![brake-rotor](screenshots/brake-rotor.png)](brake-rotor/main.kcl)
#### [car-wheel-assembly](car-wheel-assembly/main.kcl) ([screenshot](screenshots/car-wheel-assembly.png)) #### [car-wheel-assembly](car-wheel-assembly/main.kcl) ([screenshot](screenshots/car-wheel-assembly.png))
[![car-wheel-assembly](screenshots/car-wheel-assembly.png)](car-wheel-assembly/main.kcl) [![car-wheel-assembly](screenshots/car-wheel-assembly.png)](car-wheel-assembly/main.kcl)
#### [cold-plate](cold-plate/main.kcl) ([screenshot](screenshots/cold-plate.png)) #### [cold-plate](cold-plate/main.kcl) ([screenshot](screenshots/cold-plate.png))
@ -143,14 +141,10 @@ When you submit a PR to add or modify KCL samples, images will be generated and
[![spur-reduction-gearset](screenshots/spur-reduction-gearset.png)](spur-reduction-gearset/main.kcl) [![spur-reduction-gearset](screenshots/spur-reduction-gearset.png)](spur-reduction-gearset/main.kcl)
#### [surgical-drill-guide](surgical-drill-guide/main.kcl) ([screenshot](screenshots/surgical-drill-guide.png)) #### [surgical-drill-guide](surgical-drill-guide/main.kcl) ([screenshot](screenshots/surgical-drill-guide.png))
[![surgical-drill-guide](screenshots/surgical-drill-guide.png)](surgical-drill-guide/main.kcl) [![surgical-drill-guide](screenshots/surgical-drill-guide.png)](surgical-drill-guide/main.kcl)
#### [telemetry-antenna](telemetry-antenna/main.kcl) ([screenshot](screenshots/telemetry-antenna.png))
[![telemetry-antenna](screenshots/telemetry-antenna.png)](telemetry-antenna/main.kcl)
#### [thermal-block-insert](thermal-block-insert/main.kcl) ([screenshot](screenshots/thermal-block-insert.png)) #### [thermal-block-insert](thermal-block-insert/main.kcl) ([screenshot](screenshots/thermal-block-insert.png))
[![thermal-block-insert](screenshots/thermal-block-insert.png)](thermal-block-insert/main.kcl) [![thermal-block-insert](screenshots/thermal-block-insert.png)](thermal-block-insert/main.kcl)
#### [tooling-nest-block](tooling-nest-block/main.kcl) ([screenshot](screenshots/tooling-nest-block.png)) #### [tooling-nest-block](tooling-nest-block/main.kcl) ([screenshot](screenshots/tooling-nest-block.png))
[![tooling-nest-block](screenshots/tooling-nest-block.png)](tooling-nest-block/main.kcl) [![tooling-nest-block](screenshots/tooling-nest-block.png)](tooling-nest-block/main.kcl)
#### [truss-structure](truss-structure/main.kcl) ([screenshot](screenshots/truss-structure.png))
[![truss-structure](screenshots/truss-structure.png)](truss-structure/main.kcl)
#### [utility-sink](utility-sink/main.kcl) ([screenshot](screenshots/utility-sink.png)) #### [utility-sink](utility-sink/main.kcl) ([screenshot](screenshots/utility-sink.png))
[![utility-sink](screenshots/utility-sink.png)](utility-sink/main.kcl) [![utility-sink](screenshots/utility-sink.png)](utility-sink/main.kcl)
#### [walkie-talkie](walkie-talkie/main.kcl) ([screenshot](screenshots/walkie-talkie.png)) #### [walkie-talkie](walkie-talkie/main.kcl) ([screenshot](screenshots/walkie-talkie.png))

View File

@ -1,180 +0,0 @@
// Brake Rotor
// A 320mm vented brake disc (rotor), with straight vanes, 30mm thick. The disc bell should accommodate 5 M12 wheel studs on a 114.3mm pitch circle diameter.
@settings(defaultLengthUnit = mm)
// Define parameters.
dDisc = 320
dPitchCircle = 114.3
dBore = 64
nStuds = 5
dStudDrilling = 12.5 // M12
hFrictionSurface = 60
tDiscHalf = 10
// Vent parameters.
tVent = 10
wVent = 6
rVentFillet = 2
nVentBosses = 36
// Drilling parameters.
dDrillDia = 6
aBase = 90
aSweep = 30
nArcs = 12
// Bell parameters.
aDraftBell = 5
tBell = 5 // Wall thickness.
hBellAboveDiscFace = 40
hBellSubflush = 4
wUndercut = 8
fn drillHole(activeSketch, t) {
// Sketch a vent hole at line parameter value t on an arc drawn across the disc surface.
rInner = dDisc / 2 - hFrictionSurface
rOuter = dDisc / 2
aStart = aBase
aEnd = aBase - aSweep
// Linear interpolation of radius.
rCurrent = rInner + t * (rOuter - rInner)
// Linear interpolation of angle.
aCurrent = aStart + t * (aEnd - aStart)
// Calculate position.
xCenter = rCurrent * cos(aCurrent)
yCenter = rCurrent * sin(aCurrent)
// Draw.
drillCircle = circle(activeSketch, center = [xCenter, yCenter], radius = dDrillDia / 2)
return drillCircle
}
fn createDiscHalf(plane, dDiscParam, hFrictionSurfaceParam, tDiscHalfParam) {
// Create a disc half with a vent hole pattern.
sketchFace = startSketchOn(plane)
profileFace = circle(sketchFace, center = [0, 0], radius = dDiscParam / 2)
|> subtract2d(tool = circle(sketchFace, center = [0, 0], radius = dDiscParam / 2 - hFrictionSurfaceParam))
// Create three circles at t = 0, 0.5, and 1
hole1 = drillHole(activeSketch = sketchFace, t = 0.2)
hole2 = drillHole(activeSketch = sketchFace, t = 0.5)
hole3 = drillHole(activeSketch = sketchFace, t = 0.8)
// Pattern and cut.
holes = patternCircular2d(
[hole1, hole2, hole3],
instances = nArcs,
center = [0, 0],
arcDegrees = 360,
rotateDuplicates = true,
)
profileDrilled = subtract2d(profileFace, tool = holes)
// Extrude.
discHalf = extrude(profileFace, length = tDiscHalfParam)
return discHalf
}
// ---------------------------------------------------------------------------------------------------------------------
// Create inboard half.
discInboard = createDiscHalf(
plane = XY,
dDiscParam = dDisc,
hFrictionSurfaceParam = hFrictionSurface,
tDiscHalfParam = tDiscHalf,
)
// Create vents.
planeVent = offsetPlane(XY, offset = tDiscHalf)
sketchVent = startSketchOn(planeVent)
profileVent = startProfile(sketchVent, at = [-wVent, dDisc / 2])
|> angledLine(angle = 0, length = wVent, tag = $rectangleSegmentA001)
|> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = hFrictionSurface, tag = $seg02)
|> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $seg03)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg01)
|> close()
ventPad = extrude(profileVent, length = tVent)
|> fillet(
radius = rVentFillet,
tags = [
getCommonEdge(faces = [seg01, rectangleSegmentA001]),
getCommonEdge(faces = [seg02, rectangleSegmentA001]),
getCommonEdge(faces = [seg01, seg03]),
getCommonEdge(faces = [seg03, seg02])
],
)
ventSet = patternCircular3d(
ventPad,
instances = nVentBosses,
axis = [0, 0, 1],
center = [0, 0, tDiscHalf],
arcDegrees = 360,
rotateDuplicates = true,
)
// Create outboard half.
planeOutboard = offsetPlane(XY, offset = tDiscHalf + tVent)
discOutboard = createDiscHalf(
plane = planeOutboard,
dDiscParam = dDisc,
hFrictionSurfaceParam = hFrictionSurface,
tDiscHalfParam = tDiscHalf,
)
// Now create bell.
rCenter = dDisc / 2 - hFrictionSurface - wUndercut
rBore = dBore / 2
lDraftExterior = hBellAboveDiscFace / tan(90 - aDraftBell)
lDraftInterior = (hBellAboveDiscFace - tBell) / tan(90 - aDraftBell)
// Inner and outer radius of outboard face of disc bell.
rOuter = rCenter - lDraftExterior - rBore
rInner = rOuter + lDraftExterior - (tBell + lDraftInterior)
sketchDiscBell = startSketchOn(-YZ)
bodyDiscBell = startProfile(
sketchDiscBell,
at = [
-dDisc / 2 + hFrictionSurface,
tDiscHalf * 2 + tVent
],
)
|> arc(
%,
angleStart = -180,
angleEnd = 0,
radius = wUndercut / 2,
)
|> line(end = [lDraftExterior, hBellAboveDiscFace])
|> xLine(length = rOuter, tag = $seg04)
|> yLine(length = -tBell)
|> xLine(length = -rInner)
|> line(end = [-lDraftInterior, -hBellAboveDiscFace])
|> line(end = [0, -2]) // Wall thickness.
|> xLine(length = -1 * (tBell + wUndercut))
|> close(%)
|> revolve(axis = Y)
// Drill lug holes.
sketchLugs = startSketchOn(bodyDiscBell, face = seg04)
profileStud = circle(sketchLugs, center = [0, dPitchCircle / 2], radius = dStudDrilling / 2)
|> patternCircular2d(
%,
instances = nStuds,
center = [0, 0],
arcDegrees = 360,
rotateDuplicates = true,
)
clearance = 2 // Some margin on negative extrude.
lugs = extrude(profileStud, length = -1 * (tBell + clearance))

View File

@ -74,16 +74,6 @@
"main.kcl" "main.kcl"
] ]
}, },
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "brake-rotor/main.kcl",
"multipleFiles": false,
"title": "Brake Rotor",
"description": "A 320mm vented brake disc (rotor), with straight vanes, 30mm thick. The disc bell should accommodate 5 M12 wheel studs on a 114.3mm pitch circle diameter.",
"files": [
"main.kcl"
]
},
{ {
"file": "main.kcl", "file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "car-wheel-assembly/main.kcl", "pathFromProjectDirectoryToFirstFile": "car-wheel-assembly/main.kcl",
@ -632,16 +622,6 @@
"main.kcl" "main.kcl"
] ]
}, },
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "telemetry-antenna/main.kcl",
"multipleFiles": false,
"title": "Aircraft telemetry antenna plate",
"description": "Consists of a circular base plate 3 inches in diameter and 0.08 inches thick, with a tapered monopole antenna mounted at the top with a base diameter of 0.65 inches and height of 1.36 inches. Also consists of a mounting base and connector at the bottom of the plate. The plate also has 6 countersunk holes at a defined pitch circle diameter.",
"files": [
"main.kcl"
]
},
{ {
"file": "main.kcl", "file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "thermal-block-insert/main.kcl", "pathFromProjectDirectoryToFirstFile": "thermal-block-insert/main.kcl",
@ -662,16 +642,6 @@
"main.kcl" "main.kcl"
] ]
}, },
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "truss-structure/main.kcl",
"multipleFiles": false,
"title": "Truss Structure",
"description": "A truss structure is a framework composed of triangular units made from straight members connected at joints, often called nodes. Trusses are widely used in architecture, civil engineering, and construction for their ability to support large loads with minimal material.",
"files": [
"main.kcl"
]
},
{ {
"file": "main.kcl", "file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "utility-sink/main.kcl", "pathFromProjectDirectoryToFirstFile": "utility-sink/main.kcl",

View File

@ -72,25 +72,25 @@ leftSpacerShape = boxModuleFn(width = leftSpacerWidth)
// Module for power switch including front plate and red rocker button // Module for power switch including front plate and red rocker button
switchPosition = leftSpacerPosition + leftSpacerWidth / 2 + moduleWidth / 2 switchPosition = leftSpacerPosition + leftSpacerWidth / 2 + moduleWidth / 2
switchWidth = moduleWidth swtichWidth = moduleWidth
// Switch Body // Switch Body
switchBody = boxModuleFn(width = moduleWidth) switchBody = boxModuleFn(width = moduleWidth)
// Switch Plate // Switch Plate
switchPlateWidth = 20 swtichPlateWidth = 20
switchPlateHeight = 30 switchPlateHeight = 30
switchPlateThickness = 3 switchPlateThickness = 3
switchPlateShape = startSketchOn(switchBody, face = END) switchPlateShape = startSketchOn(switchBody, face = END)
|> startProfile( |> startProfile(
%, %,
at = [ at = [
-switchPlateWidth / 2, -swtichPlateWidth / 2,
-switchPlateHeight / 2 -switchPlateHeight / 2
], ],
) )
|> yLine(length = switchPlateHeight) |> yLine(length = switchPlateHeight)
|> xLine(length = switchPlateWidth) |> xLine(length = swtichPlateWidth)
|> yLine(length = -switchPlateHeight) |> yLine(length = -switchPlateHeight)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close() |> close()
@ -104,8 +104,8 @@ switchPlateBody = extrude(switchPlateShape, length = switchPlateThickness)
// Switch Button // Switch Button
switchButtonHeight = 26 switchButtonHeight = 26
switchButtonWidth = 15 swtichButtonWidth = 15
switchButtonShape = startSketchOn(offsetPlane(-YZ, offset = -switchButtonWidth / 2)) switchButtonShape = startSketchOn(offsetPlane(-YZ, offset = -swtichButtonWidth / 2))
|> startProfile( |> startProfile(
%, %,
at = [ at = [
@ -121,7 +121,7 @@ switchButtonShape = startSketchOn(offsetPlane(-YZ, offset = -switchButtonWidth /
]) ])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close() |> close()
switchButtonBody = extrude(switchButtonShape, length = switchButtonWidth) switchButtonBody = extrude(switchButtonShape, length = swtichButtonWidth)
|> translate( |> translate(
%, %,
x = switchPosition, x = switchPosition,
@ -132,7 +132,7 @@ switchButtonBody = extrude(switchButtonShape, length = switchButtonWidth)
// Spacer between switch and plug modules for layout alignment // Spacer between switch and plug modules for layout alignment
secondSpacerWidth = moduleWidth / 2 secondSpacerWidth = moduleWidth / 2
secondSpacerPosition = switchPosition + switchWidth / 2 + secondSpacerWidth / 2 secondSpacerPosition = switchPosition + swtichWidth / 2 + secondSpacerWidth / 2
secondSpacerBody = boxModuleFn(width = secondSpacerWidth) secondSpacerBody = boxModuleFn(width = secondSpacerWidth)
|> translate( |> translate(
%, %,

View File

@ -33,9 +33,14 @@ stemLoftProfile2 = startSketchOn(offsetPlane(XY, offset = 75))
// Draw the third profile for the lofted femur // Draw the third profile for the lofted femur
p3Z = 110 p3Z = 110
p3A = 25 p3A = 25
plane003 = {
origin = [0, 0.0, p3Z],
xAxis = [cos(p3A), 0, sin(p3A)],
yAxis = [0.0, 1.0, 0.0]
}
l3 = 32 l3 = 32
r3 = 4 r3 = 4
stemLoftProfile3 = startSketchOn(XY) stemLoftProfile3 = startSketchOn(plane003)
|> startProfile(at = [-15.5, -l3 / 2]) |> startProfile(at = [-15.5, -l3 / 2])
|> yLine(length = l3, tag = $seg03) |> yLine(length = l3, tag = $seg03)
|> tangentialArc(angle = -120, radius = r3) |> tangentialArc(angle = -120, radius = r3)
@ -44,14 +49,18 @@ stemLoftProfile3 = startSketchOn(XY)
|> angledLine(angle = 30, length = -segLen(seg03)) |> angledLine(angle = 30, length = -segLen(seg03))
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)]) |> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close() |> close()
|> translate(z = p3Z)
|> rotate(pitch = -p3A)
// Draw the fourth profile for the lofted femur // Draw the fourth profile for the lofted femur
p4Z = 130 p4Z = 130
p4A = 36.5 p4A = 36.5
plane004 = {
origin = [0, 0.0, p4Z],
xAxis = [cos(p4A), 0, sin(p4A)],
yAxis = [0.0, 1.0, 0.0]
}
l4 = 16 l4 = 16
r4 = 5 r4 = 5
stemLoftProfile4 = startSketchOn(XY) stemLoftProfile4 = startSketchOn(plane004)
|> startProfile(at = [-23, -l4 / 2]) |> startProfile(at = [-23, -l4 / 2])
|> yLine(length = l4, tag = $seg04) |> yLine(length = l4, tag = $seg04)
|> tangentialArc(angle = -120, radius = r4) |> tangentialArc(angle = -120, radius = r4)
@ -60,14 +69,18 @@ stemLoftProfile4 = startSketchOn(XY)
|> angledLine(angle = 30, length = -segLen(seg04)) |> angledLine(angle = 30, length = -segLen(seg04))
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)]) |> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close() |> close()
|> translate(z = p4Z)
|> rotate(pitch = -p4A)
// Draw the first profile for the femoral stem // Draw the first profile for the femoral stem
p5Z = 140 p5Z = 140
p5A = 36.5 p5A = 36.5
plane005 = {
origin = [0, 0.0, p5Z],
xAxis = [cos(p5A), 0, sin(p5A)],
yAxis = [0.0, 1.0, 0.0]
}
l5 = 1.6 l5 = 1.6
r5 = 1.6 r5 = 1.6
stemLoftProfile5 = startSketchOn(XY) stemLoftProfile5 = startSketchOn(plane005)
|> startProfile(at = [-19.5, -l5 / 2]) |> startProfile(at = [-19.5, -l5 / 2])
|> yLine(length = l5, tag = $seg05) |> yLine(length = l5, tag = $seg05)
|> tangentialArc(angle = -120, radius = r5) |> tangentialArc(angle = -120, radius = r5)
@ -76,14 +89,18 @@ stemLoftProfile5 = startSketchOn(XY)
|> angledLine(angle = 30, length = -segLen(seg05)) |> angledLine(angle = 30, length = -segLen(seg05))
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)]) |> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close() |> close()
|> translate(z = p5Z)
|> rotate(pitch = -p5A)
// Draw the second profile for the femoral stem // Draw the second profile for the femoral stem
p6Z = 145 p6Z = 145
p6A = 36.5 p6A = 36.5
plane006 = {
origin = [0, 0.0, p6Z],
xAxis = [cos(p6A), 0, sin(p6A)],
yAxis = [0.0, 1.0, 0.0]
}
l6 = 1 l6 = 1
r6 = 3 r6 = 3
stemLoftProfile6 = startSketchOn(XY) stemLoftProfile6 = startSketchOn(plane006)
|> startProfile(at = [-23.4, -l6 / 2]) |> startProfile(at = [-23.4, -l6 / 2])
|> yLine(length = l6, tag = $seg06) |> yLine(length = l6, tag = $seg06)
|> tangentialArc(angle = -120, radius = r6) |> tangentialArc(angle = -120, radius = r6)
@ -92,24 +109,27 @@ stemLoftProfile6 = startSketchOn(XY)
|> angledLine(angle = 30, length = -segLen(seg06)) |> angledLine(angle = 30, length = -segLen(seg06))
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)]) |> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close() |> close()
|> translate(z = p6Z)
|> rotate(pitch = -p6A) // Draw the third profile for the femoral stem
stemTab = clone(stemLoftProfile6)
|> extrude(%, length = 6)
// Loft the femur using all profiles in sequence // Loft the femur using all profiles in sequence
femur = loft([ femur = loft([
stemLoftProfile1, stemLoftProfile1,
stemLoftProfile2, stemLoftProfile2,
stemLoftProfile3, stemLoftProfile3,
stemLoftProfile4 stemLoftProfile4
]) ])
// Loft the femoral stem // Loft the femoral stem
femoralStem = loft([ femoralStem = loft([
clone(stemLoftProfile4), clone(stemLoftProfile4),
stemLoftProfile5, stemLoftProfile5,
clone(stemLoftProfile6) stemLoftProfile6
]) ])
// Draw the third profile for the femoral stem
stemTab = stemLoftProfile6
|> extrude(length = 6)
// Revolve a hollow socket to represent the femoral head // Revolve a hollow socket to represent the femoral head
femoralHead = startSketchOn(XZ) femoralHead = startSketchOn(XZ)
|> startProfile(at = [4, 0]) |> startProfile(at = [4, 0])

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 KiB

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

View File

@ -53,8 +53,8 @@ baseSlab = boxFn(plane = XY, width = slabWidth, height = -baseThickness)
|> appearance(%, color = "#dbd7d2") |> appearance(%, color = "#dbd7d2")
// Create ground platform beneath the base // Create ground platform beneath the base
groundSize = 50 goundSize = 50
groundBody = boxFn(plane = offsetPlane(XY, offset = -baseThickness), width = groundSize, height = -5) groundBody = boxFn(plane = offsetPlane(XY, offset = -baseThickness), width = goundSize, height = -5)
|> appearance(%, color = "#3a3631") |> appearance(%, color = "#3a3631")
// Create a single slab with handrail height to be reused with pattern // Create a single slab with handrail height to be reused with pattern

View File

@ -1,63 +0,0 @@
// Aircraft telemetry antenna plate
// Consists of a circular base plate 3 inches in diameter and 0.08 inches thick, with a tapered monopole antenna mounted at the top with a base diameter of 0.65 inches and height of 1.36 inches. Also consists of a mounting base and connector at the bottom of the plate. The plate also has 6 countersunk holes at a defined pitch circle diameter.
// Set units
@settings(defaultLengthUnit = in)
// Define parameters
plateThickness = 0.08
plateDia = 3
antennaBaseDia = 0.65
antennaAngle = 95
antennaHeight = 1.36
seatingDia = 0.625
totalHeight = 2.14
boltDiameter = .196
boltPitchCircleDiameter = 2.5
// 2D cross-sectional profile of the part that will later be revolved
antennaCrossSectionSketch = startSketchOn(YZ)
antennaCrossSectionProfile = startProfile(antennaCrossSectionSketch, at = [plateDia / 2, 0])
|> yLine(length = plateThickness)
|> xLine(length = -(plateDia - antennaBaseDia) / 2, tag = $seg03)
|> angledLine(angle = antennaAngle, length = 1.1, tag = $seg01)
|> tangentialArc(endAbsolute = [0.025, antennaHeight])
|> xLine(endAbsolute = 0, tag = $seg02)
|> yLine(length = -totalHeight)
|> xLine(length = .25)
|> yLine(length = .05)
|> angledLine(angle = 45, length = 0.025)
|> yLine(length = .125)
|> angledLine(angle = 135, length = 0.025)
|> yLine(length = .125)
|> xLine(length = .025)
|> yLine(length = .025)
|> xLine(endAbsolute = seatingDia / 2)
|> yLine(endAbsolute = -0.25)
|> xLine(endAbsolute = 0.6)
|> yLine(endAbsolute = 0)
|> close()
// Revolution about y-axis of earlier profile
antennaCrossSectionRevolve = revolve(antennaCrossSectionProfile, angle = 360, axis = Y)
// Function to create a countersunk hole
fn countersink(@holePosition) {
startSketchOn(antennaCrossSectionRevolve, face = seg03)
|> circle(center = holePosition, radius = boltDiameter / 2, tag = $hole01)
|> extrude(length = -plateThickness)
|> chamfer(length = 0.04, tags = [hole01])
return { }
}
// PCD converted to radius for positioning the holes
r = boltPitchCircleDiameter / 2
// 6 countersunk holes using the countersink function
countersink([r, 0]) // 0 °
countersink([r * 0.5, r * 0.8660254]) // 60 °
countersink([-r * 0.5, r * 0.8660254]) // 120 °
countersink([-r, 0]) // 180 °
countersink([-r * 0.5, -r * 0.8660254]) // 240 °
countersink([r * 0.5, -r * 0.8660254]) // 300 °

View File

@ -1,142 +0,0 @@
// Truss Structure
// A truss structure is a framework composed of triangular units made from straight members connected at joints, often called nodes. Trusses are widely used in architecture, civil engineering, and construction for their ability to support large loads with minimal material.
@settings(defaultLengthUnit = in)
// Define parameters
thickness = 4
totalLength = 180
totalWidth = 120
totalHeight = 120
legHeight = 48
topTrussAngle = 25
beamWidth = 4
beamLength = 2
sparAngle = 30
nFrames = 3
crossBeamLength = 82
// Sketch the top frame
topFrameSketch = startSketchOn(YZ)
profile001 = startProfile(topFrameSketch, at = [totalWidth / 2, 0])
|> xLine(length = -totalWidth, tag = $bottomFace)
|> yLine(length = 12)
|> angledLine(angle = topTrussAngle, endAbsoluteX = 0, tag = $tag001)
|> angledLine(angle = -topTrussAngle, endAbsoluteX = totalWidth / 2, tag = $tag002)
|> close()
// Create two holes in the top frame sketch to create the center beam
profile002 = startProfile(topFrameSketch, at = [totalWidth / 2 - thickness, thickness])
|> xLine(endAbsolute = thickness / 2)
|> yLine(endAbsolute = segEndY(tag001) - thickness)
|> angledLine(endAbsoluteX = profileStartX(%), angle = -topTrussAngle)
|> close(%)
profile003 = startProfile(topFrameSketch, at = [-totalWidth / 2 + thickness, thickness])
|> xLine(endAbsolute = -thickness / 2)
|> yLine(endAbsolute = segEndY(tag001) - thickness)
|> angledLine(endAbsoluteX = profileStartX(%), angle = 180 + topTrussAngle)
|> close(%)
profile004 = subtract2d(profile001, tool = profile002)
subtract2d(profile001, tool = profile003)
// Extrude the sketch to make the top frame
topFrame = extrude(profile001, length = beamLength)
// Spar 1
sketch002 = startSketchOn(offsetPlane(YZ, offset = .1))
profile006 = startProfile(sketch002, at = [thickness / 2 - 1, 14])
|> angledLine(angle = sparAngle, length = 25)
|> angledLine(angle = -topTrussAngle, length = 5)
|> angledLine(angle = 180 + sparAngle, endAbsoluteX = profileStartX(%))
|> close(%)
spar001 = extrude(profile006, length = 1.8)
// Spar2
profile007 = startProfile(sketch002, at = [-thickness / 2 + 1, 14])
|> angledLine(angle = 180 - sparAngle, length = 25)
|> angledLine(angle = 180 + topTrussAngle, length = 5)
|> angledLine(angle = -sparAngle, endAbsoluteX = profileStartX(%))
|> close(%)
spar002 = extrude(profile007, length = 1.8)
// Combine the top frame with the intermediate support beams
newFrame = topFrame + spar001 + spar002
// Create the two legs on the frame
leg001Sketch = startSketchOn(offsetPlane(XY, offset = .1))
legProfile001 = startProfile(leg001Sketch, at = [0, -totalWidth / 2])
|> xLine(%, length = beamLength - .1)
|> yLine(%, length = beamWidth - 1)
|> xLine(%, endAbsolute = profileStartX(%))
|> close(%)
legProfile002 = startProfile(leg001Sketch, at = [0, totalWidth / 2])
|> xLine(%, length = beamLength - .1)
|> yLine(%, length = -(beamWidth - 1))
|> xLine(%, endAbsolute = profileStartX(%))
|> close(%)
leg001 = extrude(legProfile001, length = -legHeight - .1)
leg002 = extrude(legProfile002, length = -legHeight - .1)
// Combine the top frame with the legs and pattern
fullFrame = newFrame + leg001 + leg002
|> patternLinear3d(
%,
instances = nFrames,
distance = crossBeamLength + beamLength,
axis = [-1, 0, 0],
)
// Create the center cross beam
centerCrossBeamSketch = startSketchOn(YZ)
profile005 = startProfile(centerCrossBeamSketch, at = [0, segEndY(tag001) - 1])
|> angledLine(%, angle = -topTrussAngle, length = beamWidth * 3 / 8)
|> yLine(length = -beamWidth * 3 / 8)
|> angledLine(%, angle = 180 - topTrussAngle, length = beamWidth * 3 / 8)
|> angledLine(%, angle = 180 + topTrussAngle, length = beamWidth * 3 / 8)
|> yLine(length = beamWidth * 3 / 8)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
// Extrude the center cross beam and pattern to every frame
centerCrossBeam = extrude(profile005, length = -crossBeamLength)
|> patternLinear3d(
%,
instances = nFrames - 1,
distance = crossBeamLength + beamLength,
axis = [-1, 0, 0],
)
// Create the two side cross beams
sideCrossBeamSketch = startSketchOn(-YZ)
profile008 = startProfile(
sideCrossBeamSketch,
at = [
-totalWidth / 2 + 0.5,
segEndY(tag002) - .5
],
)
|> yLine(length = -beamLength)
|> xLine(length = 3 / 4 * beamWidth)
|> yLine(length = beamLength)
|> close()
profile009 = startProfile(sideCrossBeamSketch, at = [totalWidth / 2, segEndY(tag002) - .5])
|> yLine(length = -beamLength)
|> xLine(%, length = -3 / 4 * beamWidth)
|> yLine(%, length = beamLength)
|> close(%)
// Extrude the side cross beams and pattern to every frame.
extrude([profile008, profile009], length = crossBeamLength)
|> patternLinear3d(
%,
instances = nFrames - 1,
distance = crossBeamLength + beamLength,
axis = [-1, 0, 0],
)

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
public/kcma-logomark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

46
public/kcma-logomark.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

13
public/zma-logomark.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

20
rust/Cargo.lock generated
View File

@ -1815,7 +1815,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-bumper" name = "kcl-bumper"
version = "0.1.76" version = "0.1.74"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@ -1826,7 +1826,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-derive-docs" name = "kcl-derive-docs"
version = "0.1.76" version = "0.1.74"
dependencies = [ dependencies = [
"Inflector", "Inflector",
"anyhow", "anyhow",
@ -1845,7 +1845,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-directory-test-macro" name = "kcl-directory-test-macro"
version = "0.1.76" version = "0.1.74"
dependencies = [ dependencies = [
"convert_case", "convert_case",
"proc-macro2", "proc-macro2",
@ -1855,7 +1855,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-language-server" name = "kcl-language-server"
version = "0.2.76" version = "0.2.74"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@ -1876,7 +1876,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-language-server-release" name = "kcl-language-server-release"
version = "0.1.76" version = "0.1.74"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@ -1896,7 +1896,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-lib" name = "kcl-lib"
version = "0.2.76" version = "0.2.74"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"approx 0.5.1", "approx 0.5.1",
@ -1973,7 +1973,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-python-bindings" name = "kcl-python-bindings"
version = "0.3.76" version = "0.3.74"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"kcl-lib", "kcl-lib",
@ -1988,7 +1988,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-test-server" name = "kcl-test-server"
version = "0.1.76" version = "0.1.74"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"hyper 0.14.32", "hyper 0.14.32",
@ -2001,7 +2001,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-to-core" name = "kcl-to-core"
version = "0.1.76" version = "0.1.74"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -2015,7 +2015,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-wasm-lib" name = "kcl-wasm-lib"
version = "0.1.76" version = "0.1.74"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bson", "bson",

View File

@ -36,20 +36,20 @@ new-sim-test test_name render_to_png="true":
# Run a KCL deterministic simulation test case and accept output. # Run a KCL deterministic simulation test case and accept output.
overwrite-sim-test-sample test_name: overwrite-sim-test-sample test_name:
ZOO_SIM_UPDATE=always EXPECTORATE=overwrite TWENTY_TWENTY=overwrite {{cita}} {{kcl_lib_flags}} --no-quiet -- simulation_tests::kcl_samples::parse_{{test_name}} EXPECTORATE=overwrite TWENTY_TWENTY=overwrite {{cita}} {{kcl_lib_flags}} --no-quiet -- simulation_tests::kcl_samples::parse_{{test_name}}
ZOO_SIM_UPDATE=always EXPECTORATE=overwrite TWENTY_TWENTY=overwrite {{cita}} {{kcl_lib_flags}} --no-quiet -- simulation_tests::kcl_samples::unparse_{{test_name}} EXPECTORATE=overwrite TWENTY_TWENTY=overwrite {{cita}} {{kcl_lib_flags}} --no-quiet -- simulation_tests::kcl_samples::unparse_{{test_name}}
ZOO_SIM_UPDATE=always EXPECTORATE=overwrite TWENTY_TWENTY=overwrite {{cita}} {{kcl_lib_flags}} --no-quiet -- simulation_tests::kcl_samples::kcl_test_execute_{{test_name}} EXPECTORATE=overwrite TWENTY_TWENTY=overwrite {{cita}} {{kcl_lib_flags}} --no-quiet -- simulation_tests::kcl_samples::kcl_test_execute_{{test_name}}
ZOO_SIM_UPDATE=always EXPECTORATE=overwrite TWENTY_TWENTY=overwrite {{cita}} {{kcl_lib_flags}} --no-quiet -- simulation_tests::kcl_samples::test_after_engine EXPECTORATE=overwrite TWENTY_TWENTY=overwrite {{cita}} {{kcl_lib_flags}} --no-quiet -- simulation_tests::kcl_samples::test_after_engine
overwrite-sim-test test_name: overwrite-sim-test test_name:
ZOO_SIM_UPDATE=always EXPECTORATE=overwrite TWENTY_TWENTY=overwrite {{cita}} {{kcl_lib_flags}} --no-quiet -- simulation_tests::{{test_name}}::parse EXPECTORATE=overwrite TWENTY_TWENTY=overwrite {{cita}} {{kcl_lib_flags}} --no-quiet -- simulation_tests::{{test_name}}::parse
ZOO_SIM_UPDATE=always EXPECTORATE=overwrite TWENTY_TWENTY=overwrite {{cita}} {{kcl_lib_flags}} --no-quiet -- simulation_tests::{{test_name}}::unparse EXPECTORATE=overwrite TWENTY_TWENTY=overwrite {{cita}} {{kcl_lib_flags}} --no-quiet -- simulation_tests::{{test_name}}::unparse
ZOO_SIM_UPDATE=always EXPECTORATE=overwrite TWENTY_TWENTY=overwrite {{cita}} {{kcl_lib_flags}} --no-quiet -- simulation_tests::{{test_name}}::kcl_test_execute EXPECTORATE=overwrite TWENTY_TWENTY=overwrite {{cita}} {{kcl_lib_flags}} --no-quiet -- simulation_tests::{{test_name}}::kcl_test_execute
[ {{test_name}} != "kcl_samples" ] || ZOO_SIM_UPDATE=always EXPECTORATE=overwrite TWENTY_TWENTY=overwrite {{cita}} {{kcl_lib_flags}} --no-quiet -- simulation_tests::{{test_name}}::test_after_engine [ {{test_name}} != "kcl_samples" ] || EXPECTORATE=overwrite TWENTY_TWENTY=overwrite {{cita}} {{kcl_lib_flags}} --no-quiet -- simulation_tests::{{test_name}}::test_after_engine
# Regenerate all the simulation test output. # Regenerate all the simulation test output.
redo-sim-tests: redo-sim-tests:
ZOO_SIM_UPDATE=always EXPECTORATE=overwrite TWENTY_TWENTY=overwrite {{cita}} {{kcl_lib_flags}} --no-quiet -- simulation_tests EXPECTORATE=overwrite TWENTY_TWENTY=overwrite {{cita}} {{kcl_lib_flags}} --no-quiet -- simulation_tests
test: test:
cargo install cargo-nextest cargo install cargo-nextest

View File

@ -1,7 +1,7 @@
[package] [package]
name = "kcl-bumper" name = "kcl-bumper"
version = "0.1.76" version = "0.1.74"
edition = "2021" edition = "2021"
repository = "https://github.com/KittyCAD/modeling-api" repository = "https://github.com/KittyCAD/modeling-api"
rust-version = "1.76" rust-version = "1.76"

View File

@ -1,7 +1,7 @@
[package] [package]
name = "kcl-derive-docs" name = "kcl-derive-docs"
description = "A tool for generating documentation from Rust derive macros" description = "A tool for generating documentation from Rust derive macros"
version = "0.1.76" version = "0.1.74"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app" repository = "https://github.com/KittyCAD/modeling-app"

View File

@ -1,139 +0,0 @@
use proc_macro2::Span;
use quote::{quote, ToTokens};
pub fn do_for_each_example_test(item: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
let item: syn::ItemFn = syn::parse2(item.clone()).unwrap();
let mut result = proc_macro2::TokenStream::new();
for name in TEST_NAMES {
let mut item = item.clone();
item.sig.ident = syn::Ident::new(
&format!("{}_{}", item.sig.ident, name.replace('-', "_")),
Span::call_site(),
);
let name = name.to_owned();
let stmts = &item.block.stmts;
let block = quote! {
{
const NAME: &str = #name;
#(#stmts)*
}
};
item.block = Box::new(syn::parse2(block).unwrap());
result.extend(Some(item.into_token_stream()));
}
result
}
pub fn do_for_all_example_test(item: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
let mut item: syn::ItemFn = syn::parse2(item).unwrap();
let len = TEST_NAMES.len();
let stmts = &item.block.stmts;
let test_names = TEST_NAMES.iter().map(|n| n.to_owned());
let block = quote! {
{
const TEST_NAMES: [&str; #len] = [#(#test_names,)*];
#(#stmts)*
}
};
item.block = Box::new(syn::parse2(block).unwrap());
item.into_token_stream()
}
pub const TEST_NAMES: [&str; 93] = [
"std-array-map-0",
"std-array-map-1",
"std-array-pop-0",
"std-array-push-0",
"std-array-reduce-0",
"std-array-reduce-1",
"std-array-reduce-2",
"std-clone-0",
"std-clone-1",
"std-clone-2",
"std-clone-3",
"std-clone-4",
"std-clone-5",
"std-clone-6",
"std-clone-7",
"std-clone-8",
"std-clone-9",
"std-helix-0",
"std-helix-1",
"std-helix-2",
"std-helix-3",
"std-math-abs-0",
"std-math-acos-0",
"std-math-asin-0",
"std-math-atan-0",
"std-math-atan2-0",
"std-math-ceil-0",
"std-math-cos-0",
"std-math-floor-0",
"std-math-ln-0",
"std-math-legLen-0",
"std-math-legAngX-0",
"std-math-legAngY-0",
"std-math-log-0",
"std-math-log10-0",
"std-math-log2-0",
"std-math-max-0",
"std-math-min-0",
"std-math-polar-0",
"std-math-pow-0",
"std-math-rem-0",
"std-math-round-0",
"std-math-sin-0",
"std-math-sqrt-0",
"std-math-tan-0",
"std-offsetPlane-0",
"std-offsetPlane-1",
"std-offsetPlane-2",
"std-offsetPlane-3",
"std-offsetPlane-4",
"std-sketch-circle-0",
"std-sketch-circle-1",
"std-sketch-patternTransform2d-0",
"std-sketch-revolve-0",
"std-sketch-revolve-1",
"std-sketch-revolve-10",
"std-sketch-revolve-11",
"std-sketch-revolve-12",
"std-sketch-revolve-2",
"std-sketch-revolve-3",
"std-sketch-revolve-4",
"std-sketch-revolve-5",
"std-sketch-revolve-6",
"std-sketch-revolve-7",
"std-sketch-revolve-8",
"std-sketch-revolve-9",
"std-solid-chamfer-0",
"std-solid-chamfer-1",
"std-solid-fillet-0",
"std-solid-fillet-1",
"std-solid-hollow-0",
"std-solid-hollow-1",
"std-solid-hollow-2",
"std-solid-patternTransform-0",
"std-solid-patternTransform-1",
"std-solid-patternTransform-2",
"std-solid-patternTransform-3",
"std-solid-patternTransform-4",
"std-solid-patternTransform-5",
"std-solid-shell-0",
"std-solid-shell-1",
"std-solid-shell-2",
"std-solid-shell-3",
"std-solid-shell-4",
"std-solid-shell-5",
"std-solid-shell-6",
"std-transform-mirror2d-0",
"std-transform-mirror2d-1",
"std-transform-mirror2d-2",
"std-transform-mirror2d-3",
"std-transform-mirror2d-4",
"std-units-toDegrees-0",
"std-units-toRadians-0",
];

View File

@ -2,16 +2,16 @@
// automated enforcement. // automated enforcement.
#![allow(clippy::style)] #![allow(clippy::style)]
mod example_tests;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
mod unbox; mod unbox;
use std::collections::HashMap; use std::{collections::HashMap, fs};
use convert_case::Casing; use convert_case::Casing;
use inflector::{cases::camelcase::to_camel_case, Inflector}; use inflector::{cases::camelcase::to_camel_case, Inflector};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use proc_macro2::Span;
use quote::{format_ident, quote, quote_spanned, ToTokens}; use quote::{format_ident, quote, quote_spanned, ToTokens};
use regex::Regex; use regex::Regex;
use serde::Deserialize; use serde::Deserialize;
@ -28,13 +28,8 @@ pub fn stdlib(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> p
} }
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn for_each_example_test(_attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream { pub fn for_each_std_mod(_attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream {
example_tests::do_for_each_example_test(item.into()).into() do_for_each_std_mod(item.into()).into()
}
#[proc_macro_attribute]
pub fn for_all_example_test(_attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream {
example_tests::do_for_all_example_test(item.into()).into()
} }
/// Describes an argument of a stdlib function. /// Describes an argument of a stdlib function.
@ -47,14 +42,6 @@ struct ArgMetadata {
/// Does not do anything if the argument is already required. /// Does not do anything if the argument is already required.
#[serde(default)] #[serde(default)]
include_in_snippet: bool, include_in_snippet: bool,
/// The snippet should suggest this value for the arg.
#[serde(default)]
snippet_value: Option<String>,
/// The snippet should suggest this value for the arg.
#[serde(default)]
snippet_value_array: Option<Vec<String>>,
} }
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
@ -82,6 +69,11 @@ struct StdlibMetadata {
#[serde(default)] #[serde(default)]
feature_tree_operation: bool, feature_tree_operation: bool,
/// If true, expects keyword arguments.
/// If false, expects positional arguments.
#[serde(default)]
keywords: bool,
/// If true, the first argument is unlabeled. /// If true, the first argument is unlabeled.
/// If false, all arguments require labels. /// If false, all arguments require labels.
#[serde(default)] #[serde(default)]
@ -100,6 +92,34 @@ fn do_stdlib(
do_stdlib_inner(metadata, attr, item) do_stdlib_inner(metadata, attr, item)
} }
fn do_for_each_std_mod(item: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
let item: syn::ItemFn = syn::parse2(item.clone()).unwrap();
let mut result = proc_macro2::TokenStream::new();
for name in fs::read_dir("kcl-lib/std").unwrap().filter_map(|e| {
let e = e.unwrap();
let filename = e.file_name();
filename.to_str().unwrap().strip_suffix(".kcl").map(str::to_owned)
}) {
for i in 0..10_usize {
let mut item = item.clone();
item.sig.ident = syn::Ident::new(&format!("{}_{}_shard_{i}", item.sig.ident, name), Span::call_site());
let stmts = &item.block.stmts;
let block = quote! {
{
const STD_MOD_NAME: &str = #name;
const SHARD: usize = #i;
const SHARD_COUNT: usize = 10;
#(#stmts)*
}
};
item.block = Box::new(syn::parse2(block).unwrap());
result.extend(Some(item.into_token_stream()));
}
}
result
}
fn do_output(res: Result<(proc_macro2::TokenStream, Vec<Error>), Error>) -> proc_macro::TokenStream { fn do_output(res: Result<(proc_macro2::TokenStream, Vec<Error>), Error>) -> proc_macro::TokenStream {
match res { match res {
Err(err) => err.to_compile_error().into(), Err(err) => err.to_compile_error().into(),
@ -287,6 +307,12 @@ fn do_stdlib_inner(
quote! { false } quote! { false }
}; };
let uses_keyword_arguments = if metadata.keywords {
quote! { true }
} else {
quote! { false }
};
let docs_crate = get_crate(None); let docs_crate = get_crate(None);
// When the user attaches this proc macro to a function with the wrong type // When the user attaches this proc macro to a function with the wrong type
@ -315,10 +341,6 @@ fn do_stdlib_inner(
} }
.trim_start_matches('_') .trim_start_matches('_')
.to_string(); .to_string();
// These aren't really KCL args, they're just state that each stdlib function's impl needs.
if arg_name == "exec_state" || arg_name == "args" {
continue;
}
let ty = match arg { let ty = match arg {
syn::FnArg::Receiver(pat) => pat.ty.as_ref().into_token_stream(), syn::FnArg::Receiver(pat) => pat.ty.as_ref().into_token_stream(),
@ -329,24 +351,27 @@ fn do_stdlib_inner(
let ty_string = rust_type_to_openapi_type(&ty_string); let ty_string = rust_type_to_openapi_type(&ty_string);
let required = !ty_ident.to_string().starts_with("Option <"); let required = !ty_ident.to_string().starts_with("Option <");
let Some(arg_meta) = metadata.args.get(&arg_name) else { let arg_meta = metadata.args.get(&arg_name);
errors.push(Error::new_spanned(arg, format!("arg {arg_name} not found"))); let description = if let Some(s) = arg_meta.map(|arg| &arg.docs) {
quote! { #s }
} else if metadata.keywords && ty_string != "Args" && ty_string != "ExecState" {
errors.push(Error::new_spanned(
&arg,
"Argument was not documented in the args block",
));
continue; continue;
} else {
quote! { String::new() }
}; };
let description = arg_meta.docs.clone(); let include_in_snippet = required || arg_meta.map(|arg| arg.include_in_snippet).unwrap_or_default();
let include_in_snippet = required || arg_meta.include_in_snippet;
let snippet_value = arg_meta.snippet_value.clone();
let snippet_value_array = arg_meta.snippet_value_array.clone();
if snippet_value.is_some() && snippet_value_array.is_some() {
errors.push(Error::new_spanned(arg, format!("arg {arg_name} has set both snippet_value and snippet_value array, but at most one of these may be set. Please delete one of them.")));
}
let label_required = !(i == 0 && metadata.unlabeled_first); let label_required = !(i == 0 && metadata.unlabeled_first);
let camel_case_arg_name = to_camel_case(&arg_name); let camel_case_arg_name = to_camel_case(&arg_name);
if ty_string != "ExecState" && ty_string != "Args" { if ty_string != "ExecState" && ty_string != "Args" {
let schema = quote! { let schema = quote! {
generator.root_schema_for::<#ty_ident>() generator.root_schema_for::<#ty_ident>()
}; };
let q0 = quote! { arg_types.push(quote! {
#docs_crate::StdLibFnArg {
name: #camel_case_arg_name.to_string(), name: #camel_case_arg_name.to_string(),
type_: #ty_string.to_string(), type_: #ty_string.to_string(),
schema: #schema, schema: #schema,
@ -354,32 +379,6 @@ fn do_stdlib_inner(
label_required: #label_required, label_required: #label_required,
description: #description.to_string(), description: #description.to_string(),
include_in_snippet: #include_in_snippet, include_in_snippet: #include_in_snippet,
};
let q1 = if let Some(snippet_value) = snippet_value {
quote! {
snippet_value: Some(#snippet_value.to_owned()),
}
} else {
quote! {
snippet_value: None,
}
};
let q2 = if let Some(snippet_value_array) = snippet_value_array {
quote! {
snippet_value_array: Some(vec![
#(#snippet_value_array.to_owned()),*
]),
}
} else {
quote! {
snippet_value_array: None,
}
};
arg_types.push(quote! {
#docs_crate::StdLibFnArg {
#q0
#q1
#q2
} }
}); });
} }
@ -443,8 +442,6 @@ fn do_stdlib_inner(
label_required: true, label_required: true,
description: String::new(), description: String::new(),
include_in_snippet: true, include_in_snippet: true,
snippet_value: None,
snippet_value_array: None,
}) })
} }
} else { } else {
@ -511,6 +508,10 @@ fn do_stdlib_inner(
vec![#(#tags),*] vec![#(#tags),*]
} }
fn keyword_arguments(&self) -> bool {
#uses_keyword_arguments
}
fn args(&self, inline_subschemas: bool) -> Vec<#docs_crate::StdLibFnArg> { fn args(&self, inline_subschemas: bool) -> Vec<#docs_crate::StdLibFnArg> {
let mut settings = schemars::gen::SchemaSettings::openapi3(); let mut settings = schemars::gen::SchemaSettings::openapi3();
// We set this to false so we can recurse them later. // We set this to false so we can recurse them later.

View File

@ -40,9 +40,6 @@ fn test_args_with_refs() {
let (item, mut errors) = do_stdlib( let (item, mut errors) = do_stdlib(
quote! { quote! {
name = "someFn", name = "someFn",
args = {
data = { docs = "The data for this function"},
},
}, },
quote! { quote! {
/// Docs /// Docs
@ -68,9 +65,6 @@ fn test_args_with_lifetime() {
let (item, mut errors) = do_stdlib( let (item, mut errors) = do_stdlib(
quote! { quote! {
name = "someFn", name = "someFn",
args = {
data = { docs = "Arg for the function" },
}
}, },
quote! { quote! {
/// Docs /// Docs
@ -123,8 +117,7 @@ fn test_stdlib_line_to() {
quote! { quote! {
name = "lineTo", name = "lineTo",
args = { args = {
data = { docs = "the sketch you're adding the line to" }, sketch = { docs = "the sketch you're adding the line to" }
sketch = { docs = "the sketch you're adding the line to" },
} }
}, },
quote! { quote! {

View File

@ -91,6 +91,10 @@ impl crate::docs::StdLibFn for SomeFn {
vec![] vec![]
} }
fn keyword_arguments(&self) -> bool {
false
}
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> { fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
let mut settings = schemars::gen::SchemaSettings::openapi3(); let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = inline_subschemas; settings.inline_subschemas = inline_subschemas;
@ -101,10 +105,8 @@ impl crate::docs::StdLibFn for SomeFn {
schema: generator.root_schema_for::<Foo>(), schema: generator.root_schema_for::<Foo>(),
required: true, required: true,
label_required: true, label_required: true,
description: "Arg for the function".to_string(), description: String::new().to_string(),
include_in_snippet: true, include_in_snippet: true,
snippet_value: None,
snippet_value_array: None,
}] }]
} }
@ -121,8 +123,6 @@ impl crate::docs::StdLibFn for SomeFn {
label_required: true, label_required: true,
description: String::new(), description: String::new(),
include_in_snippet: true, include_in_snippet: true,
snippet_value: None,
snippet_value_array: None,
}) })
} }

View File

@ -91,6 +91,10 @@ impl crate::docs::StdLibFn for SomeFn {
vec![] vec![]
} }
fn keyword_arguments(&self) -> bool {
false
}
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> { fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
let mut settings = schemars::gen::SchemaSettings::openapi3(); let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = inline_subschemas; settings.inline_subschemas = inline_subschemas;
@ -101,10 +105,8 @@ impl crate::docs::StdLibFn for SomeFn {
schema: generator.root_schema_for::<str>(), schema: generator.root_schema_for::<str>(),
required: true, required: true,
label_required: true, label_required: true,
description: "The data for this function".to_string(), description: String::new().to_string(),
include_in_snippet: true, include_in_snippet: true,
snippet_value: None,
snippet_value_array: None,
}] }]
} }
@ -121,8 +123,6 @@ impl crate::docs::StdLibFn for SomeFn {
label_required: true, label_required: true,
description: String::new(), description: String::new(),
include_in_snippet: true, include_in_snippet: true,
snippet_value: None,
snippet_value_array: None,
}) })
} }

View File

@ -92,11 +92,23 @@ impl crate::docs::StdLibFn for Show {
vec![] vec![]
} }
fn keyword_arguments(&self) -> bool {
false
}
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> { fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
let mut settings = schemars::gen::SchemaSettings::openapi3(); let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = inline_subschemas; settings.inline_subschemas = inline_subschemas;
let mut generator = schemars::gen::SchemaGenerator::new(settings); let mut generator = schemars::gen::SchemaGenerator::new(settings);
vec![] vec![crate::docs::StdLibFnArg {
name: "args".to_string(),
type_: "[number]".to_string(),
schema: generator.root_schema_for::<[f64; 2usize]>(),
required: true,
label_required: true,
description: String::new().to_string(),
include_in_snippet: true,
}]
} }
fn return_value(&self, inline_subschemas: bool) -> Option<crate::docs::StdLibFnArg> { fn return_value(&self, inline_subschemas: bool) -> Option<crate::docs::StdLibFnArg> {
@ -112,8 +124,6 @@ impl crate::docs::StdLibFn for Show {
label_required: true, label_required: true,
description: String::new(), description: String::new(),
include_in_snippet: true, include_in_snippet: true,
snippet_value: None,
snippet_value_array: None,
}) })
} }

View File

@ -92,11 +92,23 @@ impl crate::docs::StdLibFn for Show {
vec![] vec![]
} }
fn keyword_arguments(&self) -> bool {
false
}
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> { fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
let mut settings = schemars::gen::SchemaSettings::openapi3(); let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = inline_subschemas; settings.inline_subschemas = inline_subschemas;
let mut generator = schemars::gen::SchemaGenerator::new(settings); let mut generator = schemars::gen::SchemaGenerator::new(settings);
vec![] vec![crate::docs::StdLibFnArg {
name: "args".to_string(),
type_: "number".to_string(),
schema: generator.root_schema_for::<f64>(),
required: true,
label_required: true,
description: String::new().to_string(),
include_in_snippet: true,
}]
} }
fn return_value(&self, inline_subschemas: bool) -> Option<crate::docs::StdLibFnArg> { fn return_value(&self, inline_subschemas: bool) -> Option<crate::docs::StdLibFnArg> {
@ -112,8 +124,6 @@ impl crate::docs::StdLibFn for Show {
label_required: true, label_required: true,
description: String::new(), description: String::new(),
include_in_snippet: true, include_in_snippet: true,
snippet_value: None,
snippet_value_array: None,
}) })
} }

View File

@ -93,11 +93,23 @@ impl crate::docs::StdLibFn for MyFunc {
vec![] vec![]
} }
fn keyword_arguments(&self) -> bool {
false
}
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> { fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
let mut settings = schemars::gen::SchemaSettings::openapi3(); let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = inline_subschemas; settings.inline_subschemas = inline_subschemas;
let mut generator = schemars::gen::SchemaGenerator::new(settings); let mut generator = schemars::gen::SchemaGenerator::new(settings);
vec![] vec![crate::docs::StdLibFnArg {
name: "args".to_string(),
type_: "kittycad::types::InputFormat".to_string(),
schema: generator.root_schema_for::<Option<kittycad::types::InputFormat>>(),
required: false,
label_required: true,
description: String::new().to_string(),
include_in_snippet: false,
}]
} }
fn return_value(&self, inline_subschemas: bool) -> Option<crate::docs::StdLibFnArg> { fn return_value(&self, inline_subschemas: bool) -> Option<crate::docs::StdLibFnArg> {
@ -113,8 +125,6 @@ impl crate::docs::StdLibFn for MyFunc {
label_required: true, label_required: true,
description: String::new(), description: String::new(),
include_in_snippet: true, include_in_snippet: true,
snippet_value: None,
snippet_value_array: None,
}) })
} }

View File

@ -93,6 +93,10 @@ impl crate::docs::StdLibFn for LineTo {
vec![] vec![]
} }
fn keyword_arguments(&self) -> bool {
false
}
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> { fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
let mut settings = schemars::gen::SchemaSettings::openapi3(); let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = inline_subschemas; settings.inline_subschemas = inline_subschemas;
@ -104,10 +108,8 @@ impl crate::docs::StdLibFn for LineTo {
schema: generator.root_schema_for::<LineToData>(), schema: generator.root_schema_for::<LineToData>(),
required: true, required: true,
label_required: true, label_required: true,
description: "the sketch you're adding the line to".to_string(), description: String::new().to_string(),
include_in_snippet: true, include_in_snippet: true,
snippet_value: None,
snippet_value_array: None,
}, },
crate::docs::StdLibFnArg { crate::docs::StdLibFnArg {
name: "sketch".to_string(), name: "sketch".to_string(),
@ -117,8 +119,6 @@ impl crate::docs::StdLibFn for LineTo {
label_required: true, label_required: true,
description: "the sketch you're adding the line to".to_string(), description: "the sketch you're adding the line to".to_string(),
include_in_snippet: true, include_in_snippet: true,
snippet_value: None,
snippet_value_array: None,
}, },
] ]
} }
@ -136,8 +136,6 @@ impl crate::docs::StdLibFn for LineTo {
label_required: true, label_required: true,
description: String::new(), description: String::new(),
include_in_snippet: true, include_in_snippet: true,
snippet_value: None,
snippet_value_array: None,
}) })
} }

View File

@ -92,11 +92,23 @@ impl crate::docs::StdLibFn for Min {
vec![] vec![]
} }
fn keyword_arguments(&self) -> bool {
false
}
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> { fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
let mut settings = schemars::gen::SchemaSettings::openapi3(); let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = inline_subschemas; settings.inline_subschemas = inline_subschemas;
let mut generator = schemars::gen::SchemaGenerator::new(settings); let mut generator = schemars::gen::SchemaGenerator::new(settings);
vec![] vec![crate::docs::StdLibFnArg {
name: "args".to_string(),
type_: "[number]".to_string(),
schema: generator.root_schema_for::<Vec<f64>>(),
required: true,
label_required: true,
description: String::new().to_string(),
include_in_snippet: true,
}]
} }
fn return_value(&self, inline_subschemas: bool) -> Option<crate::docs::StdLibFnArg> { fn return_value(&self, inline_subschemas: bool) -> Option<crate::docs::StdLibFnArg> {
@ -112,8 +124,6 @@ impl crate::docs::StdLibFn for Min {
label_required: true, label_required: true,
description: String::new(), description: String::new(),
include_in_snippet: true, include_in_snippet: true,
snippet_value: None,
snippet_value_array: None,
}) })
} }

View File

@ -92,11 +92,23 @@ impl crate::docs::StdLibFn for Show {
vec![] vec![]
} }
fn keyword_arguments(&self) -> bool {
false
}
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> { fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
let mut settings = schemars::gen::SchemaSettings::openapi3(); let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = inline_subschemas; settings.inline_subschemas = inline_subschemas;
let mut generator = schemars::gen::SchemaGenerator::new(settings); let mut generator = schemars::gen::SchemaGenerator::new(settings);
vec![] vec![crate::docs::StdLibFnArg {
name: "args".to_string(),
type_: "number".to_string(),
schema: generator.root_schema_for::<Option<f64>>(),
required: false,
label_required: true,
description: String::new().to_string(),
include_in_snippet: false,
}]
} }
fn return_value(&self, inline_subschemas: bool) -> Option<crate::docs::StdLibFnArg> { fn return_value(&self, inline_subschemas: bool) -> Option<crate::docs::StdLibFnArg> {
@ -112,8 +124,6 @@ impl crate::docs::StdLibFn for Show {
label_required: true, label_required: true,
description: String::new(), description: String::new(),
include_in_snippet: true, include_in_snippet: true,
snippet_value: None,
snippet_value_array: None,
}) })
} }

View File

@ -92,11 +92,23 @@ impl crate::docs::StdLibFn for Import {
vec![] vec![]
} }
fn keyword_arguments(&self) -> bool {
false
}
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> { fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
let mut settings = schemars::gen::SchemaSettings::openapi3(); let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = inline_subschemas; settings.inline_subschemas = inline_subschemas;
let mut generator = schemars::gen::SchemaGenerator::new(settings); let mut generator = schemars::gen::SchemaGenerator::new(settings);
vec![] vec![crate::docs::StdLibFnArg {
name: "args".to_string(),
type_: "kittycad::types::InputFormat".to_string(),
schema: generator.root_schema_for::<Option<kittycad::types::InputFormat>>(),
required: false,
label_required: true,
description: String::new().to_string(),
include_in_snippet: false,
}]
} }
fn return_value(&self, inline_subschemas: bool) -> Option<crate::docs::StdLibFnArg> { fn return_value(&self, inline_subschemas: bool) -> Option<crate::docs::StdLibFnArg> {
@ -112,8 +124,6 @@ impl crate::docs::StdLibFn for Import {
label_required: true, label_required: true,
description: String::new(), description: String::new(),
include_in_snippet: true, include_in_snippet: true,
snippet_value: None,
snippet_value_array: None,
}) })
} }

View File

@ -92,11 +92,23 @@ impl crate::docs::StdLibFn for Import {
vec![] vec![]
} }
fn keyword_arguments(&self) -> bool {
false
}
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> { fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
let mut settings = schemars::gen::SchemaSettings::openapi3(); let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = inline_subschemas; settings.inline_subschemas = inline_subschemas;
let mut generator = schemars::gen::SchemaGenerator::new(settings); let mut generator = schemars::gen::SchemaGenerator::new(settings);
vec![] vec![crate::docs::StdLibFnArg {
name: "args".to_string(),
type_: "kittycad::types::InputFormat".to_string(),
schema: generator.root_schema_for::<Option<kittycad::types::InputFormat>>(),
required: false,
label_required: true,
description: String::new().to_string(),
include_in_snippet: false,
}]
} }
fn return_value(&self, inline_subschemas: bool) -> Option<crate::docs::StdLibFnArg> { fn return_value(&self, inline_subschemas: bool) -> Option<crate::docs::StdLibFnArg> {
@ -112,8 +124,6 @@ impl crate::docs::StdLibFn for Import {
label_required: true, label_required: true,
description: String::new(), description: String::new(),
include_in_snippet: true, include_in_snippet: true,
snippet_value: None,
snippet_value_array: None,
}) })
} }

View File

@ -92,11 +92,23 @@ impl crate::docs::StdLibFn for Import {
vec![] vec![]
} }
fn keyword_arguments(&self) -> bool {
false
}
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> { fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
let mut settings = schemars::gen::SchemaSettings::openapi3(); let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = inline_subschemas; settings.inline_subschemas = inline_subschemas;
let mut generator = schemars::gen::SchemaGenerator::new(settings); let mut generator = schemars::gen::SchemaGenerator::new(settings);
vec![] vec![crate::docs::StdLibFnArg {
name: "args".to_string(),
type_: "kittycad::types::InputFormat".to_string(),
schema: generator.root_schema_for::<Option<kittycad::types::InputFormat>>(),
required: false,
label_required: true,
description: String::new().to_string(),
include_in_snippet: false,
}]
} }
fn return_value(&self, inline_subschemas: bool) -> Option<crate::docs::StdLibFnArg> { fn return_value(&self, inline_subschemas: bool) -> Option<crate::docs::StdLibFnArg> {
@ -112,8 +124,6 @@ impl crate::docs::StdLibFn for Import {
label_required: true, label_required: true,
description: String::new(), description: String::new(),
include_in_snippet: true, include_in_snippet: true,
snippet_value: None,
snippet_value_array: None,
}) })
} }

View File

@ -92,11 +92,23 @@ impl crate::docs::StdLibFn for Show {
vec![] vec![]
} }
fn keyword_arguments(&self) -> bool {
false
}
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> { fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
let mut settings = schemars::gen::SchemaSettings::openapi3(); let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = inline_subschemas; settings.inline_subschemas = inline_subschemas;
let mut generator = schemars::gen::SchemaGenerator::new(settings); let mut generator = schemars::gen::SchemaGenerator::new(settings);
vec![] vec![crate::docs::StdLibFnArg {
name: "args".to_string(),
type_: "[number]".to_string(),
schema: generator.root_schema_for::<Vec<f64>>(),
required: true,
label_required: true,
description: String::new().to_string(),
include_in_snippet: true,
}]
} }
fn return_value(&self, inline_subschemas: bool) -> Option<crate::docs::StdLibFnArg> { fn return_value(&self, inline_subschemas: bool) -> Option<crate::docs::StdLibFnArg> {
@ -112,8 +124,6 @@ impl crate::docs::StdLibFn for Show {
label_required: true, label_required: true,
description: String::new(), description: String::new(),
include_in_snippet: true, include_in_snippet: true,
snippet_value: None,
snippet_value_array: None,
}) })
} }

View File

@ -91,6 +91,10 @@ impl crate::docs::StdLibFn for SomeFunction {
vec![] vec![]
} }
fn keyword_arguments(&self) -> bool {
false
}
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> { fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
let mut settings = schemars::gen::SchemaSettings::openapi3(); let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = inline_subschemas; settings.inline_subschemas = inline_subschemas;
@ -111,8 +115,6 @@ impl crate::docs::StdLibFn for SomeFunction {
label_required: true, label_required: true,
description: String::new(), description: String::new(),
include_in_snippet: true, include_in_snippet: true,
snippet_value: None,
snippet_value_array: None,
}) })
} }

View File

@ -1,7 +1,7 @@
[package] [package]
name = "kcl-directory-test-macro" name = "kcl-directory-test-macro"
description = "A tool for generating tests from a directory of kcl files" description = "A tool for generating tests from a directory of kcl files"
version = "0.1.76" version = "0.1.74"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app" repository = "https://github.com/KittyCAD/modeling-app"

View File

@ -1,6 +1,6 @@
[package] [package]
name = "kcl-language-server-release" name = "kcl-language-server-release"
version = "0.1.76" version = "0.1.74"
edition = "2021" edition = "2021"
authors = ["KittyCAD Inc <kcl@kittycad.io>"] authors = ["KittyCAD Inc <kcl@kittycad.io>"]
publish = false publish = false

View File

@ -2,7 +2,7 @@
name = "kcl-language-server" name = "kcl-language-server"
description = "A language server for KCL." description = "A language server for KCL."
authors = ["KittyCAD Inc <kcl@kittycad.io>"] authors = ["KittyCAD Inc <kcl@kittycad.io>"]
version = "0.2.76" version = "0.2.74"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@ -1,7 +1,7 @@
[package] [package]
name = "kcl-lib" name = "kcl-lib"
description = "KittyCAD Language implementation and tools" description = "KittyCAD Language implementation and tools"
version = "0.2.76" version = "0.2.74"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app" repository = "https://github.com/KittyCAD/modeling-app"

View File

@ -2,7 +2,7 @@ mod cache;
use kcl_lib::{ use kcl_lib::{
test_server::{execute_and_export_step, execute_and_snapshot, execute_and_snapshot_no_auth}, test_server::{execute_and_export_step, execute_and_snapshot, execute_and_snapshot_no_auth},
BacktraceItem, ExecError, ModuleId, SourceRange, ExecError,
}; };
/// The minimum permissible difference between asserted twenty-twenty images. /// The minimum permissible difference between asserted twenty-twenty images.
@ -441,15 +441,10 @@ async fn kcl_test_import_file_doesnt_exist() {
model = cube"#; model = cube"#;
let result = execute_and_snapshot(code, None).await; let result = execute_and_snapshot(code, None).await;
let err = result.unwrap_err(); assert!(result.is_err());
let err = err.as_kcl_error().unwrap();
assert_eq!(err.message(), "File `thing.obj` does not exist.");
assert_eq!( assert_eq!(
err.backtrace(), result.err().unwrap().to_string(),
vec![BacktraceItem { r#"semantic: KclErrorDetails { source_ranges: [SourceRange([0, 18, 0])], message: "File `thing.obj` does not exist." }"#
source_range: SourceRange::new(0, 18, ModuleId::default()),
fn_name: None,
}]
); );
} }
@ -524,18 +519,10 @@ import 'e2e/executor/inputs/cube.gltf'
model = cube"#; model = cube"#;
let result = execute_and_snapshot(code, None).await; let result = execute_and_snapshot(code, None).await;
let err = result.unwrap_err(); assert!(result.is_err());
let err = err.as_kcl_error().unwrap();
assert_eq!( assert_eq!(
err.message(), result.err().unwrap().to_string(),
"The given format does not match the file extension. Expected: `gltf`, Given: `obj`" r#"semantic: KclErrorDetails { source_ranges: [SourceRange([32, 70, 0])], message: "The given format does not match the file extension. Expected: `gltf`, Given: `obj`" }"#
);
assert_eq!(
err.backtrace(),
vec![BacktraceItem {
source_range: SourceRange::new(32, 70, ModuleId::default()),
fn_name: None,
}]
); );
} }
@ -1679,15 +1666,10 @@ example = extrude(exampleSketch, length = 10)
"#; "#;
let result = execute_and_snapshot(code, None).await; let result = execute_and_snapshot(code, None).await;
let err = result.unwrap_err(); assert!(result.is_err());
let err = err.as_kcl_error().unwrap();
assert_eq!(err.message(), "Cannot have an x constrained angle of 90 degrees");
assert_eq!( assert_eq!(
err.backtrace(), result.err().unwrap().to_string(),
vec![BacktraceItem { r#"type: KclErrorDetails { source_ranges: [SourceRange([70, 111, 0])], message: "Cannot have an x constrained angle of 90 degrees" }"#
source_range: SourceRange::new(70, 111, ModuleId::default()),
fn_name: Some("angledLine".to_owned())
}]
); );
} }
@ -1704,15 +1686,10 @@ example = extrude(exampleSketch, length = 10)
"#; "#;
let result = execute_and_snapshot(code, None).await; let result = execute_and_snapshot(code, None).await;
let err = result.unwrap_err(); assert!(result.is_err());
let err = err.as_kcl_error().unwrap();
assert_eq!(err.message(), "Cannot have an x constrained angle of 270 degrees");
assert_eq!( assert_eq!(
err.backtrace(), result.err().unwrap().to_string(),
vec![BacktraceItem { r#"type: KclErrorDetails { source_ranges: [SourceRange([70, 112, 0])], message: "Cannot have an x constrained angle of 270 degrees" }"#
source_range: SourceRange::new(70, 112, ModuleId::default()),
fn_name: Some("angledLine".to_owned())
}]
); );
} }
@ -1729,15 +1706,10 @@ example = extrude(exampleSketch, length = 10)
"#; "#;
let result = execute_and_snapshot(code, None).await; let result = execute_and_snapshot(code, None).await;
let err = result.unwrap_err(); assert!(result.is_err());
let err = err.as_kcl_error().unwrap();
assert_eq!(err.message(), "Cannot have a y constrained angle of 0 degrees");
assert_eq!( assert_eq!(
err.backtrace(), result.err().unwrap().to_string(),
vec![BacktraceItem { r#"type: KclErrorDetails { source_ranges: [SourceRange([70, 110, 0])], message: "Cannot have a y constrained angle of 0 degrees" }"#
source_range: SourceRange::new(70, 110, ModuleId::default()),
fn_name: Some("angledLine".to_owned())
}]
); );
} }
@ -1754,15 +1726,10 @@ example = extrude(exampleSketch, length = 10)
"#; "#;
let result = execute_and_snapshot(code, None).await; let result = execute_and_snapshot(code, None).await;
let err = result.unwrap_err(); assert!(result.is_err());
let err = err.as_kcl_error().unwrap();
assert_eq!(err.message(), "Cannot have a y constrained angle of 180 degrees");
assert_eq!( assert_eq!(
err.backtrace(), result.err().unwrap().to_string(),
vec![BacktraceItem { r#"type: KclErrorDetails { source_ranges: [SourceRange([70, 112, 0])], message: "Cannot have a y constrained angle of 180 degrees" }"#
source_range: SourceRange::new(70, 112, ModuleId::default()),
fn_name: Some("angledLine".to_owned())
}]
); );
} }
@ -1779,15 +1746,10 @@ extrusion = extrude(sketch001, length = 10)
"#; "#;
let result = execute_and_snapshot(code, None).await; let result = execute_and_snapshot(code, None).await;
let err = result.unwrap_err(); assert!(result.is_err());
let err = err.as_kcl_error().unwrap();
assert_eq!(err.message(), "Cannot have an x constrained angle of 90 degrees");
assert_eq!( assert_eq!(
err.backtrace(), result.err().unwrap().to_string(),
vec![BacktraceItem { r#"type: KclErrorDetails { source_ranges: [SourceRange([66, 116, 0])], message: "Cannot have an x constrained angle of 90 degrees" }"#
source_range: SourceRange::new(66, 116, ModuleId::default()),
fn_name: Some("angledLine".to_owned())
}]
); );
} }
@ -1795,7 +1757,7 @@ extrusion = extrude(sketch001, length = 10)
async fn kcl_test_angled_line_of_x_length_270() { async fn kcl_test_angled_line_of_x_length_270() {
let code = r#"sketch001 = startSketchOn(XZ) let code = r#"sketch001 = startSketchOn(XZ)
|> startProfile(at = [0, 0]) |> startProfile(at = [0, 0])
|> angledLine(angle = 270, lengthX = 90, tag = $edge1) |> angledLine(angle = 90, lengthX = 90, tag = $edge1)
|> angledLine(angle = -15, lengthX = -15, tag = $edge2) |> angledLine(angle = -15, lengthX = -15, tag = $edge2)
|> line(end = [0, -5]) |> line(end = [0, -5])
|> close(tag = $edge3) |> close(tag = $edge3)
@ -1804,15 +1766,10 @@ extrusion = extrude(sketch001, length = 10)
"#; "#;
let result = execute_and_snapshot(code, None).await; let result = execute_and_snapshot(code, None).await;
let err = result.unwrap_err(); assert!(result.is_err());
let err = err.as_kcl_error().unwrap();
assert_eq!(err.message(), "Cannot have an x constrained angle of 270 degrees");
assert_eq!( assert_eq!(
err.backtrace(), result.err().unwrap().to_string(),
vec![BacktraceItem { r#"type: KclErrorDetails { source_ranges: [SourceRange([66, 116, 0])], message: "Cannot have an x constrained angle of 90 degrees" }"#
source_range: SourceRange::new(66, 117, ModuleId::default()),
fn_name: Some("angledLine".to_owned())
}]
); );
} }
@ -1831,15 +1788,10 @@ example = extrude(exampleSketch, length = 10)
"#; "#;
let result = execute_and_snapshot(code, None).await; let result = execute_and_snapshot(code, None).await;
let err = result.unwrap_err(); assert!(result.is_err());
let err = err.as_kcl_error().unwrap();
assert_eq!(err.message(), "Cannot have a y constrained angle of 0 degrees");
assert_eq!( assert_eq!(
err.backtrace(), result.err().unwrap().to_string(),
vec![BacktraceItem { r#"type: KclErrorDetails { source_ranges: [SourceRange([95, 130, 0])], message: "Cannot have a y constrained angle of 0 degrees" }"#
source_range: SourceRange::new(95, 130, ModuleId::default()),
fn_name: Some("angledLine".to_owned())
}]
); );
} }
@ -1858,15 +1810,10 @@ example = extrude(exampleSketch, length = 10)
"#; "#;
let result = execute_and_snapshot(code, None).await; let result = execute_and_snapshot(code, None).await;
let err = result.unwrap_err(); assert!(result.is_err());
let err = err.as_kcl_error().unwrap();
assert_eq!(err.message(), "Cannot have a y constrained angle of 180 degrees");
assert_eq!( assert_eq!(
err.backtrace(), result.err().unwrap().to_string(),
vec![BacktraceItem { r#"type: KclErrorDetails { source_ranges: [SourceRange([95, 132, 0])], message: "Cannot have a y constrained angle of 180 degrees" }"#
source_range: SourceRange::new(95, 132, ModuleId::default()),
fn_name: Some("angledLine".to_owned())
}]
); );
} }
@ -1885,15 +1832,10 @@ example = extrude(exampleSketch, length = 10)
"#; "#;
let result = execute_and_snapshot(code, None).await; let result = execute_and_snapshot(code, None).await;
let err = result.unwrap_err(); assert!(result.is_err());
let err = err.as_kcl_error().unwrap();
assert_eq!(err.message(), "Cannot have a y constrained angle of 180 degrees");
assert_eq!( assert_eq!(
err.backtrace(), result.err().unwrap().to_string(),
vec![BacktraceItem { r#"type: KclErrorDetails { source_ranges: [SourceRange([95, 133, 0])], message: "Cannot have a y constrained angle of 180 degrees" }"#
source_range: SourceRange::new(95, 133, ModuleId::default()),
fn_name: Some("angledLine".to_owned())
}]
); );
} }
@ -1907,31 +1849,10 @@ someFunction('INVALID')
"#; "#;
let result = execute_and_snapshot(code, None).await; let result = execute_and_snapshot(code, None).await;
let err = result.unwrap_err(); assert!(result.is_err());
let err = err.as_kcl_error().unwrap();
assert_eq!( assert_eq!(
err.message(), result.err().unwrap().to_string(),
"This function expected the input argument to be Solid or Plane but it's actually of type string" r#"semantic: KclErrorDetails { source_ranges: [SourceRange([46, 55, 0]), SourceRange([60, 83, 0])], message: "This function expected the input argument to be Solid or Plane but it's actually of type string" }"#
);
assert_eq!(
err.source_ranges(),
vec![
SourceRange::new(46, 55, ModuleId::default()),
SourceRange::new(60, 83, ModuleId::default()),
]
);
assert_eq!(
err.backtrace(),
vec![
BacktraceItem {
source_range: SourceRange::new(46, 55, ModuleId::default()),
fn_name: Some("someFunction".to_owned()),
},
BacktraceItem {
source_range: SourceRange::new(60, 83, ModuleId::default()),
fn_name: None,
},
]
); );
} }
@ -1952,14 +1873,12 @@ async fn kcl_test_error_no_auth_websocket() {
"#; "#;
let result = execute_and_snapshot_no_auth(code, None).await; let result = execute_and_snapshot_no_auth(code, None).await;
let err = result.unwrap_err(); assert!(result.is_err());
let err = err.as_kcl_error().unwrap(); assert!(result
assert!( .err()
err.message() .unwrap()
.contains("Please send the following object over this websocket"), .to_string()
"actual: {}", .contains("Please send the following object over this websocket"));
err.message()
);
} }
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]

Some files were not shown because too many files have changed in this diff Show More