Compare commits

...

25 Commits

Author SHA1 Message Date
129b91518f Don't bail command palette if there's no engineConnection (#6286)
The connection can't be bad if there is not yet `:galaxy-brain:`.

But seriously this behavior regressed share links because we would bail
from the command palette because the connection wasn't healthy.
2025-04-11 18:24:56 +00:00
c0aa763c3b initPromise in main.ts (#6285)
* initPromise in main.ts

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

* move initPromise so dep tree doesnt shit the bed

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-04-11 18:17:46 +00:00
319c60d4fa BREAKING: Change tangential arc to keyword args (#6266)
* Change tangentialArc, tangentialArcTo, and tangentialArcToRelative to keyword args

* Change tangentialArc offset to angle and convert to kw arg calls

* Fix lints

* Fix sketch errors and all unit tests passing

* Fix tangentialArcTo calls in KCL samples

* Update tangentialArc in samples

* Update sim test output

* Fix formatting

* Fix mistake in merge

* Fix gear rack sample

* Update output after more samples fixes

* Update gear rack output

* Add end label to docs snippet

* Fix to not add endAbsolute for an arc with radius or angle arguments

* Update docs outputs

* Fix formatting

* Fix executor tests

* Fix formatting

* Fix bench input files

* Fix spelling

* Improve error messages

---------

Co-authored-by: Adam Chalmers <adam.chalmers@zoo.dev>
2025-04-11 14:17:20 -04:00
66f95d25f6 Don't let too-tall line height goof up action buttons (#6253) 2025-04-11 14:06:36 -04:00
72ab72cea1 Bump vite from 6.2.5 to 6.2.6 in /packages/codemirror-lang-kcl in the security group (#6280)
Bump vite in /packages/codemirror-lang-kcl in the security group

Bumps the security group in /packages/codemirror-lang-kcl with 1 update: [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).


Updates `vite` from 6.2.5 to 6.2.6
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v6.2.6/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.2.6/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 6.2.6
  dependency-type: indirect
  dependency-group: security
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-11 16:28:43 +00:00
4e1e9f9c6e Bump vite from 5.4.17 to 5.4.18 in the security group (#6281)
Bumps the security group with 1 update: [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).


Updates `vite` from 5.4.17 to 5.4.18
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.4.18/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.4.18/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 5.4.18
  dependency-type: direct:development
  dependency-group: security
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-11 16:24:24 +00:00
8b14d32879 Bump the security group in /rust with 2 updates (#6212)
Bumps the security group in /rust with 2 updates: [pyo3](https://github.com/pyo3/pyo3) and [tokio](https://github.com/tokio-rs/tokio).


Updates `pyo3` from 0.24.0 to 0.24.1
- [Release notes](https://github.com/pyo3/pyo3/releases)
- [Changelog](https://github.com/PyO3/pyo3/blob/v0.24.1/CHANGELOG.md)
- [Commits](https://github.com/pyo3/pyo3/compare/v0.24.0...v0.24.1)

Updates `tokio` from 1.44.1 to 1.44.2
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.44.1...tokio-1.44.2)

---
updated-dependencies:
- dependency-name: pyo3
  dependency-version: 0.24.1
  dependency-type: direct:production
  dependency-group: security
- dependency-name: tokio
  dependency-version: 1.44.2
  dependency-type: direct:production
  dependency-group: security
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-11 16:12:53 +00:00
7c5170dc16 Use the correct variable for the main branch (#6278) 2025-04-11 15:04:28 +00:00
a07dbc3aac chore: stabilizing named views e2e tests (#6246)
* fix: moving toast application state:

* fix: forgot the await on the expect calls, ts didn't yell at me?

* fix: cleaning up the camera move e2e code

* fix: lint,tsc,fmt

* Unblock on macOS for now

---------

Co-authored-by: Frank Noirot <frank@zoo.dev>
Co-authored-by: Jace Browning <jacebrowning@gmail.com>
2025-04-11 10:32:44 -04:00
7d3294ff78 Quick fix for windows codesign failures with digicert (#6275)
* pierremtb/issue6256-another-test

* yarn to npm install

* Revert "yarn to npm install"

This reverts commit 4a3daf950f.

* yarn to npm install

* Force IS_RELEASE=true

* Clean up for review
2025-04-11 14:31:44 +00:00
121c393466 UI fix for comment settings collision (#6249)
* UI fix for comment settings collision

* fmt

* Fix tsc by using Promise.reject

---------

Co-authored-by: Frank Noirot <frankjohnson1993@gmail.com>
2025-04-11 14:16:45 +00:00
c6ec54c138 Send test results to an API for analysis (#6261)
* Send test results to an API

* Include platform

* Include target

* Return earlier

* Include actual commit SHA

* Include PR number

* Rename variables for clarity
2025-04-11 10:04:13 -04:00
1f6b90d383 #6182 Improve calculate_circle_center (#6192)
* add tests for calculate_circle_center - the first one succeeds with the current impl

* fix calculate_circle_center

* comment cleanup

* clippy

* comment format

* update circle_three_point sim test snapshot for slight floating point changes introduced by calculate_circle_center refactor

* use TAU instead of 2 * PI

* clippy
2025-04-11 12:43:18 +02:00
c45c2e27ba make sure the nix flake never breaks (#6273)
make sure teh nix flake never breaks

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-04-11 06:27:42 +00:00
5832890dbb Add CSG operations to the artifact graph (#6104)
* Add CSG operations to the artifact graph

* Add tool IDs for subtract

* Fix names to match modeling-cmds now that it's done

* Update output since adding CSG ops

* Update formatting

* Add extra solid ids to the graph

* Fix to not add duplicates to the graph
2025-04-11 05:16:29 +00:00
6f2e6d14b6 refactor: Change to use topLevelModule helper function (#6264) 2025-04-11 05:13:10 +00:00
0d899694b2 Remove experimental warning on whole-module imports (#6240) 2025-04-11 04:22:51 +00:00
7bd5e7365d Insert a newline between block comments and attributes (#6250)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
2025-04-11 03:19:52 +00:00
a63e51e2ad Remove import function from std (#6241)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-04-11 03:11:59 +00:00
1b8eee86a1 Fix vscode lsp bugs (#6271)
* start of shit

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

* untitled test

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

* updates

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-04-11 12:53:05 +10:00
6d50278d34 bump kcl friends (#6272)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-04-10 19:01:30 -07:00
35844842de Bump modeling api & pull thru csg endpoints (#6245)
* csg-upts

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

* base

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

* do the id shit

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

* tried to run

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

* csg-upts

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

* use bens samples

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

* use bens samples

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

* gen std

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

* gen std

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

* updates

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

* updates

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

* fix;

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-04-10 18:30:57 -07:00
843e772094 Franknoirot/adhoc/improve e2e (#6265)
* fix: increasing timeout to reduce failures..?

* fix: removing debugging code

* Remove unnecessary pixel color check

* Only close the command palette on disconnection events

This code closes the palette on *any* network event, including it
getting established. This made tests like "Create a few projects using
the default project name" unreliable.

Also adds a block comment about how we should do something more
sophisticated than bail out in the future.

* Show a toast to explain why the user just lost their command flow

* Remove unnecessary click in test. `fill` focuses already

* Oop don't spam this disconnection toast if the palette isn't open

Honoring exhausting hook dependency checks

* Skip circle snapshot test, it's being weird

* Disable the export button if engine connection is unavailable

* Refactor useDemoCode to hopefully be more reliable

* allow weak connections to receive demo code

* revert Refactor useDemoCode to hopefully be more reliable
Commit:
2625c14783 [2625c1478]

---------

Co-authored-by: Kevin Nadro <kevin@zoo.dev>
Co-authored-by: Jace Browning <jacebrowning@gmail.com>
Co-authored-by: Andrew Varga <grizzly33@gmail.com>
2025-04-11 00:08:39 +00:00
9d0518dfeb Fix URL encoded names after rename to Zoo Design Studio (#6269) 2025-04-10 17:20:37 -04:00
d33d399c31 Repetitive structs removed for import file extensions (#6211)
* get rid of repetitive structs

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

fmt

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

get rid of more

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

add more

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

updates

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

fix

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

updates

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

await the shit

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

updates

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

put it at the root

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

* updates

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

* fix;es

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

* fixes

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

* fix

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

* kcl-language-server flake

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

* updates

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-04-10 13:57:12 -07:00
262 changed files with 21096 additions and 19319 deletions

View File

@ -207,6 +207,13 @@ jobs:
smctl.exe keypair ls
C:\Windows\System32\certutil.exe -csp "DigiCert Signing Manager KSP" -key -user
smksp_cert_sync.exe
smctl windows certsync
# This last line `smctl windows certsync` was added after windows codesign failures started happening
# with nightly-v25.4.10. It looks like `smksp_cert_sync.exe` used to do the sync to the local cert store,
# but stopped doing it overnight. This extra call that I randomly got from this azure-related doc page
# https://docs.digicert.com/en/digicert-keylocker/code-signing/sign-with-third-party-signing-tools/windows-applications/sign-azure-apps-with-signtool-using-ksp-library.html#sync-certificates--windows-only--618365
# seems to be doing that extra sync that we need for scripts/sign-win.js to work.
# TODO: we still need to make sign-win.js errors fail the workflow, see issue #6276
shell: cmd
- name: Build the app (debug)
@ -378,7 +385,7 @@ jobs:
NOTES: ${{ needs.prepare-files.outputs.notes }}
PUB_DATE: ${{ github.event.repository.updated_at }}
WEBSITE_DIR: ${{ env.IS_NIGHTLY == 'true' && 'dl.zoo.dev/releases/modeling-app/nightly' || 'dl.zoo.dev/releases/modeling-app' }}
URL_CODED_NAME: ${{ env.IS_NIGHTLY == 'true' && 'Zoo%20Modeling%20App%20%28Nightly%29' || 'Zoo%20Modeling%20App' }}
URL_CODED_NAME: ${{ env.IS_NIGHTLY == 'true' && 'Zoo%20Design%20Studio%20%28Nightly%29' || 'Zoo%20Design%20Studio' }}
run: |
RELEASE_DIR=https://${WEBSITE_DIR}
jq --null-input \

View File

@ -231,6 +231,11 @@ jobs:
env:
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
snapshottoken: ${{ secrets.KITTYCAD_API_TOKEN }}
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 }}
TARGET: web
- uses: actions/upload-artifact@v4
if: ${{ needs.conditions.outputs.should-run == 'true' && !cancelled() && (success() || failure()) }}
@ -365,6 +370,11 @@ jobs:
env:
FAIL_ON_CONSOLE_ERRORS: true
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 }}
TARGET: desktop
- uses: actions/upload-artifact@v4
if: ${{ needs.conditions.outputs.should-run == 'true' && always() }}

55
.github/workflows/nix.yml vendored Normal file
View File

@ -0,0 +1,55 @@
name: Test Nix Flake
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
nix-flake-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: cachix/install-nix-action@v31
with:
nix_path: nixpkgs=channel:nixos-unstable
- name: nix flake check for all platforms
run: |
nix flake check --all-systems
nix-build-linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: cachix/install-nix-action@v31
with:
nix_path: nixpkgs=channel:nixos-unstable
- name: nix build . for x86_64-linux
run: nix build .
nix-build-macos:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: cachix/install-nix-action@v31
with:
nix_path: nixpkgs=channel:nixos-unstable
- name: nix build . for x86_64-darwin
run: nix build .

1
.gitignore vendored
View File

@ -26,6 +26,7 @@ yarn-error.log*
.idea
.vscode
.helix
result
# rust
rust/target

View File

@ -17,7 +17,7 @@ WASM_PACK ?= $(USERPROFILE)/.cargo/bin/wasm-pack.exe
else
CARGO ?= ~/.cargo/bin/cargo
WASM_PACK ?= ~/.cargo/bin/wasm-pack
endif
endif
.PHONY: install
install: node_modules/.yarn-integrity $(CARGO) $(WASM_PACK) ## Install dependencies

View File

@ -188,9 +188,9 @@ example = extrude(exampleSketch, length = 1)
sweepPath = startSketchOn(XZ)
|> startProfileAt([0.05, 0.05], %)
|> line(end = [0, 7])
|> tangentialArc({ offset = 90, radius = 5 }, %)
|> tangentialArc(angle = 90, radius = 5)
|> line(end = [-3, 0])
|> tangentialArc({ offset = -90, radius = 5 }, %)
|> tangentialArc(angle = -90, radius = 5)
|> line(end = [0, 7])
pipeHole = startSketchOn(XY)

View File

@ -65,7 +65,7 @@ case = startSketchOn(-XZ)
|> startProfileAt([-size, -size], %)
|> line(end = [2 * size, 0])
|> line(end = [0, 2 * size])
|> tangentialArcTo([-size, size], %)
|> tangentialArc(endAbsolute = [-size, size])
|> close()
|> extrude(length = 65)

File diff suppressed because one or more lines are too long

View File

@ -77,6 +77,7 @@ layout: manual
* [`helix`](kcl/std-helix)
* [`hole`](kcl/hole)
* [`hollow`](kcl/hollow)
* [`intersect`](kcl/intersect)
* [`lastSegX`](kcl/lastSegX)
* [`lastSegY`](kcl/lastSegY)
* [`legAngX`](kcl/legAngX)
@ -123,14 +124,14 @@ layout: manual
* [`sqrt`](kcl/sqrt)
* [`startProfileAt`](kcl/startProfileAt)
* [`startSketchOn`](kcl/startSketchOn)
* [`subtract`](kcl/subtract)
* [`sweep`](kcl/sweep)
* [`tangentToEnd`](kcl/tangentToEnd)
* [`tangentialArc`](kcl/tangentialArc)
* [`tangentialArcTo`](kcl/tangentialArcTo)
* [`tangentialArcToRelative`](kcl/tangentialArcToRelative)
* [`toDegrees`](kcl/toDegrees)
* [`toRadians`](kcl/toRadians)
* [`translate`](kcl/translate)
* [`union`](kcl/union)
* [`xLine`](kcl/xLine)
* [`yLine`](kcl/yLine)
* **std::math**

File diff suppressed because one or more lines are too long

View File

@ -57,7 +57,7 @@ case = startSketchOn(XY)
|> startProfileAt([-size, -size], %)
|> line(end = [2 * size, 0])
|> line(end = [0, 2 * size])
|> tangentialArcTo([-size, size], %)
|> tangentialArc(endAbsolute = [-size, size])
|> close(%)
|> extrude(length = 65)
@ -88,7 +88,7 @@ case = startSketchOn(XY)
|> startProfileAt([-size, -size], %)
|> line(end = [2 * size, 0])
|> line(end = [0, 2 * size])
|> tangentialArcTo([-size, size], %)
|> tangentialArc(endAbsolute = [-size, size])
|> close(%)
|> extrude(length = 65)

View File

@ -65,9 +65,9 @@ rotate(
sweepPath = startSketchOn(XZ)
|> startProfileAt([0.05, 0.05], %)
|> line(end = [0, 7])
|> tangentialArc({ offset = 90, radius = 5 }, %)
|> tangentialArc(angle = 90, radius = 5)
|> line(end = [-3, 0])
|> tangentialArc({ offset = -90, radius = 5 }, %)
|> tangentialArc(angle = -90, radius = 5)
|> line(end = [0, 7])
// Create a hole for the pipe.
@ -90,9 +90,9 @@ sweepSketch = startSketchOn(XY)
sweepPath = startSketchOn(XZ)
|> startProfileAt([0.05, 0.05], %)
|> line(end = [0, 7])
|> tangentialArc({ offset = 90, radius = 5 }, %)
|> tangentialArc(angle = 90, radius = 5)
|> line(end = [-3, 0])
|> tangentialArc({ offset = -90, radius = 5 }, %)
|> tangentialArc(angle = -90, radius = 5)
|> line(end = [0, 7])
// Create a hole for the pipe.
@ -115,9 +115,9 @@ sweepSketch = startSketchOn(XY)
sweepPath = startSketchOn(XZ)
|> startProfileAt([0.05, 0.05], %)
|> line(end = [0, 7])
|> tangentialArc({ offset = 90, radius = 5 }, %)
|> tangentialArc(angle = 90, radius = 5)
|> line(end = [-3, 0])
|> tangentialArc({ offset = -90, radius = 5 }, %)
|> tangentialArc(angle = -90, radius = 5)
|> line(end = [0, 7])
// Create a hole for the pipe.
@ -162,7 +162,7 @@ circleSketch = circle(sketch001, center = [200, -30.29], radius = 32.63)
sketch002 = startSketchOn(YZ)
sweepPath = startProfileAt([0, 0], sketch002)
|> yLine(length = 231.81)
|> tangentialArc({ radius = 80, offset = -90 }, %)
|> tangentialArc(radius = 80, angle = -90)
|> xLine(length = 384.93)
parts = sweep([rectangleSketch, circleSketch], path = sweepPath)

View File

@ -49,9 +49,9 @@ scale(
sweepPath = startSketchOn(XZ)
|> startProfileAt([0.05, 0.05], %)
|> line(end = [0, 7])
|> tangentialArc({ offset = 90, radius = 5 }, %)
|> tangentialArc(angle = 90, radius = 5)
|> line(end = [-3, 0])
|> tangentialArc({ offset = -90, radius = 5 }, %)
|> tangentialArc(angle = -90, radius = 5)
|> line(end = [0, 7])
// Create a hole for the pipe.
@ -96,7 +96,7 @@ circleSketch = circle(sketch001, center = [200, -30.29], radius = 32.63)
sketch002 = startSketchOn(YZ)
sweepPath = startProfileAt([0, 0], sketch002)
|> yLine(length = 231.81)
|> tangentialArc({ radius = 80, offset = -90 }, %)
|> tangentialArc(radius = 80, angle = -90)
|> xLine(length = 384.93)
parts = sweep([rectangleSketch, circleSketch], path = sweepPath)

View File

@ -30,7 +30,7 @@ segLen(tag: TagIdentifier): number
exampleSketch = startSketchOn(XZ)
|> startProfileAt([0, 0], %)
|> angledLine(angle = 60, length = 10, tag = $thing)
|> tangentialArc({ offset = -120, radius = 5 }, %)
|> tangentialArc(angle = -120, radius = 5)
|> angledLine(angle = -60, length = segLen(thing))
|> close()

View File

@ -103,7 +103,7 @@ case = startSketchOn(-XZ)
|> startProfileAt([-size, -size], %)
|> line(end = [2 * size, 0])
|> line(end = [0, 2 * size])
|> tangentialArcTo([-size, size], %)
|> tangentialArc(endAbsolute = [-size, size])
|> close()
|> extrude(length = 65)
@ -128,7 +128,7 @@ case = startSketchOn(XY)
|> startProfileAt([-size, -size], %)
|> line(end = [2 * size, 0])
|> line(end = [0, 2 * size])
|> tangentialArcTo([-size, size], %)
|> tangentialArc(endAbsolute = [-size, size])
|> close()
|> extrude(length = 65)
@ -156,7 +156,7 @@ case = startSketchOn(XY)
|> startProfileAt([-size, -size], %)
|> line(end = [2 * size, 0])
|> line(end = [0, 2 * size])
|> tangentialArcTo([-size, size], %)
|> tangentialArc(endAbsolute = [-size, size])
|> close()
|> extrude(length = 65)

View File

@ -103,9 +103,9 @@ example = extrude(sketch001, length = 10)
sketch0011 = startSketchOn(XY)
|> startProfileAt([6.77, 0], %)
|> yLine(length = 1.27)
|> tangentialArcTo([5.96, 2.37], %)
|> tangentialArcTo([-6.2, 2.44], %)
|> tangentialArcTo([-6.6, 1.82], %)
|> tangentialArc(endAbsolute = [5.96, 2.37])
|> tangentialArc(endAbsolute = [-6.2, 2.44])
|> tangentialArc(endAbsolute = [-6.6, 1.82])
|> yLine(length = -1.82)
|> mirror2d( axis = X )
|> extrude(length = 10)

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -47,9 +47,9 @@ sweep(
sweepPath = startSketchOn(XZ)
|> startProfileAt([0.05, 0.05], %)
|> line(end = [0, 7])
|> tangentialArc({ offset = 90, radius = 5 }, %)
|> tangentialArc(angle = 90, radius = 5)
|> line(end = [-3, 0])
|> tangentialArc({ offset = -90, radius = 5 }, %)
|> tangentialArc(angle = -90, radius = 5)
|> line(end = [0, 7])
// Create a hole for the pipe.
@ -102,7 +102,7 @@ circleSketch = circle(sketch001, center = [200, -30.29], radius = 32.63)
sketch002 = startSketchOn(YZ)
sweepPath = startProfileAt([0, 0], sketch002)
|> yLine(length = 231.81)
|> tangentialArc({ radius = 80, offset = -90 }, %)
|> tangentialArc(radius = 80, angle = -90)
|> xLine(length = 384.93)
sweep([rectangleSketch, circleSketch], path = sweepPath)
@ -120,7 +120,7 @@ circleSketch = circle(sketch001, center = [200, -30.29], radius = 32.63)
sketch002 = startSketchOn(YZ)
sweepPath = startProfileAt([0, 0], sketch002)
|> yLine(length = 231.81)
|> tangentialArc({ radius = 80, offset = -90 }, %)
|> tangentialArc(radius = 80, angle = -90)
|> xLine(length = 384.93)
sweep(circleSketch, path = sweepPath, sectional = true)

View File

@ -31,9 +31,9 @@ tangentToEnd(tag: TagIdentifier): number
pillSketch = startSketchOn(XZ)
|> startProfileAt([0, 0], %)
|> line(end = [20, 0])
|> tangentialArcToRelative([0, 10], %, $arc1)
|> tangentialArc(end = [0, 10], tag = $arc1)
|> angledLine(angle = tangentToEnd(arc1), length = 20)
|> tangentialArcToRelative([0, -10], %)
|> tangentialArc(end = [0, -10])
|> close()
pillExtrude = extrude(pillSketch, length = 10)
@ -46,9 +46,9 @@ pillExtrude = extrude(pillSketch, length = 10)
pillSketch = startSketchOn(XZ)
|> startProfileAt([0, 0], %)
|> line(end = [0, 20])
|> tangentialArcTo([10, 20], %, $arc1)
|> tangentialArc(endAbsolute = [10, 20], tag = $arc1)
|> angledLine(angle = tangentToEnd(arc1), length = 20)
|> tangentialArcToRelative([-10, 0], %)
|> tangentialArc(end = [-10, 0])
|> close()
pillExtrude = extrude(pillSketch, length = 10)

File diff suppressed because one or more lines are too long

View File

@ -45,9 +45,9 @@ translate(
sweepPath = startSketchOn(XZ)
|> startProfileAt([0.05, 0.05], %)
|> line(end = [0, 7])
|> tangentialArc({ offset = 90, radius = 5 }, %)
|> tangentialArc(angle = 90, radius = 5)
|> line(end = [-3, 0])
|> tangentialArc({ offset = -90, radius = 5 }, %)
|> tangentialArc(angle = -90, radius = 5)
|> line(end = [0, 7])
// Create a hole for the pipe.
@ -97,7 +97,7 @@ circleSketch = circle(sketch001, center = [200, -30.29], radius = 32.63)
sketch002 = startSketchOn(YZ)
sweepPath = startProfileAt([0, 0], sketch002)
|> yLine(length = 231.81)
|> tangentialArc({ radius = 80, offset = -90 }, %)
|> tangentialArc(radius = 80, angle = -90)
|> xLine(length = 384.93)
parts = sweep([rectangleSketch, circleSketch], path = sweepPath)

View File

@ -252,7 +252,7 @@ Data for an imported geometry.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: [`ImportedGeometry`](/docs/kcl/types/ImportedGeometry)| | No |
| `type` |enum: `ImportedGeometry`| | No |
| `id` |[`string`](/docs/kcl/types/string)| The ID of the imported geometry. | No |
| `value` |`[` [`string`](/docs/kcl/types/string) `]`| The original file paths. | No |

File diff suppressed because one or more lines are too long

View File

@ -920,7 +920,7 @@ sketch001 = startSketchOn(XZ)
`sketch001 = startSketchOn(XZ)
|> startProfileAt([4.61, -14.01], %)
|> line(end = [12.73, -0.09])
|> tangentialArcTo([24.95, -5.38], %)
|> tangentialArc(endAbsolute = [24.95, -5.38])
|> close()`
)
})
@ -969,7 +969,7 @@ sketch001 = startSketchOn(XZ)
// expect the code to have changed
await expect(page.locator('.cm-content')).toHaveText(
`sketch001 = startSketchOn(XZ) |> startProfileAt([4.61, -14.01], %) |> line(end = [12.73, -0.09]) |> tangentialArcTo([24.95, -5.38], %) |> close()extrude001 = extrude(sketch001, length = 5)`
`sketch001 = startSketchOn(XZ) |> startProfileAt([4.61, -14.01], %) |> line(end = [12.73, -0.09]) |> tangentialArc(endAbsolute = [24.95, -5.38]) |> close()extrude001 = extrude(sketch001, length = 5)`
)
// Now hit undo
@ -982,7 +982,7 @@ sketch001 = startSketchOn(XZ)
.toHaveText(`sketch001 = startSketchOn(XZ)
|> startProfileAt([4.61, -14.01], %)
|> line(end = [12.73, -0.09])
|> tangentialArcTo([24.95, -5.38], %)
|> tangentialArc(endAbsolute = [24.95, -5.38])
|> close()`)
})
@ -998,7 +998,7 @@ sketch001 = startSketchOn(XZ)
sketch001 = startSketchOn(XZ)
|> startProfileAt([4.61, -10.01], %)
|> line(end = [12.73, -0.09])
|> tangentialArcTo([24.95, -0.38], %)
|> tangentialArc(endAbsolute = [24.95, -0.38])
|> close()
|> extrude(length = 5)`
)
@ -1072,7 +1072,7 @@ sketch001 = startSketchOn(XZ)
// we wait so it saves the code
await page.waitForTimeout(800)
// drag tangentialArcTo handle
// drag tangentialArc handle
const tangentEnd = await u.getBoundingBox('[data-overlay-index="1"]')
await page.dragAndDrop('#stream', '#stream', {
sourcePosition: { x: tangentEnd.x + 10, y: tangentEnd.y - 5 },
@ -1089,7 +1089,7 @@ sketch001 = startSketchOn(XZ)
`sketch001 = startSketchOn(XZ)
|> startProfileAt([2.71, -2.71], %)
|> line(end = [15.4, -2.78])
|> tangentialArcTo([27.6, -3.05], %)
|> tangentialArc(endAbsolute = [27.6, -3.05])
|> close()
|> extrude(length = 5)`,
{ shouldNormalise: true }
@ -1104,7 +1104,7 @@ sketch001 = startSketchOn(XZ)
`sketch001 = startSketchOn(XZ)
|> startProfileAt([2.71, -2.71], %)
|> line(end = [15.4, -2.78])
|> tangentialArcTo([24.95, -0.38], %)
|> tangentialArc(endAbsolute = [24.95, -0.38])
|> close()
|> extrude(length = 5)`,
{ shouldNormalise: true }
@ -1119,7 +1119,7 @@ sketch001 = startSketchOn(XZ)
`sketch001 = startSketchOn(XZ)
|> startProfileAt([2.71, -2.71], %)
|> line(end = [12.73, -0.09])
|> tangentialArcTo([24.95, -0.38], %)
|> tangentialArc(endAbsolute = [24.95, -0.38])
|> close()
|> extrude(length = 5)`,
{ shouldNormalise: true }
@ -1135,7 +1135,7 @@ sketch001 = startSketchOn(XZ)
`sketch001 = startSketchOn(XZ)
|> startProfileAt([4.61, -10.01], %)
|> line(end = [12.73, -0.09])
|> tangentialArcTo([24.95, -0.38], %)
|> tangentialArc(endAbsolute = [24.95, -0.38])
|> close()
|> extrude(length = 5)`,
{ shouldNormalise: true }
@ -1144,7 +1144,7 @@ sketch001 = startSketchOn(XZ)
)
test(
`Can use the import stdlib function on a local OBJ file`,
`Can import a local OBJ file`,
{ tag: '@electron' },
async ({ page, context }, testInfo) => {
test.fixme(orRunWhenFullSuiteEnabled())
@ -1194,7 +1194,7 @@ sketch001 = startSketchOn(XZ)
.toBeLessThan(15)
})
await test.step(`Write the import function line`, async () => {
await u.codeLocator.fill(`import('cube.obj')`)
await u.codeLocator.fill(`import 'cube.obj'\ncube`)
await page.waitForTimeout(800)
})
await test.step(`Reset the camera before checking`, async () => {

View File

@ -99,7 +99,6 @@ export class HomePageFixture {
createAndGoToProject = async (projectTitle = 'untitled') => {
await this.projectsLoaded()
await this.projectButtonNew.click()
await this.projectTextName.click()
await this.projectTextName.fill(projectTitle)
await this.projectButtonContinue.click()
}

View File

@ -233,7 +233,7 @@ export class SceneFixture {
settled = async (cmdBar: CmdBarFixture) => {
const u = await getUtils(this.page)
await expect(this.startEditSketchBtn).not.toBeDisabled()
await expect(this.startEditSketchBtn).not.toBeDisabled({ timeout: 15_000 })
await expect(this.startEditSketchBtn).toBeVisible()
await cmdBar.openCmdBar()

View File

@ -0,0 +1,63 @@
import type { Reporter, TestCase, TestResult } from '@playwright/test/reporter'
class MyAPIReporter implements Reporter {
onTestEnd(test: TestCase, result: TestResult): void {
if (!process.env.TAB_API_URL || !process.env.TAB_API_KEY) {
return
}
const payload = {
// Required information
project: 'https://github.com/KittyCAD/modeling-app',
branch: process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME || '',
commit: process.env.CI_COMMIT_SHA || process.env.GITHUB_SHA || '',
test: test.titlePath().slice(2).join(' '),
status: result.status,
// Optional information
duration: result.duration / 1000,
message: result.error?.stack,
target: process.env.TARGET || null,
platform: process.env.RUNNER_OS || process.platform,
// Extra test and result data
annotations: test.annotations.map((a) => a.type),
retries: result.retry,
// Extra environment variables
CI_COMMIT_SHA: process.env.CI_COMMIT_SHA || null,
CI_PR_NUMBER: process.env.CI_PR_NUMBER || null,
GITHUB_BASE_REF: process.env.GITHUB_BASE_REF || null,
GITHUB_EVENT_NAME: process.env.GITHUB_EVENT_NAME || null,
GITHUB_HEAD_REF: process.env.GITHUB_HEAD_REF || null,
GITHUB_REF_NAME: process.env.GITHUB_REF_NAME || null,
GITHUB_REF: process.env.GITHUB_REF || null,
GITHUB_SHA: process.env.GITHUB_SHA || null,
GITHUB_WORKFLOW: process.env.GITHUB_WORKFLOW || null,
RUNNER_ARCH: process.env.RUNNER_ARCH || null,
}
void (async () => {
try {
const response = await fetch(`${process.env.TAB_API_URL}/api/results`, {
method: 'POST',
headers: new Headers({
'Content-Type': 'application/json',
'X-API-Key': process.env.TAB_API_KEY || '',
}),
body: JSON.stringify(payload),
})
if (!response.ok && !process.env.CI) {
console.error(
'TAB API - Failed to send test result:',
await response.text()
)
}
} catch {
if (!process.env.CI) {
console.error('TAB API - Unable to send test result')
}
}
})()
}
}
export default MyAPIReporter

View File

@ -6,7 +6,9 @@ import type { NamedView } from '@rust/kcl-lib/bindings/NamedView'
import {
createProject,
perProjectsettingsToToml,
orRunWhenFullSuiteEnabled,
perProjectSettingsToToml,
runningOnMac,
tomlToPerProjectSettings,
} from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
@ -57,11 +59,13 @@ function tomlStringOverWriteNamedViewUuids(toml: string): string {
settings.settings.app.named_views = remappedNamedViews
}
}
return perProjectsettingsToToml(settings)
return perProjectSettingsToToml(settings)
}
test.describe('Named view tests', () => {
test.skip() // TODO: Jace is working on these
if (runningOnMac()) {
test.fixme(orRunWhenFullSuiteEnabled())
}
test('Verify project.toml is not created', async ({ page }, testInfo) => {
// Create project and load it
const projectName = 'named-views'
@ -105,6 +109,9 @@ test.describe('Named view tests', () => {
PROJECT_SETTINGS_FILE_NAME
)
const toastMessage = page.getByText('Named view uuid1 created.')
await expect(toastMessage).toBeInViewport()
// Expect project.toml to be generated on disk since a named view was created
await expect(async () => {
let exists = await fileExists(tempProjectSettingsFilePath)
@ -130,7 +137,6 @@ test.describe('Named view tests', () => {
}, testInfo) => {
const projectName = 'named-views'
const myNamedView1 = 'uuid1'
const myNamedView2 = 'uuid2'
// Create project and go into the project
await createProject({ name: projectName, page })
@ -142,6 +148,9 @@ test.describe('Named view tests', () => {
await cmdBar.argumentInput.fill(myNamedView1)
await cmdBar.progressCmdBar(false)
let toastMessage = page.getByText('Named view uuid1 created.')
await expect(toastMessage).toBeInViewport()
// Generate file paths for project.toml
const projectDirName = testInfo.outputPath('electron-test-projects-dir')
const tempProjectSettingsFilePath = join(
@ -170,17 +179,20 @@ test.describe('Named view tests', () => {
// Delete a named view
await cmdBar.openCmdBar()
await cmdBar.chooseCommand('delete named view')
cmdBar.selectOption({ name: myNamedView2 })
cmdBar.selectOption({ name: myNamedView1 })
await cmdBar.progressCmdBar(false)
toastMessage = page.getByText('Named view uuid1 removed.')
await expect(toastMessage).toBeInViewport()
await expect(async () => {
// Read project.toml into memory again since we deleted a named view
let tomlString = await fsp.readFile(tempProjectSettingsFilePath, 'utf-8')
// Rewrite the uuids in the named views to match snapshot otherwise they will be randomly generated from rust and break
tomlString = tomlStringOverWriteNamedViewUuids(tomlString)
// // Write the entire tomlString to a snapshot.
// // There are many key/value pairs to check this is a safer match.
// Write the entire tomlString to a snapshot.
// There are many key/value pairs to check this is a safer match.
expect(tomlString).toMatchSnapshot('verify-named-view-gets-deleted')
}).toPass()
})
@ -202,6 +214,9 @@ test.describe('Named view tests', () => {
await cmdBar.argumentInput.fill(myNamedView)
await cmdBar.progressCmdBar(false)
let toastMessage = page.getByText('Named view uuid1 created.')
await expect(toastMessage).toBeInViewport()
// Generate file paths for project.toml
const projectDirName = testInfo.outputPath('electron-test-projects-dir')
const tempProjectSettingsFilePath = join(
@ -258,26 +273,19 @@ test.describe('Named view tests', () => {
await cmdBar.argumentInput.fill(myNamedView1)
await cmdBar.progressCmdBar(false)
await page.waitForTimeout(1000)
let toastMessage = page.getByText('Named view uuid1 created.')
await expect(toastMessage).toBeInViewport()
const orbitMouseStart = { x: 800, y: 130 }
const orbitMouseEnd = { x: 0, y: 130 }
await page.mouse.move(orbitMouseStart.x, orbitMouseStart.y)
await page.mouse.down({ button: 'middle' })
await page.mouse.move(orbitMouseEnd.x, orbitMouseEnd.y, {
steps: 3,
})
await page.mouse.up({ button: 'middle' })
await page.waitForTimeout(1000)
await scene.moveCameraTo({ x: 608, y: 0, z: 0 }, { x: 0, y: 0, z: 0 })
await page.waitForTimeout(2500)
await cmdBar.openCmdBar()
await cmdBar.chooseCommand('create named view')
await cmdBar.argumentInput.fill(myNamedView2)
await cmdBar.progressCmdBar(false)
// Wait a moment for the project.toml to get written to disk with the new view point
await page.waitForTimeout(1000)
toastMessage = page.getByText('Named view uuid2 created.')
await expect(toastMessage).toBeInViewport()
// Generate paths for the project.toml
const tempProjectSettingsFilePath = join(

View File

@ -1,16 +1,5 @@
[settings]
app = { }
modeling = { }
text_editor = { }
command_bar = { }
[settings.app.named_views.0656fb1a-9640-473e-b334-591dc70c0138]
name = "uuid1"
eye_offset = 1_378.0059
fov_y = 45
is_ortho = false
ortho_scale_enabled = true
ortho_scale_factor = 1.6
pivot_position = [ 0, 0, 0 ]
pivot_rotation = [ 0.5380994, 0.0, 0.0, 0.8428814 ]
world_coord_system = "right_handed_up_z"
version = 1

View File

@ -17,12 +17,12 @@ version = 1
[settings.app.named_views.c810cf04-c6cc-4a4a-8b11-17bf445dcab7]
name = "uuid2"
eye_offset = 1_378.0059
eye_offset = 608
fov_y = 45
is_ortho = false
ortho_scale_enabled = true
ortho_scale_factor = 1.6
pivot_position = [ 1_826.5239, 0.0, 0.0 ]
pivot_rotation = [ 0.5380994, 0.0, 0.0, 0.8428814 ]
pivot_position = [ 0, 0, 0 ]
pivot_rotation = [ 0.5, 0.5, 0.5, 0.5 ]
world_coord_system = "right_handed_up_z"
version = 1

View File

@ -550,7 +550,7 @@ openSketch = startSketchOn(XY)
|> startProfileAt([-5, 0], %)
|> line(endAbsolute = [0, 5])
|> xLine(length = 5)
|> tangentialArcTo([10, 0], %)
|> tangentialArc(endAbsolute = [10, 0])
`
const viewPortSize = { width: 1000, height: 500 }
await page.setBodyDimensions(viewPortSize)
@ -634,8 +634,8 @@ openSketch = startSketchOn(XY)
// Wait for enter sketch mode to complete
await page.waitForTimeout(500)
await editor.expectState({
activeLines: [`|>tangentialArcTo([10,0],%)`],
highlightedCode: 'tangentialArcTo([10,0],%)',
activeLines: [`|>tangentialArc(endAbsolute=[10,0])`],
highlightedCode: 'tangentialArc(endAbsolute=[10,0])',
diagnostics: [],
})
})
@ -1624,7 +1624,7 @@ profile001 = circle(sketch001, center = [0, 0], radius = 500)
sketch002 = startSketchOn(XZ)
|> startProfileAt([0, 0], %)
|> xLine(length = -500)
|> tangentialArcTo([-2000, 500], %)`,
|> tangentialArc(endAbsolute = [-2000, 500])`,
},
{
targetType: 'rectangle',
@ -1640,7 +1640,7 @@ profile001 = startProfileAt([-400, -400], sketch001)
sketch002 = startSketchOn(XZ)
|> startProfileAt([0, 0], %)
|> xLine(length = -500)
|> tangentialArcTo([-2000, 500], %)`,
|> tangentialArc(endAbsolute = [-2000, 500])`,
},
]
sweepCases.map(({ initialCode, targetType, testPoint }) => {

View File

@ -68,12 +68,10 @@ test.describe('edit with AI example snapshots', () => {
body1CapCoords.x,
body1CapCoords.y
)
const yellow: [number, number, number] = [179, 179, 131]
const submittingToast = page.getByText('Submitting to Text-to-CAD API...')
await test.step('wait for scene to load select body and check selection came through', async () => {
await clickBody1Cap()
await scene.expectPixelColor(yellow, body1CapCoords, 20)
await editor.expectState({
highlightedCode: '',
activeLines: ['|>startProfileAt([-73.64,-42.89],%)'],

View File

@ -63,7 +63,7 @@ test.describe('Sketch tests', { tag: ['@skipWin'] }, () => {
part002 = startSketchOn(-XZ)
${startProfileAt3}
|> xLine(length = width / 4)
|> tangentialArcTo([width / 2, 0], %)
|> tangentialArc(endAbsolute = [width / 2, 0])
|> xLine(length = -width / 4 + wireRadius)
|> yLine(length = wireOffset)
|> arc({
@ -119,7 +119,7 @@ test.describe('Sketch tests', { tag: ['@skipWin'] }, () => {
sketch001 = startSketchOn(XZ)
|> startProfileAt([2.61, -4.01], %)
|> xLine(length = 8.73)
|> tangentialArcTo([8.33, -1.31], %)`
|> tangentialArc(endAbsolute = [8.33, -1.31])`
)
})
@ -130,7 +130,7 @@ sketch001 = startSketchOn(XZ)
await expect(async () => {
await page.mouse.click(700, 200)
await page.getByText('tangentialArcTo([8.33, -1.31], %)').click()
await page.getByText('tangentialArc(endAbsolute = [8.33, -1.31])').click()
await expect(
page.getByRole('button', { name: 'Edit Sketch' })
).toBeEnabled({ timeout: 2000 })
@ -139,7 +139,7 @@ sketch001 = startSketchOn(XZ)
await page.waitForTimeout(600) // wait for animation
await page.getByText('tangentialArcTo([8.33, -1.31], %)').click()
await page.getByText('tangentialArc(endAbsolute = [8.33, -1.31])').click()
await page.keyboard.press('End')
await page.keyboard.down('Shift')
await page.keyboard.press('ArrowUp')
@ -212,7 +212,7 @@ sketch001 = startProfileAt([12.34, -12.34], sketch002)
`sketch001 = startSketchOn(XZ)
|> startProfileAt([4.61, -14.01], %)
|> line(end = [12.73, -0.09])
|> tangentialArcTo([24.95, -5.38], %)
|> tangentialArc(endAbsolute = [24.95, -5.38])
|> arcTo({
interior = [20.18, -1.7],
end = [11.82, -1.16]
@ -262,7 +262,7 @@ sketch001 = startProfileAt([12.34, -12.34], sketch002)
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn(XZ)
|> startProfileAt([4.61, -14.01], %)
|> line(end = [12.73, -0.09])
|> tangentialArcTo([24.95, -5.38], %)
|> tangentialArc(endAbsolute = [24.95, -5.38])
|> arcTo({
interior = [20.18, -1.7],
end = [11.82, -1.16]
@ -326,7 +326,7 @@ sketch001 = startProfileAt([12.34, -12.34], sketch002)
prevContent = await page.locator('.cm-content').innerText()
}
// drag tangentialArcTo handle
// drag tangentialArc handle
const tangentEnd = await u.getBoundingBox('[data-overlay-index="1"]')
await page.mouse.move(tangentEnd.x, tangentEnd.y - 5)
await page.mouse.down()
@ -407,7 +407,7 @@ sketch001 = startProfileAt([12.34, -12.34], sketch002)
.toHaveText(`sketch001 = startSketchOn(XZ)
|> startProfileAt([6.44, -12.07], %)
|> line(end = [14.72, 1.97])
|> tangentialArcTo([26.92, -3.32], %)
|> tangentialArc(endAbsolute = [26.92, -3.32])
|> arcTo({
interior = [18.11, -3.73],
end = [9.77, -3.19]
@ -577,7 +577,7 @@ sketch001 = startSketchOn(XZ)
sketch001 = startSketchOn(XZ)
|> startProfileAt([4.61, -10.01], %)
|> line(end = [12.73, -0.09])
|> tangentialArcTo([24.95, -0.38], %)
|> tangentialArc(endAbsolute = [24.95, -0.38])
|> close()
|> extrude(length = 5)`
)
@ -646,7 +646,7 @@ sketch001 = startSketchOn(XZ)
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
prevContent = await page.locator('.cm-content').innerText()
// drag tangentialArcTo handle
// drag tangentialArc handle
const tangentEnd = await u.getBoundingBox('[data-overlay-index="1"]')
await page.dragAndDrop('#stream', '#stream', {
sourcePosition: { x: tangentEnd.x + 10, y: tangentEnd.y - 5 },
@ -663,7 +663,7 @@ sketch001 = startSketchOn(XZ)
`sketch001 = startSketchOn(XZ)
|> startProfileAt([7.12, -12.68], %)
|> line(end = [12.68, -1.09])
|> tangentialArcTo([24.89, 0.68], %)
|> tangentialArc(endAbsolute = [24.89, 0.68])
|> close()
|> extrude(length = 5)`,
{ shouldNormalise: true }
@ -685,7 +685,7 @@ sketch001 = startSketchOn(XZ)
sketch001 = startSketchOn(XZ)
|> startProfileAt([4.61, -14.01], %)
|> line(end = [12.73, -0.09])
|> tangentialArcTo([24.95, -5.38], %)
|> tangentialArc(endAbsolute = [24.95, -5.38])
|> close()
|> revolve(axis = X)`
)
@ -757,7 +757,7 @@ sketch001 = startSketchOn(XZ)
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
prevContent = await page.locator('.cm-content').innerText()
// drag tangentialArcTo handle
// drag tangentialArc handle
const tangentEnd = await u.getBoundingBox('[data-overlay-index="1"]')
await page.mouse.move(tangentEnd.x, tangentEnd.y - 5)
await page.mouse.down()
@ -771,7 +771,7 @@ sketch001 = startSketchOn(XZ)
`sketch001 = startSketchOn(XZ)
|> startProfileAt([6.44, -12.07], %)
|> line(end = [14.72, 1.97])
|> tangentialArcTo([24.95, -5.38], %)
|> tangentialArc(endAbsolute = [24.95, -5.38])
|> line(end = [1.97, 2.06])
|> close()
|> revolve(axis = X)`,
@ -1653,7 +1653,7 @@ profile003 = startProfileAt([206.63, -56.73], sketch001)
await page.waitForTimeout(600)
})
const codeFromTangentialArc = ` |> tangentialArcTo([39.49, 88.22], %)`
const codeFromTangentialArc = ` |> tangentialArc(endAbsolute = [39.49, 88.22])`
await test.step('check that tangential tool does not snap to other profile starts', async () => {
await toolbar.tangentialArcBtn.click()
await page.waitForTimeout(1000)
@ -1675,7 +1675,7 @@ profile003 = startProfileAt([206.63, -56.73], sketch001)
// check pixel is now gray at tanArcLocation to verify code has executed
await scene.expectPixelColor([26, 26, 26], tanArcLocation, 15)
await editor.expectEditor.not.toContain(
`tangentialArcTo([39.49, 88.22], %)`
`tangentialArc(endAbsolute = [39.49, 88.22])`
)
})
@ -1876,7 +1876,7 @@ profile003 = startProfileAt([206.63, -56.73], sketch001)
await endArcStartLine()
await editor.expectEditor.toContain(
`|> tangentialArcTo([16.61, 4.14], %)`
`|> tangentialArc(endAbsolute = [16.61, 4.14])`
)
// Add a three-point arc segment
@ -2416,7 +2416,7 @@ sketch001 = startSketchOn(XZ)
profile001 = startProfileAt([-63.43, 193.08], sketch001)
|> line(end = [168.52, 149.87])
|> line(end = [190.29, -39.18])
|> tangentialArcTo([319.63, 129.65], %)
|> tangentialArc(endAbsolute = [319.63, 129.65])
|> line(end = [-217.65, -21.76])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()

View File

@ -588,6 +588,7 @@ test(
'Draft circle should look right',
{ tag: '@snapshot' },
async ({ page, context, cmdBar, scene }) => {
test.fixme(orRunWhenFullSuiteEnabled())
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
const PUR = 400 / 37.5 //pixeltoUnitRatio
@ -683,7 +684,7 @@ test.describe(
await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20)
code += `
|> tangentialArcTo([551.2, -62.01], %)`
|> tangentialArc(endAbsolute = [551.2, -62.01])`
await expect(u.codeLocator).toHaveText(code)
// click tangential arc tool again to unequip it
@ -777,7 +778,7 @@ test.describe(
await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20)
code += `
|> tangentialArcTo([551.2, -62.01], %)`
|> tangentialArc(endAbsolute = [551.2, -62.01])`
await expect(u.codeLocator).toHaveText(code)
await page

View File

@ -98,7 +98,7 @@ part001 = startSketchOn(XZ)
intersectTag: a,
offset: 0
}, %)
|> tangentialArcTo([13.14 + 0, 13.14], %)
|> tangentialArc(endAbsolute = [13.14 + 0, 13.14])
|> close()
|> extrude(length = 5 + 7)
`

View File

@ -0,0 +1,15 @@
import { createProject } from '@e2e/playwright/test-utils'
import { test } from '@e2e/playwright/zoo-test'
test.describe('Stress test', () => {
test('Create project and load stress test', async ({
cmdBar,
scene,
page,
}, testInfo) => {
const projectName = 'stress-test-project'
// Create and load project
await createProject({ name: projectName, page })
await scene.settled(cmdBar)
})
})

View File

@ -1140,7 +1140,7 @@ export function tomlToPerProjectSettings(
return TOML.parse(toml)
}
export function perProjectsettingsToToml(
export function perProjectSettingsToToml(
settings: DeepPartial<ProjectConfiguration>
) {
// eslint-disable-next-line no-restricted-syntax

View File

@ -229,7 +229,7 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
intersectTag = a,
offset = 9
}, %)
|> tangentialArcTo([5 + 3.14 + 13, 20 + 3.14], %)
|> tangentialArc(endAbsolute = [5 + 3.14 + 13, 20 + 3.14])
`
)
})
@ -477,7 +477,7 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
intersectTag = a,
offset = 9
}, %)
|> tangentialArcTo([3.14 + 13, 3.14], %)
|> tangentialArc(endAbsolute = [3.14 + 13, 3.14])
`
)
localStorage.setItem('disableAxis', 'true')
@ -602,7 +602,7 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
intersectTag = a,
offset = 9
}, %)
|> tangentialArcTo([3.14 + 13, 1.14], %)
|> tangentialArc(endAbsolute = [3.14 + 13, 1.14])
`
)
localStorage.setItem('disableAxis', 'true')
@ -735,11 +735,7 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
locator: '[data-overlay-toolbar-index="11"]',
})
})
test('for segment [tangentialArcTo]', async ({
page,
editor,
homePage,
}) => {
test('for segment [tangentialArc]', async ({ page, editor, homePage }) => {
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
@ -762,7 +758,7 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
intersectTag = a,
offset = 9
}, %)
|> tangentialArcTo([3.14 + 13, -3.14], %)
|> tangentialArc(endAbsolute = [3.14 + 13, -3.14])
`
)
localStorage.setItem('disableAxis', 'true')
@ -787,28 +783,29 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
const clickUnconstrained = _clickUnconstrained(page, editor)
const clickConstrained = _clickConstrained(page, editor)
const tangentialArcTo = await u.getBoundingBox(
'[data-overlay-index="12"]'
)
const tangentialArc = await u.getBoundingBox('[data-overlay-index="12"]')
let ang = await u.getAngle('[data-overlay-index="12"]')
console.log('tangentialArcTo')
console.log('tangentialArc')
await clickConstrained({
hoverPos: { x: tangentialArcTo.x, y: tangentialArcTo.y },
hoverPos: { x: tangentialArc.x, y: tangentialArc.y },
constraintType: 'xAbsolute',
expectBeforeUnconstrained: 'tangentialArcTo([3.14 + 13, -3.14], %)',
expectAfterUnconstrained: 'tangentialArcTo([16.14, -3.14], %)',
expectFinal: 'tangentialArcTo([xAbs001, -3.14], %)',
expectBeforeUnconstrained:
'tangentialArc(endAbsolute = [3.14 + 13, -3.14])',
expectAfterUnconstrained: 'tangentialArc(endAbsolute = [16.14, -3.14])',
expectFinal: 'tangentialArc(endAbsolute = [xAbs001, -3.14])',
ang: ang + 180,
steps: 6,
locator: '[data-overlay-toolbar-index="12"]',
})
console.log('tangentialArcTo2')
console.log('tangentialArc2')
await clickUnconstrained({
hoverPos: { x: tangentialArcTo.x, y: tangentialArcTo.y },
hoverPos: { x: tangentialArc.x, y: tangentialArc.y },
constraintType: 'yAbsolute',
expectBeforeUnconstrained: 'tangentialArcTo([xAbs001, -3.14], %)',
expectAfterUnconstrained: 'tangentialArcTo([xAbs001, yAbs001], %)',
expectFinal: 'tangentialArcTo([xAbs001, -3.14], %)',
expectBeforeUnconstrained:
'tangentialArc(endAbsolute = [xAbs001, -3.14])',
expectAfterUnconstrained:
'tangentialArc(endAbsolute = [xAbs001, yAbs001])',
expectFinal: 'tangentialArc(endAbsolute = [xAbs001, -3.14])',
ang: ang + 180,
steps: 10,
locator: '[data-overlay-toolbar-index="12"]',
@ -1091,7 +1088,7 @@ part001 = startSketchOn(XZ)
intersectTag = a,
offset = 9
}, %)
|> tangentialArcTo([3.14 + 13, 1.14], %)
|> tangentialArc(endAbsolute = [3.14 + 13, 1.14])
|> arcTo({
interior = [16.25, 5.12],
end = [21.61, 4.15]
@ -1161,8 +1158,8 @@ part001 = startSketchOn(XZ)
ang = await u.getAngle('[data-overlay-index="12"]')
await deleteSegmentSequence({
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
codeToBeDeleted: 'tangentialArcTo([3.14 + 13, 1.14], %)',
stdLibFnName: 'tangentialArcTo',
codeToBeDeleted: 'tangentialArc(endAbsolute = [3.14 + 13, 1.14])',
stdLibFnName: 'tangentialArc',
ang: ang + 180,
steps: 6,
locator: '[data-overlay-toolbar-index="12"]',

View File

@ -535,7 +535,7 @@ part001 = startSketchOn(XZ)
intersectTag = a,
offset = 0
}, %)
|> tangentialArcTo([13.14 + 0, 13.14], %)
|> tangentialArc(endAbsolute = [13.14 + 0, 13.14])
|> close()
|> extrude(length = 5 + 7)
`
@ -574,7 +574,7 @@ part001 = startSketchOn(XZ)
const extrusionTopCap: Coords2d = [800, 240]
const flatExtrusionFace: Coords2d = [960, 160]
const tangentialArcTo: Coords2d = [840, 160]
const tangentialArc: Coords2d = [840, 160]
const close: Coords2d = [720, 200]
const nothing: Coords2d = [600, 200]
const closeEdge: Coords2d = [744, 233]
@ -671,28 +671,28 @@ part001 = startSketchOn(XZ)
)
await checkCodeAtHoverPosition(
'tangentialArcTo',
tangentialArcTo,
'tangentialArcTo([13.14+0,13.14],%)extrude(length=5+7)',
'tangentialArcTo([13.14 + 0, 13.14], %)'
'tangentialArc',
tangentialArc,
'tangentialArc(endAbsolute=[13.14+0,13.14])extrude(length=5+7)',
'tangentialArc(endAbsolute = [13.14 + 0, 13.14])'
)
await checkCodeAtHoverPosition(
'tangentialArcEdge',
tangentialArcEdge,
`tangentialArcTo([13.14+0,13.14],%)`,
'tangentialArcTo([13.14 + 0, 13.14], %)'
`tangentialArc(endAbsolute=[13.14+0,13.14])`,
'tangentialArc(endAbsolute = [13.14 + 0, 13.14])'
)
await checkCodeAtHoverPosition(
'tangentialArcOppositeEdge',
tangentialArcOppositeEdge,
`tangentialArcTo([13.14+0,13.14],%)`,
'tangentialArcTo([13.14 + 0, 13.14], %)'
`tangentialArc(endAbsolute=[13.14+0,13.14])`,
'tangentialArc(endAbsolute = [13.14 + 0, 13.14])'
)
await checkCodeAtHoverPosition(
'tangentialArcAdjacentEdge',
tangentialArcAdjacentEdge,
`tangentialArcTo([13.14+0,13.14],%)`,
'tangentialArcTo([13.14 + 0, 13.14], %)'
`tangentialArc(endAbsolute=[13.14+0,13.14])`,
'tangentialArc(endAbsolute = [13.14 + 0, 13.14])'
)
await checkCodeAtHoverPosition(
@ -940,7 +940,7 @@ part001 = startSketchOn(XZ)
},
{
pos: [1107, 161],
expectedCode: 'tangentialArcTo([167.95, -28.85], %)',
expectedCode: 'tangentialArc(endAbsolute = [167.95, -28.85])',
},
] as const
await page.addInitScript(

77
flake.lock generated
View File

@ -1,6 +1,56 @@
{
"nodes": {
"naersk": {
"inputs": {
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1743800763,
"narHash": "sha256-YFKV+fxEpMgP5VsUcM6Il28lI0NlpM7+oB1XxbBAYCw=",
"owner": "nix-community",
"repo": "naersk",
"rev": "ed0232117731a4c19d3ee93aa0c382a8fe754b01",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "naersk",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1744157173,
"narHash": "sha256-bWSjxDwq7iVePrhmA7tY2dyMWHuNJo8knkO4y+q4ZkY=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "6a39c6e495eefabc935d8ddf66aa45d85b85fa3f",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1744157173,
"narHash": "sha256-bWSjxDwq7iVePrhmA7tY2dyMWHuNJo8knkO4y+q4ZkY=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "6a39c6e495eefabc935d8ddf66aa45d85b85fa3f",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_3": {
"locked": {
"lastModified": 1736320768,
"narHash": "sha256-nIYdTAiKIGnFNugbomgBJR+Xv5F1ZQU+HfaBqJKroC0=",
@ -16,38 +66,23 @@
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1728538411,
"narHash": "sha256-f0SBJz1eZ2yOuKUr5CA9BHULGXVSn6miBuUWdTyhUhU=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "b69de56fac8c2b6f8fd27f2eca01dcda8e0a4221",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs",
"naersk": "naersk",
"nixpkgs": "nixpkgs_2",
"rust-overlay": "rust-overlay"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": "nixpkgs_2"
"nixpkgs": "nixpkgs_3"
},
"locked": {
"lastModified": 1736476219,
"narHash": "sha256-+qyv3QqdZCdZ3cSO/cbpEY6tntyYjfe1bB12mdpNFaY=",
"lastModified": 1744338850,
"narHash": "sha256-pwMIVmsb8fjjT92n5XFDqCsplcX70qVMMT7NulumPXs=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "de30cc5963da22e9742bbbbb9a3344570ed237b9",
"rev": "5e64aecc018e6f775572609e7d7485fdba6985a7",
"type": "github"
},
"original": {

136
flake.nix
View File

@ -1,84 +1,96 @@
{
description = "modeling-app development environment";
description = "zoo.dev modeling-app";
# Flake inputs
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
rust-overlay.url = "github:oxalica/rust-overlay"; # A helper for Rust + Nix
rust-overlay.url = "github:oxalica/rust-overlay";
naersk.url = "github:nix-community/naersk";
};
# Flake outputs
outputs = { self, nixpkgs, rust-overlay }:
let
# Overlays enable you to customize the Nixpkgs attribute set
overlays = [
# Makes a `rust-bin` attribute available in Nixpkgs
(import rust-overlay)
# Provides a `rustToolchain` attribute for Nixpkgs that we can use to
# create a Rust environment
(self: super: {
rustToolchain = super. rust-bin.stable.latest.default.override {
targets = [ "wasm32-unknown-unknown" ];
extensions = [ "rustfmt" "llvm-tools-preview" "rust-src" ];
outputs = {
self,
nixpkgs,
rust-overlay,
naersk,
}: let
overlays = [
(import rust-overlay)
(self: super: {
rustToolchain = super.rust-bin.stable.latest.default.override {
targets = ["wasm32-unknown-unknown"];
extensions = ["rustfmt" "llvm-tools-preview" "rust-src"];
};
})
];
allSystems = [
"x86_64-linux"
"aarch64-linux"
"x86_64-darwin"
"aarch64-darwin"
];
forAllSystems = f:
nixpkgs.lib.genAttrs allSystems (system:
f {
pkgs = import nixpkgs {
inherit overlays system;
};
})
(self: super: {
cargo-llvm-cov = super.cargo-llvm-cov.overrideAttrs(oa: {
doCheck = false; doInstallCheck = false;
});
})
];
# Systems supported
allSystems = [
"x86_64-linux" # 64-bit Intel/AMD Linux
"aarch64-linux" # 64-bit ARM Linux
"x86_64-darwin" # 64-bit Intel macOS
"aarch64-darwin" # 64-bit ARM macOS
];
# Helper to provide system-specific attributes
forAllSystems = f: nixpkgs.lib.genAttrs allSystems (system: f {
pkgs = import nixpkgs { inherit overlays system; config.allowBroken = true; };
});
in
{
# Development environment output
devShells = forAllSystems ({ pkgs }: {
default = pkgs.mkShell {
# The Nix packages provided in the environment
packages = (with pkgs; [
# The package provided by our custom overlay. Includes cargo, Clippy, cargo-fmt,
# rustdoc, rustfmt, and other tools.
system = system;
});
in {
devShells = forAllSystems ({pkgs, ...}: {
default = pkgs.mkShell {
packages =
(with pkgs; [
rustToolchain
cargo-llvm-cov
cargo-nextest
just
postgresql.lib
openssl
pkg-config
nodejs_22
yarn
electron
playwright-driver.browsers
]) ++ pkgs.lib.optionals pkgs.stdenv.isDarwin (with pkgs; [
wasm-pack
python3Full
])
++ pkgs.lib.optionals pkgs.stdenv.isDarwin (with pkgs; [
libiconv
darwin.apple_sdk.frameworks.Security
]);
TARGET_CC = "${pkgs.stdenv.cc}/bin/${pkgs.stdenv.cc.targetPrefix}cc";
LIBCLANG_PATH = "${pkgs.libclang.lib}/lib";
ELECTRON_OVERRIDE_DIST_PATH = "${pkgs.electron}/bin/";
PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS = true;
PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH = "${pkgs.playwright-driver.browsers}/chromium-1091/chrome-linux/chrome";
PLAYWRIGHT_BROWSERS_PATH = "${pkgs.playwright-driver.browsers}";
NODE_ENV = "development";
};
});
};
TARGET_CC = "${pkgs.stdenv.cc}/bin/${pkgs.stdenv.cc.targetPrefix}cc";
LIBCLANG_PATH = "${pkgs.libclang.lib}/lib";
ELECTRON_OVERRIDE_DIST_PATH = "${pkgs.electron}/bin/";
PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS = true;
PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH = "${pkgs.playwright-driver.browsers}/chromium-1091/chrome-linux/chrome";
PLAYWRIGHT_BROWSERS_PATH = "${pkgs.playwright-driver.browsers}";
NODE_ENV = "development";
};
});
packages = forAllSystems ({
pkgs,
system,
}: let
naersk-lib = pkgs.callPackage naersk {
cargo = pkgs.rustToolchain;
rustc = pkgs.rustToolchain;
};
in {
kcl-language-server = naersk-lib.buildPackage {
pname = "kcl-language-server";
version = "0.1.0";
release = true;
src = ./rust;
cargoBuildOptions = opt: opt ++ ["-p" "kcl-language-server"];
buildInputs = [pkgs.openssl pkgs.pkg-config];
};
default = self.packages.${system}.kcl-language-server;
});
};
}

View File

@ -234,7 +234,7 @@
"ts-node": "^10.0.0",
"typescript": "^5.8.3",
"typescript-eslint": "^8.29.0",
"vite": "^5.4.17",
"vite": "^5.4.18",
"vite-plugin-package-version": "^1.1.0",
"vite-plugin-top-level-await": "^1.5.0",
"vite-tsconfig-paths": "^4.3.2",

View File

@ -834,9 +834,9 @@ vite-tsconfig-paths@^5.1.4:
tsconfck "^3.0.3"
"vite@^5.0.0 || ^6.0.0":
version "6.2.5"
resolved "https://registry.yarnpkg.com/vite/-/vite-6.2.5.tgz#d093b5fe8eb96e594761584a966ab13f24457820"
integrity sha512-j023J/hCAa4pRIUH6J9HemwYfjB5llR2Ps0CWeikOtdR8+pAURAk0DoJC5/mm9kd+UgdnIy7d6HE4EAvlYhPhA==
version "6.2.6"
resolved "https://registry.yarnpkg.com/vite/-/vite-6.2.6.tgz#7f0ccf2fdc0c1eda079ce258508728e2473d3f61"
integrity sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw==
dependencies:
esbuild "^0.25.0"
postcss "^8.5.3"

View File

@ -389,6 +389,13 @@ export class LanguageServerPlugin implements PluginValue {
}
if (insertText && insertTextFormat === 2) {
// We end with ${} so you can jump to the end of the snippet.
// After the last argument.
// This is not standard from the lsp so we add it here.
if (insertText.endsWith(')')) {
// We have a function its safe to insert the ${} at the end.
insertText = insertText + '${}'
}
return snippetCompletion(insertText, completion)
}

View File

@ -45,6 +45,7 @@ export default defineConfig({
[process.env.CI ? 'dot' : 'list'],
['json', { outputFile: './test-results/report.json' }],
['html'],
['./e2e/playwright/lib/api-reporter.ts'],
],
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {

View File

@ -45,6 +45,7 @@ export default defineConfig({
['dot'],
['json', { outputFile: './test-results/report.json' }],
['html'],
['./e2e/playwright/lib/api-reporter.ts'],
],
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {

View File

@ -10,25 +10,25 @@ fn dividerSketch(plane) {
sketch000 = startSketchOn(plane)
|> startProfileAt([-16.82, 21.2], %)
|> line(end = [-0.13, -1.27])
|> tangentialArcTo([-15.94, profileStartY(%) - 7.73], %)
|> tangentialArcTo([-16.6, profileStartY(%) - 15.52], %)
|> tangentialArcTo([-18.38, profileStartY(%) - 18.63], %)
|> tangentialArc(endAbsolute = [-15.94, profileStartY(%) - 7.73])
|> tangentialArc(endAbsolute = [-16.6, profileStartY(%) - 15.52])
|> tangentialArc(endAbsolute = [-18.38, profileStartY(%) - 18.63])
|> line(end = [-1.25, -2.6])
|> xLine(length = 6.04)
|> line(end = [6.68, 7.87])
|> tangentialArcTo([10.06, profileStartY(%) - 12.69], %)
|> tangentialArc(endAbsolute = [10.06, profileStartY(%) - 12.69])
|> line(end = [7.28, -8.47])
|> xLine(length = 5.98)
|> line(end = [-1.3, 3.01])
|> tangentialArcTo([22.45, profileStartY(%) - 2.84], %)
|> tangentialArcTo([25.08, profileStartY(%) + 6.42], %)
|> tangentialArc(endAbsolute = [22.45, profileStartY(%) - 2.84])
|> tangentialArc(endAbsolute = [25.08, profileStartY(%) + 6.42])
|> line(end = [2.35, 16.36])
|> line(end = [1.78, 1.15])
|> tangentialArcTo([23.93, profileStartY(%) + 27.29], %)
|> tangentialArc(endAbsolute = [23.93, profileStartY(%) + 27.29])
|> line(end = [-1.92, 0.21])
|> line(end = [-3.74, -26.54])
|> tangentialArcTo([15.13, profileStartY(%) - 1.72], %)
|> tangentialArcTo(profileStart(%), %)
|> tangentialArc(endAbsolute = [15.13, profileStartY(%) - 1.72])
|> tangentialArc(endAbsolute = profileStart(%))
|> close()
return sketch000
}

View File

@ -17,66 +17,42 @@ brakeCaliperSketch = startSketchOn(XY)
0,
rotorTotalThickness + caliperTolerance - caliperInnerEdgeRadius
])
|> tangentialArc({
offset = 90,
radius = caliperInnerEdgeRadius
}, %)
|> tangentialArc(angle = 90, radius = caliperInnerEdgeRadius)
|> line(end = [
-caliperPadLength + 2 * caliperInnerEdgeRadius,
0
])
|> tangentialArc({
offset = -90,
radius = caliperInnerEdgeRadius
}, %)
|> tangentialArc(angle = -90, radius = caliperInnerEdgeRadius)
|> line(end = [
0,
caliperThickness - (caliperInnerEdgeRadius * 2)
])
|> tangentialArc({
offset = -90,
radius = caliperInnerEdgeRadius
}, %)
|> tangentialArc(angle = -90, radius = caliperInnerEdgeRadius)
|> line(end = [
caliperPadLength + caliperThickness - caliperOuterEdgeRadius - caliperInnerEdgeRadius,
0
])
|> tangentialArc({
offset = -90,
radius = caliperOuterEdgeRadius
}, %)
|> tangentialArc(angle = -90, radius = caliperOuterEdgeRadius)
|> line(end = [
0,
-2 * caliperTolerance - (2 * caliperThickness) - rotorTotalThickness + 2 * caliperOuterEdgeRadius
])
|> tangentialArc({
offset = -90,
radius = caliperOuterEdgeRadius
}, %)
|> tangentialArc(angle = -90, radius = caliperOuterEdgeRadius)
|> line(end = [
-caliperPadLength - caliperThickness + caliperOuterEdgeRadius + caliperInnerEdgeRadius,
0
])
|> tangentialArc({
offset = -90,
radius = caliperInnerEdgeRadius
}, %)
|> tangentialArc(angle = -90, radius = caliperInnerEdgeRadius)
|> line(end = [
0,
caliperThickness - (2 * caliperInnerEdgeRadius)
])
|> tangentialArc({
offset = -90,
radius = caliperInnerEdgeRadius
}, %)
|> tangentialArc(angle = -90, radius = caliperInnerEdgeRadius)
|> line(end = [
caliperPadLength - (2 * caliperInnerEdgeRadius),
0
])
|> tangentialArc({
offset = 90,
radius = caliperInnerEdgeRadius
}, %)
|> tangentialArc(angle = 90, radius = caliperInnerEdgeRadius)
|> close()
// Revolve the brake caliper sketch

View File

@ -17,7 +17,7 @@ tireSketch = startSketchOn(XY)
],
tag = $edge1,
)
|> tangentialArc({ offset = -90, radius = bendRadius }, %)
|> tangentialArc(angle = -90, radius = bendRadius)
|> line(endAbsolute = [
tireOuterDiameter / 2,
tireDepth / 2 - tireTreadOffset
@ -36,7 +36,7 @@ tireSketch = startSketchOn(XY)
tireOuterDiameter / 2,
-tireDepth / 2 + bendRadius
])
|> tangentialArc({ offset = -90, radius = bendRadius }, %)
|> tangentialArc(angle = -90, radius = bendRadius)
|> line(endAbsolute = [tireInnerDiameter / 2, -tireDepth / 2], tag = $edge2)
|> close()

View File

@ -26,7 +26,7 @@ fn lug(plane, length, diameter) {
|> angledLine(angle = 70, lengthY = lugHeadLength)
|> xLine(endAbsolute = lugDiameter / 2)
|> yLine(endAbsolute = lugLength)
|> tangentialArc({ offset = 90, radius = fromMm(3) }, %)
|> tangentialArc(angle = 90, radius = fromMm(3))
|> xLine(endAbsolute = 0 + .001, tag = $c1)
|> yLine(endAbsolute = lugThreadDepth)
|> xLine(endAbsolute = lugThreadDiameter)

View File

@ -19,17 +19,11 @@ fn cycloidalGear(gearPitch, gearHeight, holeDiameter, helixAngle) {
angleEnd = -90 + helixAngleP,
radius = gearPitch
}, %)
|> tangentialArc({
radius = gearPitch * 1.67,
offset = 60
}, %)
|> tangentialArc({ radius = gearPitch, offset = -180 }, %)
|> tangentialArc({
radius = gearPitch * 1.67,
offset = 60
}, %)
|> tangentialArc({ radius = gearPitch, offset = -180 }, %)
|> tangentialArcTo([profileStartX(%), profileStartY(%)], %)
|> tangentialArc(radius = gearPitch * 1.67, angle = 60)
|> tangentialArc(radius = gearPitch, angle = -180)
|> tangentialArc(radius = gearPitch * 1.67, angle = 60)
|> tangentialArc(radius = gearPitch, angle = -180)
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close(%)
|> hole(circle(center = [0, 0], radius = holeDiameter / 2), %)
return gearProfile

View File

@ -184,15 +184,15 @@ handlePlane = startSketchOn(offsetPlane(XY, offset = handleHeightAboveTheFloor))
handleProfilePath = startProfileAt([0 + handleOffset, 0], handlePlane)
|> yLine(length = -handleLengthSegmentA)
|> tangentialArcTo([
|> tangentialArc(endAbsolute = [
handleFillet + handleOffset,
-handleDepth
], %)
])
|> xLine(length = handleLengthSegmentB)
|> tangentialArcTo([
|> tangentialArc(endAbsolute = [
handleOffset + handleWidth,
-handleLengthSegmentA
], %)
])
|> yLine(length = handleLengthSegmentA)
handleSectionPlane = startSketchOn(XZ)
handleProfileSection = circle(

View File

@ -33,9 +33,9 @@ fn primaryTube(n, angle001, length001, length002, length003) {
sweepPath = startSketchOn(sweepPlane)
|> startProfileAt([0, plateHeight], %)
|> line(end = [0, length001])
|> tangentialArc({ offset = -80, radius = bendRadius }, %, $arc01)
|> tangentialArc(angle = -80, radius = bendRadius, tag = $arc01)
|> angledLine(angle = tangentToEnd(arc01), length = length002)
|> tangentialArc({ offset = 85, radius = bendRadius }, %, $arc02)
|> tangentialArc(angle = 85, radius = bendRadius, tag = $arc02)
|> angledLine(angle = tangentToEnd(arc02), length = length003)
// Create the cross section of each tube and sweep them
@ -57,21 +57,21 @@ primaryTube(3, 25.2, 5, 5, 3)
flangeSketch = startSketchOn(XY)
|> startProfileAt([3 + 1.3, -1.25], %)
|> xLine(length = -2.6, tag = $seg01)
|> tangentialArc({ radius = .3, offset = -40 }, %)
|> tangentialArc({ radius = .9, offset = 80 }, %)
|> tangentialArc({ radius = .3, offset = -40 }, %)
|> tangentialArc(radius = .3, angle = -40)
|> tangentialArc(radius = .9, angle = 80)
|> tangentialArc(radius = .3, angle = -40)
|> xLine(length = -1.4, tag = $seg03)
|> yLine(length = segLen(seg01), tag = $seg04)
|> xLine(length = 3.1, tag = $seg05)
|> tangentialArc({ radius = .3, offset = -40 }, %)
|> tangentialArc({ radius = 1.5, offset = 80 }, %)
|> tangentialArc({ radius = .3, offset = -40 }, %)
|> tangentialArc(radius = .3, angle = -40)
|> tangentialArc(radius = 1.5, angle = 80)
|> tangentialArc(radius = .3, angle = -40)
|> xLine(length = segLen(seg05), tag = $seg07)
|> yLine(endAbsolute = profileStartY(%), tag = $seg08)
|> xLine(length = -segLen(seg03), tag = $seg09)
|> tangentialArc({ radius = .3, offset = -40 }, %)
|> tangentialArc({ radius = .9, offset = 80 }, %)
|> tangentialArcTo([profileStartX(%), profileStartY(%)], %)
|> tangentialArc(radius = .3, angle = -40)
|> tangentialArc(radius = .9, angle = 80)
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
// Create openings in the flange to accommodate each tube

View File

@ -36,9 +36,9 @@ fn slot(sketch1, start, end, width) {
ystart = width / 2 * sin(toRadians(angle - 90)) + start[1]
slotSketch = startProfileAt([xstart, ystart], sketch1)
|> angledLine(angle = angle, length = dist)
|> tangentialArc({ radius = width / 2, offset = 180 }, %)
|> tangentialArc(radius = width / 2, angle = 180)
|> angledLine(angle = angle, length = -dist)
|> tangentialArcTo([profileStartX(%), profileStartY(%)], %)
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
return slotSketch
}

View File

@ -127,7 +127,7 @@ sketch006 = startSketchOn(XZ)
|> yLine(length = 10)
|> line(end = [0.6, 0])
|> yLine(length = -.05)
|> tangentialArc({ radius = 0.6, offset = -90 }, %)
|> tangentialArc(radius = 0.6, angle = -90)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> revolve(axis = Y)
@ -192,15 +192,15 @@ sketch011 = startSketchOn(XZ)
sketch012 = startSketchOn(offsetPlane(XZ, offset = handleThickness / 2))
|> startProfileAt([2.3, 6.4], %)
|> line(end = [0.56, 0])
|> tangentialArcTo([4.1, 5.26], %)
|> tangentialArcTo([4.17, 1.6], %)
|> tangentialArcTo([3.13, 0.61], %)
|> tangentialArc(endAbsolute = [4.1, 5.26])
|> tangentialArc(endAbsolute = [4.17, 1.6])
|> tangentialArc(endAbsolute = [3.13, 0.61])
|> line(end = [-1.09, 0])
|> line(end = [0, 0.43])
|> line(end = [0.99, -0.02])
|> tangentialArcTo([3.63, 1.6], %)
|> tangentialArcTo([3.56, 5.15], %)
|> tangentialArcTo([2.72, 5.88], %)
|> tangentialArc(endAbsolute = [3.63, 1.6])
|> tangentialArc(endAbsolute = [3.56, 5.15])
|> tangentialArc(endAbsolute = [2.72, 5.88])
|> line(end = [-0.4, 0])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()

View File

@ -24,13 +24,13 @@ rackBody = startSketchOn(XY)
fn tooth() {
toothSketch = startSketchOn(XY)
|> startProfileAt([-length / 2 + 0.567672, minHeight], %)
|> tangentialArcToRelative([0.157636, 0.110378], %)
|> tangentialArc(end = [0.157636, 0.110378])
|> line(end = [0.329118, 0.904244])
|> tangentialArcToRelative([0.157636, 0.110378], %)
|> tangentialArc(end = [0.157636, 0.110378])
|> line(end = [0.186505, 0])
|> tangentialArcToRelative([0.157636, -0.110378], %)
|> tangentialArc(end = [0.157636, -0.110378])
|> line(end = [0.329118, -0.904244])
|> tangentialArcToRelative([0.157636, -0.110378], %)
|> tangentialArc(end = [0.157636, -0.110378])
|> close()
|> extrude(length = width)
return toothSketch
@ -44,7 +44,7 @@ teeth = tooth()
endCapTooth = startSketchOn(XY)
|> startProfileAt([-length / 2, 11.849525], %)
|> line(end = [0.314524, -0.864147])
|> tangentialArcToRelative([0.157636, -0.110378], %)
|> tangentialArc(end = [0.157636, -0.110378])
|> line(endAbsolute = [-length / 2, minHeight])
|> close()
|> extrude(length = width)
@ -53,7 +53,7 @@ endCapTooth = startSketchOn(XY)
endCapTooth2 = startSketchOn(XY)
|> startProfileAt([length / 2, 11.849525], %)
|> line(end = [-0.314524, -0.864147])
|> tangentialArcToRelative([-0.157636, -0.110378], %)
|> tangentialArc(end = [-0.157636, -0.110378])
|> line(endAbsolute = [length / 2, minHeight])
|> close()
|> extrude(length = width)

View File

@ -18,7 +18,7 @@ iBeam = startSketchOn(-XZ)
|> xLine(length = flangeWidth / 2)
|> yLine(length = -flangeThickness)
|> xLine(endAbsolute = webThickness / 2 + rootRadius)
|> tangentialArc({ radius = rootRadius, offset = 90 }, %)
|> tangentialArc(radius = rootRadius, angle = 90)
|> yLine(endAbsolute = 0)
|> mirror2d(axis = X)
|> mirror2d(axis = Y)

View File

@ -74,11 +74,11 @@ fn keyFn(originStart, keyWidth, keyHeight, repeats, color) {
radius = 0.1
}, %)
|> angledLine(angle = 0, length = keyWidth - .2, tag = $rectangleSegmentA001)
|> tangentialArc({ radius = 0.1, offset = 90 }, %)
|> tangentialArc(radius = 0.1, angle = 90)
|> angledLine(angle = segAng(rectangleSegmentA001) + 90, length = keyHeight - .2, tag = $rectangleSegmentB001)
|> tangentialArc({ radius = 0.1, offset = 90 }, %)
|> tangentialArc(radius = 0.1, angle = 90)
|> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $rectangleSegmentC001)
|> tangentialArc({ radius = 0.1, offset = 90 }, %)
|> tangentialArc(radius = 0.1, angle = 90)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $rectangleSegmentD001)
|> close()
|> extrude(length = keyDepth)

View File

@ -69,7 +69,7 @@ customPlane = {
}
sketch003 = startSketchOn(customPlane)
|> startProfileAt([0, 0], %)
|> tangentialArc({ offset = 60, radius = height }, %)
|> tangentialArc(angle = 60, radius = height)
|> angledLine(angle = 60, endAbsoluteY = 0)
|> close()
|> extrude(length = wallThickness)
@ -85,11 +85,11 @@ sketch004 = startSketchOn(sketch002, 'END')
|> angledLine(angle = 60, endAbsoluteY = segEndY(seg01))
|> yLine(endAbsolute = height)
|> xLine(length = wallThickness)
|> tangentialArcTo([
|> tangentialArc(endAbsolute = [
(frontLength - wallsWidth) / 2 + wallsWidth,
height - ((height - exitHeight) / 2)
], %)
|> tangentialArcTo([frontLength, exitHeight], %)
])
|> tangentialArc(endAbsolute = [frontLength, exitHeight])
|> yLine(endAbsolute = 0)
|> close(tag = $seg04)
|> extrude(length = wallThickness)
@ -110,11 +110,11 @@ sketch005 = startSketchOn(customPlane2)
|> startProfileAt([0, 0], %)
|> yLine(endAbsolute = height)
|> xLine(endAbsolute = wallsWidth)
|> tangentialArcTo([
|> tangentialArc(endAbsolute = [
(frontLength - wallsWidth) / 2 + wallsWidth,
height - ((height - exitHeight) / 2)
], %)
|> tangentialArcTo([frontLength, exitHeight], %)
])
|> tangentialArc(endAbsolute = [frontLength, exitHeight])
|> yLine(endAbsolute = 0, tag = $seg03)
|> close()
|> extrude(length = wallThickness)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 57 KiB

View File

@ -37,49 +37,25 @@ bracketProfile = startSketchOn(XZ)
0
], %)
|> xLine(length = flangeLength)
|> tangentialArc({
radius = exteriorBendRadius,
offset = bendAngle
}, %)
|> tangentialArc(radius = exteriorBendRadius, angle = bendAngle)
|> angledLine(angle = bendAngle, endAbsoluteY = hatHeight - thickness, tag = $seg01)
|> tangentialArc({
radius = interiorBendRadius,
offset = -bendAngle
}, %)
|> tangentialArc(radius = interiorBendRadius, angle = -bendAngle)
|> xLine(endAbsolute = 0, tag = $seg02)
|> xLine(length = segLen(seg02))
|> tangentialArc({
radius = interiorBendRadius,
offset = -bendAngle
}, %)
|> tangentialArc(radius = interiorBendRadius, angle = -bendAngle)
|> angledLine(angle = -bendAngle, length = segLen(seg01))
|> tangentialArc({
radius = exteriorBendRadius,
offset = bendAngle
}, %)
|> tangentialArc(radius = exteriorBendRadius, angle = bendAngle)
|> xLine(length = flangeLength)
|> yLine(length = thickness, tag = $seg03)
|> xLine(length = -flangeLength, tag = $seg04)
|> tangentialArc({
radius = interiorBendRadius,
offset = -bendAngle
}, %)
|> tangentialArc(radius = interiorBendRadius, angle = -bendAngle)
|> angledLine(angle = 180 - bendAngle, length = segLen(seg01))
|> tangentialArc({
radius = exteriorBendRadius,
offset = bendAngle
}, %)
|> tangentialArc(radius = exteriorBendRadius, angle = bendAngle)
|> xLine(endAbsolute = 0, tag = $seg05)
|> xLine(length = -segLen(seg05))
|> tangentialArc({
radius = exteriorBendRadius,
offset = bendAngle
}, %)
|> tangentialArc(radius = exteriorBendRadius, angle = bendAngle)
|> angledLine(angle = bendAngle - 180, length = segLen(seg01))
|> tangentialArc({
radius = interiorBendRadius,
offset = -bendAngle
}, %)
|> tangentialArc(radius = interiorBendRadius, angle = -bendAngle)
|> xLine(length = -flangeLength, tag = $seg06)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg07)
|> close()

48
rust/Cargo.lock generated
View File

@ -1780,7 +1780,7 @@ dependencies = [
[[package]]
name = "kcl-bumper"
version = "0.1.58"
version = "0.1.60"
dependencies = [
"anyhow",
"clap",
@ -1791,7 +1791,7 @@ dependencies = [
[[package]]
name = "kcl-derive-docs"
version = "0.1.58"
version = "0.1.60"
dependencies = [
"Inflector",
"anyhow",
@ -1810,7 +1810,7 @@ dependencies = [
[[package]]
name = "kcl-directory-test-macro"
version = "0.1.58"
version = "0.1.60"
dependencies = [
"proc-macro2",
"quote",
@ -1819,7 +1819,7 @@ dependencies = [
[[package]]
name = "kcl-language-server"
version = "0.2.58"
version = "0.2.60"
dependencies = [
"anyhow",
"clap",
@ -1840,7 +1840,7 @@ dependencies = [
[[package]]
name = "kcl-language-server-release"
version = "0.1.58"
version = "0.1.60"
dependencies = [
"anyhow",
"clap",
@ -1860,7 +1860,7 @@ dependencies = [
[[package]]
name = "kcl-lib"
version = "0.2.58"
version = "0.2.60"
dependencies = [
"anyhow",
"approx 0.5.1",
@ -1928,7 +1928,7 @@ dependencies = [
[[package]]
name = "kcl-python-bindings"
version = "0.3.58"
version = "0.3.60"
dependencies = [
"anyhow",
"kcl-lib",
@ -1943,7 +1943,7 @@ dependencies = [
[[package]]
name = "kcl-test-server"
version = "0.1.58"
version = "0.1.60"
dependencies = [
"anyhow",
"hyper 0.14.32",
@ -1956,7 +1956,7 @@ dependencies = [
[[package]]
name = "kcl-to-core"
version = "0.1.58"
version = "0.1.60"
dependencies = [
"anyhow",
"async-trait",
@ -1970,7 +1970,7 @@ dependencies = [
[[package]]
name = "kcl-wasm-lib"
version = "0.1.58"
version = "0.1.60"
dependencies = [
"bson",
"console_error_panic_hook",
@ -2033,9 +2033,9 @@ dependencies = [
[[package]]
name = "kittycad-modeling-cmds"
version = "0.2.110"
version = "0.2.113"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdfd16800a12a2eaefff53958bd871875c246e669274269f7caefc25d19641ad"
checksum = "fa1c927569925425a1b03711617c384a30cb7554394e8a6a01266910b22421de"
dependencies = [
"anyhow",
"chrono",
@ -2762,9 +2762,9 @@ dependencies = [
[[package]]
name = "pyo3"
version = "0.24.0"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f1c6c3591120564d64db2261bec5f910ae454f01def849b9c22835a84695e86"
checksum = "17da310086b068fbdcefbba30aeb3721d5bb9af8db4987d6735b2183ca567229"
dependencies = [
"cfg-if",
"indoc",
@ -2781,9 +2781,9 @@ dependencies = [
[[package]]
name = "pyo3-build-config"
version = "0.24.0"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9b6c2b34cf71427ea37c7001aefbaeb85886a074795e35f161f5aecc7620a7a"
checksum = "e27165889bd793000a098bb966adc4300c312497ea25cf7a690a9f0ac5aa5fc1"
dependencies = [
"once_cell",
"target-lexicon",
@ -2791,9 +2791,9 @@ dependencies = [
[[package]]
name = "pyo3-ffi"
version = "0.24.0"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5507651906a46432cdda02cd02dd0319f6064f1374c9147c45b978621d2c3a9c"
checksum = "05280526e1dbf6b420062f3ef228b78c0c54ba94e157f5cb724a609d0f2faabc"
dependencies = [
"libc",
"pyo3-build-config",
@ -2801,9 +2801,9 @@ dependencies = [
[[package]]
name = "pyo3-macros"
version = "0.24.0"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0d394b5b4fd8d97d48336bb0dd2aebabad39f1d294edd6bcd2cccf2eefe6f42"
checksum = "5c3ce5686aa4d3f63359a5100c62a127c9f15e8398e5fdeb5deef1fed5cd5f44"
dependencies = [
"proc-macro2",
"pyo3-macros-backend",
@ -2813,9 +2813,9 @@ dependencies = [
[[package]]
name = "pyo3-macros-backend"
version = "0.24.0"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd72da09cfa943b1080f621f024d2ef7e2773df7badd51aa30a2be1f8caa7c8e"
checksum = "f4cf6faa0cbfb0ed08e89beb8103ae9724eb4750e3a78084ba4017cbe94f3855"
dependencies = [
"heck",
"proc-macro2",
@ -3956,9 +3956,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.44.1"
version = "1.44.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a"
checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48"
dependencies = [
"backtrace",
"bytes",

View File

@ -36,10 +36,10 @@ dashmap = { version = "6.1.0" }
http = "1"
indexmap = "2.7.0"
kittycad = { version = "0.3.36", default-features = false, features = ["js", "requests"] }
kittycad-modeling-cmds = { version = "0.2.110", features = ["ts-rs", "websocket"] }
kittycad-modeling-cmds = { version = "0.2.113", features = ["ts-rs", "websocket"] }
lazy_static = "1.5.0"
miette = "7.5.0"
pyo3 = { version = "0.24.0" }
pyo3 = { version = "0.24.1" }
serde = { version = "1", features = ["derive"] }
serde_json = { version = "1" }
slog = "2.7.0"

View File

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

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@
name = "kcl-language-server"
description = "A language server for KCL."
authors = ["KittyCAD Inc <kcl@kittycad.io>"]
version = "0.2.58"
version = "0.2.60"
edition = "2021"
license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -32,7 +32,7 @@ tracing-subscriber = { workspace = true }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
signal-hook = "0.3.17"
tokio = { version = "1.43.0", features = ["full"] }
tokio = { version = "1.44.2", features = ["full"] }
tower-lsp = { version = "0.20.0", features = ["proposed"] }
[target.'cfg(target_arch = "wasm32")'.dependencies]

View File

@ -9,7 +9,10 @@ export async function createClient(
serverOptions: lc.ServerOptions
): Promise<lc.LanguageClient> {
const clientOptions: lc.LanguageClientOptions = {
documentSelector: [{ scheme: 'file', language: 'kcl' }],
documentSelector: [
{ scheme: 'file', language: 'kcl' },
{ scheme: 'untitled', language: 'kcl' },
],
initializationOptions,
traceOutputChannel,
outputChannel,

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-lib"
description = "KittyCAD Language implementation and tools"
version = "0.2.58"
version = "0.2.60"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"
@ -103,7 +103,7 @@ tokio-tungstenite = { version = "0.24.0", features = [
tower-lsp = { workspace = true, features = ["proposed", "default"] }
[features]
default = ["engine"]
default = ["cli", "engine"]
cli = ["dep:clap", "kittycad/clap"]
dhat-heap = ["dep:dhat"]
# For the lsp server, when run with stdout for rpc we want to disable println.
@ -126,7 +126,7 @@ insta = { version = "1.41.1", features = ["json", "filters", "redactions"] }
kcl-directory-test-macro = { version = "0.1", path = "../kcl-directory-test-macro" }
miette = { version = "7.5.0", features = ["fancy"] }
pretty_assertions = "1.4.1"
tokio = { version = "1.41.1", features = ["rt-multi-thread", "macros", "time"] }
tokio = { version = "1.44.2", features = ["rt-multi-thread", "macros", "time"] }
twenty-twenty = "0.8.0"
[lints]

View File

@ -10,41 +10,41 @@ let corner_radius = 5.0
let brace_base = startSketchOn(XY)
|> startProfileAt([corner_radius, 0], %)
|> line(end = [width - corner_radius, 0.0])
|> tangentialArcToRelative([corner_radius, corner_radius], %)
|> tangentialArc(end = [corner_radius, corner_radius])
|> yLine(length = 25.0 - corner_radius)
|> tangentialArcToRelative([-corner_radius, corner_radius], %)
|> tangentialArc(end = [-corner_radius, corner_radius])
|> xLine(length = -(d_wrist_circumference[0] - (corner_radius * 2)))
|> tangentialArcToRelative([-corner_radius, corner_radius], %)
|> tangentialArc(end = [-corner_radius, corner_radius])
|> yLine(length = length - 25.0 - 23.0 - (corner_radius * 2))
|> tangentialArcToRelative([corner_radius, corner_radius], %)
|> tangentialArc(end = [corner_radius, corner_radius])
|> xLine(length = 15.0 - (corner_radius * 2))
|> tangentialArcToRelative([corner_radius, corner_radius], %)
|> tangentialArc(end = [corner_radius, corner_radius])
|> yLine(length = 23.0 - corner_radius)
|> tangentialArcToRelative([-corner_radius, corner_radius], %)
|> tangentialArc(end = [-corner_radius, corner_radius])
|> xLine(length = -(hand_thickness + 15.0 + 15.0 - (corner_radius * 2)))
|> tangentialArcToRelative([-corner_radius, -corner_radius], %)
|> tangentialArc(end = [-corner_radius, -corner_radius])
|> yLine(length = -(23.0 - corner_radius))
|> tangentialArcToRelative([corner_radius, -corner_radius], %)
|> tangentialArc(end = [corner_radius, -corner_radius])
|> xLine(length = 15.0 - (corner_radius * 2))
|> tangentialArcToRelative([corner_radius, -corner_radius], %)
|> tangentialArc(end = [corner_radius, -corner_radius])
|> yLine(length = -(length - 25.0 - 23.0 - (corner_radius * 2)))
|> tangentialArcToRelative([-corner_radius, -corner_radius], %)
|> tangentialArc(end = [-corner_radius, -corner_radius])
|> xLine(length = -(d_wrist_circumference[1] + d_wrist_circumference[2] + d_wrist_circumference[3] - hand_thickness - corner_radius))
|> tangentialArcToRelative([-corner_radius, -corner_radius], %)
|> tangentialArc(end = [-corner_radius, -corner_radius])
|> yLine(length = -(25.0 - corner_radius))
|> tangentialArcToRelative([corner_radius, -corner_radius], %)
|> tangentialArc(end = [corner_radius, -corner_radius])
|> close()
let inner = startSketchOn(XY)
|> startProfileAt([0, 0], %)
|> xLine(length = 1.0)
|> tangentialArcToRelative([corner_radius, corner_radius], %)
|> tangentialArc(end = [corner_radius, corner_radius])
|> yLine(length = 25.0 - (corner_radius * 2))
|> tangentialArcToRelative([-corner_radius, corner_radius], %)
|> tangentialArc(end = [-corner_radius, corner_radius])
|> xLine(length = -1.0)
|> tangentialArcToRelative([-corner_radius, -corner_radius], %)
|> tangentialArc(end = [-corner_radius, -corner_radius])
|> yLine(length = -(25.0 - (corner_radius * 2)))
|> tangentialArcToRelative([corner_radius, -corner_radius], %)
|> tangentialArc(end = [corner_radius, -corner_radius])
|> close()
let final = brace_base

View File

@ -11,17 +11,17 @@ const wallMountL = 8
const bracket = startSketchOn(XY)
|> startProfileAt([0, 0], %)
|> line(end = [0, wallMountL])
|> tangentialArc({
radius: filletR,
offset: 90
}, %)
|> tangentialArc(
radius = filletR,
angle = 90,
)
|> line(end = [-shelfMountL, 0])
|> line(end = [0, -thickness])
|> line(end = [shelfMountL, 0])
|> tangentialArc({
radius: filletR - thickness,
offset: -90
}, %)
|> tangentialArc(
radius = filletR - thickness,
angle = -90,
)
|> line(end = [0, -wallMountL])
|> close()
|> extrude(length = width)

View File

@ -763,9 +763,9 @@ const sketch010fl = startSketchOn(extrude001fl, 'START')
originStart[2] + .81 - (.438 / 2)
], %)
|> xLine(length = 0.75 - .438)
|> tangentialArcTo([-0.66 - originStart[0],originStart[2] + .81 + .438 / 2], %)
|> tangentialArc(endAbsolute = [-0.66 - originStart[0],originStart[2] + .81 + .438 / 2])
|> xLine(length = -0.75 + .438)
|> tangentialArcTo([profileStartX(%), profileStartY(%)], %)
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> patternLinear2d(
axis = [0, 1],
@ -781,12 +781,12 @@ const sketch011fl = startSketchOn(extrude001fl, 'START')
originStart[2] + railHeight * 1.75 / 2 + .438 / 2
], %)
|> xLine(length = 0.75 - .438)
|> tangentialArcTo([
-0.66 - originStart[0],originStart[2]+
railHeight * 1.75 / 2 - (.438 / 2)
], %)
|> tangentialArc(endAbsolute = [
-0.66 - originStart[0],
originStart[2] + railHeight * 1.75 / 2 - (.438 / 2)
])
|> xLine(length = -0.75 + .438)
|> tangentialArcTo([profileStartX(%), profileStartY(%)], %)
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
const extrude011fl = extrude(sketch011fl, length = -thickness)
@ -798,12 +798,12 @@ const sketch012fl = startSketchOn(extrude001fl, 'START')
railHeight * 1.75 - .81 + .438 / 2
], %)
|> xLine(length = 0.75 - .438)
|> tangentialArcTo([
|> tangentialArc(endAbsolute = [
-0.66 - originStart[0], originStart[2]+
railHeight * 1.75 - .81 - (.438 / 2)
], %)
])
|> xLine(length = -0.75 + .438)
|> tangentialArcTo([profileStartX(%), profileStartY(%)], %)
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> patternLinear2d(
axis = [0, -1],
@ -1006,12 +1006,12 @@ const sketch010fr = startSketchOn(extrude001fr, 'START')
originStart[2] + .81 - (.438 / 2)
], %)
|> xLine(length = 0.75 - .438)
|> tangentialArcTo([
|> tangentialArc(endAbsolute = [
-0.66 - originStart[0],
originStart[2] + .81 + .438 / 2
], %)
])
|> xLine(length = -0.75 + .438)
|> tangentialArcTo([profileStartX(%), profileStartY(%)], %)
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> patternLinear2d(
axis = [0, 1],
@ -1027,12 +1027,12 @@ const sketch011fr = startSketchOn(extrude001fr, 'START')
originStart[2] + railHeight * 1.75 / 2 + .438 / 2
], %)
|> xLine(length = 0.75 - .438)
|> tangentialArcTo([
|> tangentialArc(endAbsolute = [
-0.66 - originStart[0],
originStart[2] + railHeight * 1.75 / 2 - (.438 / 2)
], %)
])
|> xLine(length = -0.75 + .438)
|> tangentialArcTo([profileStartX(%), profileStartY(%)], %)
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
const extrude011fr = extrude(sketch011fr, length = -thickness)
@ -1044,12 +1044,12 @@ const sketch012fr = startSketchOn(extrude001fr, 'START')
originStart[2] + railHeight * 1.75 - .81 + .438 / 2
], %)
|> xLine(length = 0.75 - .438)
|> tangentialArcTo([
|> tangentialArc(endAbsolute = [
-0.66 - originStart[0],
originStart[2] + railHeight * 1.75 - .81 - (.438 / 2)
], %)
])
|> xLine(length = -0.75 + .438)
|> tangentialArcTo([profileStartX(%), profileStartY(%)], %)
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> patternLinear2d(
axis = [0, -1],
@ -1252,12 +1252,12 @@ const sketch010rr = startSketchOn(extrude001rr, 'START')
originStart[2] + .81 - (.438 / 2)
], %)
|> xLine(length = 0.75 - .438)
|> tangentialArcTo([
|> tangentialArc(endAbsolute = [
-0.66 - originStart[0]+1.5-serverDepth,
originStart[2] + .81 + .438 / 2
], %)
])
|> xLine(length = -0.75 + .438)
|> tangentialArcTo([profileStartX(%), profileStartY(%)], %)
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> patternLinear2d(
axis = [0, 1],
@ -1273,12 +1273,12 @@ const sketch011rr = startSketchOn(extrude001rr, 'START')
originStart[2] + railHeight * 1.75 / 2 + .438 / 2
], %)
|> xLine(length = 0.75 - .438)
|> tangentialArcTo([
|> tangentialArc(endAbsolute = [
-0.66 - originStart[0]+1.5-serverDepth,
originStart[2] + railHeight * 1.75 / 2 - (.438 / 2)
], %)
])
|> xLine(length = -0.75 + .438)
|> tangentialArcTo([profileStartX(%), profileStartY(%)], %)
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
const extrude011rr = extrude(sketch011rr, length = -thickness)
@ -1290,12 +1290,12 @@ const sketch012rr = startSketchOn(extrude001rr, 'START')
originStart[2] + railHeight * 1.75 - .81 + .438 / 2
], %)
|> xLine(length = 0.75 - .438)
|> tangentialArcTo([
|> tangentialArc(endAbsolute = [
-0.66 - originStart[0]+1.5-serverDepth,
originStart[2] + railHeight * 1.75 - .81 - (.438 / 2)
], %)
])
|> xLine(length = -0.75 + .438)
|> tangentialArcTo([profileStartX(%), profileStartY(%)], %)
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> patternLinear2d(
axis = [0, -1],
@ -1497,12 +1497,12 @@ const sketch010rl = startSketchOn(extrude001rl, 'START')
originStart[2] + .81 - (.438 / 2)
], %)
|> xLine(length = 0.75 - .438)
|> tangentialArcTo([
|> tangentialArc(endAbsolute = [
-0.66 - originStart[0] - serverDepth + 1.5,
originStart[2] + .81 + .438 / 2
], %)
])
|> xLine(length = -0.75 + .438)
|> tangentialArcTo([profileStartX(%), profileStartY(%)], %)
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> patternLinear2d(
axis = [0, 1],
@ -1518,12 +1518,12 @@ const sketch011rl = startSketchOn(extrude001rl, 'START')
originStart[2] + railHeight * 1.75 / 2 + .438 / 2
], %)
|> xLine(length = 0.75 - .438)
|> tangentialArcTo([
|> tangentialArc(endAbsolute = [
-0.66 - originStart[0] - serverDepth + 1.5,
originStart[2] + railHeight * 1.75 / 2 - (.438 / 2)
], %)
])
|> xLine(length = -0.75 + .438)
|> tangentialArcTo([profileStartX(%), profileStartY(%)], %)
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
const extrude011rl = extrude(sketch011rl, length = -thickness)
@ -1535,12 +1535,12 @@ const sketch012rl = startSketchOn(extrude001rl, 'START')
originStart[2] + railHeight * 1.75 - .81 + .438 / 2
], %)
|> xLine(length = 0.75 - .438)
|> tangentialArcTo([
|> tangentialArc(endAbsolute = [
-0.66 - originStart[0] - serverDepth + 1.5,
originStart[2] + railHeight * 1.75 - .81 - (.438 / 2)
], %)
])
|> xLine(length = -0.75 + .438)
|> tangentialArcTo([profileStartX(%), profileStartY(%)], %)
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> patternLinear2d(
axis = [0, -1],
@ -1593,15 +1593,15 @@ fn streamServer = (serverPos) => {
|> xLine(length = 0.2)
|> yLine(length = -0.36)
|> xLine(length = 0.5)
|> tangentialArcTo([
|> tangentialArc(endAbsolute = [
0.3,
17.15 + 4.114 + 1 + serverPos * 1.75 - 11.114
], %)
])
|> yLine(length = -1.77)
|> tangentialArcTo([
|> tangentialArc(endAbsolute = [
-0.13,
14.89 + 4.114 + 1 + serverPos * 1.75 - 11.114
], %)
])
|> xLine(length = -0.52)
|> yLine(length = -0.42)
|> line(end = [0.34, -0.15])
@ -1617,15 +1617,15 @@ fn streamServer = (serverPos) => {
|> xLine(length = 0.2)
|> yLine(length = -0.36)
|> xLine(length = 0.5)
|> tangentialArcTo([
|> tangentialArc(endAbsolute = [
0.3,
17.15 + 4.114 + 1 + serverPos * 1.75 - 11.114
], %)
])
|> yLine(length = -1.77)
|> tangentialArcTo([
|> tangentialArc(endAbsolute = [
-0.13,
14.89 + 4.114 + 1 + serverPos * 1.75 - 11.114
], %)
])
|> xLine(length = -0.52)
|> yLine(length = -0.42)
|> line(end = [0.34, -0.15])

View File

@ -676,12 +676,12 @@ const sketch010fl = startSketchOn(extrude001fl, 'START')
originStart[2] + .81 - (.438 / 2)
], %)
|> xLine(length = 0.75 - .438)
|> tangentialArcTo([
|> tangentialArc(endAbsolute = [
-0.66 - originStart[0],
originStart[2] + .81 + .438 / 2
], %)
])
|> xLine(length = -0.75 + .438)
|> tangentialArcTo([profileStartX(%), profileStartY(%)], %)
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> patternLinear2d(
axis = [0, 1],
@ -697,12 +697,12 @@ const sketch011fl = startSketchOn(extrude001fl, 'START')
originStart[2] + railHeight * 1.75 / 2 + .438 / 2
], %)
|> xLine(length = 0.75 - .438)
|> tangentialArcTo([
|> tangentialArc(endAbsolute = [
-0.66 - originStart[0],
originStart[2] + railHeight * 1.75 / 2 - (.438 / 2)
], %)
])
|> xLine(length = -0.75 + .438)
|> tangentialArcTo([profileStartX(%), profileStartY(%)], %)
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
const extrude011fl = extrude(sketch011fl, length = -thickness)
@ -714,12 +714,12 @@ const sketch012fl = startSketchOn(extrude001fl, 'START')
originStart[2] + railHeight * 1.75 - .81 + .438 / 2
], %)
|> xLine(length = 0.75 - .438)
|> tangentialArcTo([
|> tangentialArc(endAbsolute = [
-0.66 - originStart[0],
originStart[2] + railHeight * 1.75 - .81 - (.438 / 2)
], %)
])
|> xLine(length = -0.75 + .438)
|> tangentialArcTo([profileStartX(%), profileStartY(%)], %)
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> patternLinear2d(
axis = [0, -1],
@ -814,12 +814,12 @@ const sketch010fr = startSketchOn(extrude001fr, 'START')
originStart[2] + .81 - (.438 / 2)
], %)
|> xLine(length = 0.75 - .438)
|> tangentialArcTo([
|> tangentialArc(endAbsolute = [
-0.66 - originStart[0],
originStart[2] + .81 + .438 / 2
], %)
])
|> xLine(length = -0.75 + .438)
|> tangentialArcTo([profileStartX(%), profileStartY(%)], %)
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> patternLinear2d(
axis = [0, 1],
@ -835,12 +835,12 @@ const sketch011fr = startSketchOn(extrude001fr, 'START')
originStart[2] + railHeight * 1.75 / 2 + .438 / 2
], %)
|> xLine(length = 0.75 - .438)
|> tangentialArcTo([
|> tangentialArc(endAbsolute = [
-0.66 - originStart[0],
originStart[2] + railHeight * 1.75 / 2 - (.438 / 2)
], %)
])
|> xLine(length = -0.75 + .438)
|> tangentialArcTo([profileStartX(%), profileStartY(%)], %)
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
const extrude011fr = extrude(sketch011fr, length = -thickness)
@ -852,12 +852,12 @@ const sketch012fr = startSketchOn(extrude001fr, 'START')
originStart[2] + railHeight * 1.75 - .81 + .438 / 2
], %)
|> xLine(length = 0.75 - .438)
|> tangentialArcTo([
|> tangentialArc(endAbsolute = [
-0.66 - originStart[0],
originStart[2] + railHeight * 1.75 - .81 - (.438 / 2)
], %)
])
|> xLine(length = -0.75 + .438)
|> tangentialArcTo([profileStartX(%), profileStartY(%)], %)
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> patternLinear2d(
axis = [0, -1],
@ -952,12 +952,12 @@ const sketch010rr = startSketchOn(extrude001rr, 'START')
originStart[2] + .81 - (.438 / 2)
], %)
|> xLine(length = 0.75 - .438)
|> tangentialArcTo([
|> tangentialArc(endAbsolute = [
-0.66 - originStart[0] + 1.5 - serverDepth,
originStart[2] + .81 + .438 / 2
], %)
])
|> xLine(length = -0.75 + .438)
|> tangentialArcTo([profileStartX(%), profileStartY(%)], %)
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> patternLinear2d(
axis = [0, 1],
@ -973,12 +973,12 @@ const sketch011rr = startSketchOn(extrude001rr, 'START')
originStart[2] + railHeight * 1.75 / 2 + .438 / 2
], %)
|> xLine(length = 0.75 - .438)
|> tangentialArcTo([
|> tangentialArc(endAbsolute = [
-0.66 - originStart[0] + 1.5 - serverDepth,
originStart[2] + railHeight * 1.75 / 2 - (.438 / 2)
], %)
])
|> xLine(length = -0.75 + .438)
|> tangentialArcTo([profileStartX(%), profileStartY(%)], %)
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
const extrude011rr = extrude(sketch011rr, length = -thickness)
@ -990,12 +990,12 @@ const sketch012rr = startSketchOn(extrude001rr, 'START')
originStart[2] + railHeight * 1.75 - .81 + .438 / 2
], %)
|> xLine(length = 0.75 - .438)
|> tangentialArcTo([
|> tangentialArc(endAbsolute = [
-0.66 - originStart[0] + 1.5 - serverDepth,
originStart[2] + railHeight * 1.75 - .81 - (.438 / 2)
], %)
])
|> xLine(length = -0.75 + .438)
|> tangentialArcTo([profileStartX(%), profileStartY(%)], %)
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> patternLinear2d(
axis = [0, -1],
@ -1089,12 +1089,12 @@ const sketch010rl = startSketchOn(extrude001rl, 'START')
originStart[2] + .81 - (.438 / 2)
], %)
|> xLine(length = 0.75 - .438)
|> tangentialArcTo([
|> tangentialArc(endAbsolute = [
-0.66 - originStart[0] - serverDepth + 1.5,
originStart[2] + .81 + .438 / 2
], %)
])
|> xLine(length = -0.75 + .438)
|> tangentialArcTo([profileStartX(%), profileStartY(%)], %)
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> patternLinear2d(
axis = [0, 1],
@ -1110,12 +1110,12 @@ const sketch011rl = startSketchOn(extrude001rl, 'START')
originStart[2] + railHeight * 1.75 / 2 + .438 / 2
], %)
|> xLine(length = 0.75 - .438)
|> tangentialArcTo([
|> tangentialArc(endAbsolute = [
-0.66 - originStart[0] - serverDepth + 1.5,
originStart[2] + railHeight * 1.75 / 2 - (.438 / 2)
], %)
])
|> xLine(length = -0.75 + .438)
|> tangentialArcTo([profileStartX(%), profileStartY(%)], %)
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
const extrude011rl = extrude(sketch011rl, length = -thickness)
@ -1127,12 +1127,12 @@ const sketch012rl = startSketchOn(extrude001rl, 'START')
originStart[2] + railHeight * 1.75 - .81 + .438 / 2
], %)
|> xLine(length = 0.75 - .438)
|> tangentialArcTo([
|> tangentialArc(endAbsolute = [
-0.66 - originStart[0] - serverDepth + 1.5,
originStart[2] + railHeight * 1.75 - .81 - (.438 / 2)
], %)
])
|> xLine(length = -0.75 + .438)
|> tangentialArcTo([profileStartX(%), profileStartY(%)], %)
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> patternLinear2d(
axis = [0, -1],
@ -1184,15 +1184,15 @@ fn streamServer = (serverPos) => {
|> xLine(length = 0.2)
|> yLine(length = -0.36)
|> xLine(length = 0.5)
|> tangentialArcTo([
|> tangentialArc(endAbsolute = [
0.3,
17.15 + 4.114 + 1 + serverPos * 1.75 - 11.114
], %)
])
|> yLine(length = -1.77)
|> tangentialArcTo([
|> tangentialArc(endAbsolute = [
-0.13,
14.89 + 4.114 + 1 + serverPos * 1.75 - 11.114
], %)
])
|> xLine(length = -0.52)
|> yLine(length = -0.42)
|> line(end = [0.34, -0.15])
@ -1208,15 +1208,15 @@ fn streamServer = (serverPos) => {
|> xLine(length = 0.2)
|> yLine(length = -0.36)
|> xLine(length = 0.5)
|> tangentialArcTo([
|> tangentialArc(endAbsolute = [
0.3,
17.15 + 4.114 + 1 + serverPos * 1.75 - 11.114
], %)
])
|> yLine(length = -1.77)
|> tangentialArcTo([
|> tangentialArc(endAbsolute = [
-0.13,
14.89 + 4.114 + 1 + serverPos * 1.75 - 11.114
], %)
])
|> xLine(length = -0.52)
|> yLine(length = -0.42)
|> line(end = [0.34, -0.15])

View File

@ -10,16 +10,17 @@ startSketchOn(XY)
angle = angleStart,
length = .000001,
)
|> tangentialArc({
offset: angleOffset,
|> tangentialArc(
angle = angleOffset,
radius: r,
}, %, $arc1)
|> tangentialArc({
offset: angleOffset,
radius: 0.5*r,
}, %, $arc2)
|> tangentialArc({
offset: -angleOffset,
radius: 0.5*r,
}, %, $arc3)
tag = $arc1,
)
|> tangentialArc(
angle = angleOffset,
radius = 0.5*r,
tag = $arc2)
|> tangentialArc(
angle = -angleOffset,
radius = 0.5*r,
tag = $arc3)
|> xLine(endAbsolute = 1)

View File

@ -1,6 +1,6 @@
const boxSketch = startSketchOn(XY)
|> startProfileAt([0, 0], %)
|> line(end = [0, 10])
|> tangentialArc({radius: 5, offset: 90}, %)
|> tangentialArc(radius = 5, angle = 90)
|> line(end = [5, -15])
|> extrude(length = 10)

View File

@ -159,7 +159,7 @@ async fn kcl_test_basic_tangential_arc_with_point() {
let code = r#"boxSketch = startSketchOn(XY)
|> startProfileAt([0, 0], %)
|> line(end = [0, 10])
|> tangentialArcToRelative([-5, 5], %)
|> tangentialArc(end = [-5, 5])
|> line(end = [5, -15])
|> extrude(length = 10)
"#;
@ -173,7 +173,7 @@ async fn kcl_test_basic_tangential_arc_to() {
let code = r#"boxSketch = startSketchOn(XY)
|> startProfileAt([0, 0], %)
|> line(end = [0, 10])
|> tangentialArcTo([-5, 15], %)
|> tangentialArc(endAbsolute = [-5, 15])
|> line(end = [5, -15])
|> extrude(length = 10)
"#;
@ -224,14 +224,14 @@ wallMountL = 8
bracket = startSketchOn(XY)
|> startProfileAt([0, 0], %)
|> line(end = [0, wallMountL])
|> tangentialArc({ radius= filletR, offset: 90 }, %)
|> tangentialArc(radius = filletR, angle = 90 )
|> line(end = [-shelfMountL, 0])
|> line(end = [0, -thickness])
|> line(end = [shelfMountL, 0])
|> tangentialArc({
radius= filletR - thickness,
offset: -90
}, %)
|> tangentialArc(
radius = filletR - thickness,
angle = -90,
)
|> line(end = [0, -wallMountL])
|> close()
|> extrude(length = width)
@ -306,7 +306,7 @@ thing = other_circle([2, 2], 20)
#[tokio::test(flavor = "multi_thread")]
async fn kcl_test_rounded_with_holes() {
let code = r#"fn tarc = (to, sktch, tag?) => {
return tangentialArcTo(to, sktch, tag)
return tangentialArc(sktch, endAbsolute = to, tag = tag)
}
fn roundedRectangle = (pos, w, l, cornerRadius) => {
@ -705,7 +705,7 @@ async fn kcl_test_error_sketch_on_arc_face() {
let code = r#"fn cube = (pos, scale) => {
sg = startSketchOn(XY)
|> startProfileAt(pos, %)
|> tangentialArcToRelative([0, scale], %, $here)
|> tangentialArc(end = [0, scale], tag = $here)
|> line(end = [scale, 0])
|> line(end = [0, -scale])
@ -1342,7 +1342,7 @@ async fn kcl_test_error_empty_start_sketch_on_string() {
|> line(end = [190.03, -118.13])
|> line(end = [-33.38, -202.86])
|> line(end = [-315.86, -64.2])
|> tangentialArcTo([-147.66, 121.34], %)
|> tangentialArc(endAbsolute = [-147.66, 121.34])
|> close()
|> extrude(length = 100)
@ -1352,10 +1352,11 @@ secondSketch = startSketchOn(part001, '')
"#;
let result = execute_and_snapshot(code, None).await;
assert!(result.is_err());
let err = result.unwrap_err();
let err = err.as_kcl_error().unwrap();
assert_eq!(
result.err().unwrap().to_string(),
r#"semantic: KclErrorDetails { source_ranges: [SourceRange([297, 299, 0])], message: "Argument at index 1 was supposed to be type Option<FaceTag> but found string (text)" }"#
err.message(),
"Argument at index 1 was supposed to be type Option<FaceTag> but found string (text)"
);
}

View File

@ -444,12 +444,11 @@ impl FnData {
}
}
#[allow(clippy::literal_string_with_formatting_args)]
pub(super) fn to_autocomplete_snippet(&self) -> String {
if self.name == "loft" {
return "loft([${0:sketch000}, ${1:sketch001}])${}".to_owned();
return "loft([${0:sketch000}, ${1:sketch001}])".to_owned();
} else if self.name == "hole" {
return "hole(${0:holeSketch}, ${1:%})${}".to_owned();
return "hole(${0:holeSketch}, ${1:%})".to_owned();
}
let mut args = Vec::new();
let mut index = 0;
@ -459,9 +458,7 @@ impl FnData {
args.push(arg_str);
}
}
// We end with ${} so you can jump to the end of the snippet.
// After the last argument.
format!("{}({})${{}}", self.preferred_name, args.join(", "))
format!("{}({})", self.preferred_name, args.join(", "))
}
fn to_signature_help(&self) -> SignatureHelp {

View File

@ -498,12 +498,17 @@ pub trait StdLibFn: std::fmt::Debug + Send + Sync {
})
}
#[allow(clippy::literal_string_with_formatting_args)]
fn to_autocomplete_snippet(&self) -> Result<String> {
if self.name() == "loft" {
return Ok("loft([${0:sketch000}, ${1:sketch001}])${}".to_string());
return Ok("loft([${0:sketch000}, ${1:sketch001}])".to_string());
} else if self.name() == "union" {
return Ok("union([${0:extrude001}, ${1:extrude002}])".to_string());
} else if self.name() == "subtract" {
return Ok("subtract([${0:extrude001}], tools = [${1:extrude002}])".to_string());
} else if self.name() == "intersect" {
return Ok("intersect([${0:extrude001}, ${1:extrude002}])".to_string());
} else if self.name() == "hole" {
return Ok("hole(${0:holeSketch}, ${1:%})${}".to_string());
return Ok("hole(${0:holeSketch}, ${1:%})".to_string());
}
let in_keyword_fn = self.keyword_arguments();
let mut args = Vec::new();
@ -514,9 +519,7 @@ pub trait StdLibFn: std::fmt::Debug + Send + Sync {
args.push(arg_str);
}
}
// We end with ${} so you can jump to the end of the snippet.
// After the last argument.
Ok(format!("{}({})${{}}", self.name(), args.join(", ")))
Ok(format!("{}({})", self.name(), args.join(", ")))
}
fn to_signature_help(&self) -> SignatureHelp {
@ -890,29 +893,26 @@ mod tests {
}
#[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_line() {
let line_fn: Box<dyn StdLibFn> = Box::new(crate::std::sketch::Line);
let snippet = line_fn.to_autocomplete_snippet().unwrap();
assert_eq!(snippet, r#"line(${0:%}, end = [${1:3.14}, ${2:3.14}])${}"#);
assert_eq!(snippet, r#"line(${0:%}, end = [${1:3.14}, ${2:3.14}])"#);
}
#[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_extrude() {
let extrude_fn: Box<dyn StdLibFn> = Box::new(crate::std::extrude::Extrude);
let snippet = extrude_fn.to_autocomplete_snippet().unwrap();
assert_eq!(snippet, r#"extrude(${0:%}, length = ${1:3.14})${}"#);
assert_eq!(snippet, r#"extrude(${0:%}, length = ${1:3.14})"#);
}
#[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_fillet() {
let fillet_fn: Box<dyn StdLibFn> = Box::new(crate::std::fillet::Fillet);
let snippet = fillet_fn.to_autocomplete_snippet().unwrap();
assert_eq!(
snippet,
r#"fillet(${0:%}, radius = ${1:3.14}, tags = [${2:"tag_or_edge_fn"}])${}"#
r#"fillet(${0:%}, radius = ${1:3.14}, tags = [${2:"tag_or_edge_fn"}])"#
);
}
@ -920,18 +920,17 @@ mod tests {
fn get_autocomplete_snippet_start_sketch_on() {
let start_sketch_on_fn: Box<dyn StdLibFn> = Box::new(crate::std::sketch::StartSketchOn);
let snippet = start_sketch_on_fn.to_autocomplete_snippet().unwrap();
assert_eq!(snippet, r#"startSketchOn(${0:"XY"})${}"#);
assert_eq!(snippet, r#"startSketchOn(${0:"XY"})"#);
}
#[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_pattern_circular_3d() {
// We test this one specifically because it has ints and floats and strings.
let pattern_fn: Box<dyn StdLibFn> = Box::new(crate::std::patterns::PatternCircular3D);
let snippet = pattern_fn.to_autocomplete_snippet().unwrap();
assert_eq!(
snippet,
r#"patternCircular3d(${0:%}, instances = ${1:10}, axis = [${2:3.14}, ${3:3.14}, ${4:3.14}], center = [${5:3.14}, ${6:3.14}, ${7:3.14}], arcDegrees = ${8:3.14}, rotateDuplicates = ${9:false})${}"#
r#"patternCircular3d(${0:%}, instances = ${1:10}, axis = [${2:3.14}, ${3:3.14}, ${4:3.14}], center = [${5:3.14}, ${6:3.14}, ${7:3.14}], arcDegrees = ${8:3.14}, rotateDuplicates = ${9:false})"#
);
}
@ -942,11 +941,10 @@ mod tests {
panic!();
};
let snippet = revolve_fn.to_autocomplete_snippet();
assert_eq!(snippet, r#"revolve(axis = ${0:X})${}"#);
assert_eq!(snippet, r#"revolve(axis = ${0:X})"#);
}
#[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_circle() {
let data = kcl_doc::walk_prelude();
let DocData::Fn(circle_fn) = data.into_iter().find(|d| d.name() == "circle").unwrap() else {
@ -955,12 +953,11 @@ mod tests {
let snippet = circle_fn.to_autocomplete_snippet();
assert_eq!(
snippet,
r#"circle(center = [${0:3.14}, ${1:3.14}], radius = ${2:3.14})${}"#
r#"circle(center = [${0:3.14}, ${1:3.14}], radius = ${2:3.14})"#
);
}
#[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_arc() {
let arc_fn: Box<dyn StdLibFn> = Box::new(crate::std::sketch::Arc);
let snippet = arc_fn.to_autocomplete_snippet().unwrap();
@ -970,7 +967,7 @@ mod tests {
angleStart = ${0:3.14},
angleEnd = ${1:3.14},
radius = ${2:3.14},
}, ${3:%})${}"#
}, ${3:%})"#
);
}
@ -978,17 +975,16 @@ mod tests {
fn get_autocomplete_snippet_map() {
let map_fn: Box<dyn StdLibFn> = Box::new(crate::std::array::Map);
let snippet = map_fn.to_autocomplete_snippet().unwrap();
assert_eq!(snippet, r#"map(${0:[0..9]})${}"#);
assert_eq!(snippet, r#"map(${0:[0..9]})"#);
}
#[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_pattern_linear_2d() {
let pattern_fn: Box<dyn StdLibFn> = Box::new(crate::std::patterns::PatternLinear2D);
let snippet = pattern_fn.to_autocomplete_snippet().unwrap();
assert_eq!(
snippet,
r#"patternLinear2d(${0:%}, instances = ${1:10}, distance = ${2:3.14}, axis = [${3:3.14}, ${4:3.14}])${}"#
r#"patternLinear2d(${0:%}, instances = ${1:10}, distance = ${2:3.14}, axis = [${3:3.14}, ${4:3.14}])"#
);
}
@ -998,36 +994,32 @@ mod tests {
let snippet = appearance_fn.to_autocomplete_snippet().unwrap();
assert_eq!(
snippet,
r#"appearance(${0:%}, color = ${1:"#.to_owned() + "\"#" + r#"ff0000"})${}"#
r#"appearance(${0:%}, color = ${1:"#.to_owned() + "\"#" + r#"ff0000"})"#
);
}
#[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_loft() {
let loft_fn: Box<dyn StdLibFn> = Box::new(crate::std::loft::Loft);
let snippet = loft_fn.to_autocomplete_snippet().unwrap();
assert_eq!(snippet, r#"loft([${0:sketch000}, ${1:sketch001}])${}"#);
assert_eq!(snippet, r#"loft([${0:sketch000}, ${1:sketch001}])"#);
}
#[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_sweep() {
let sweep_fn: Box<dyn StdLibFn> = Box::new(crate::std::sweep::Sweep);
let snippet = sweep_fn.to_autocomplete_snippet().unwrap();
assert_eq!(snippet, r#"sweep(${0:%}, path = ${1:sketch000})${}"#);
assert_eq!(snippet, r#"sweep(${0:%}, path = ${1:sketch000})"#);
}
#[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_hole() {
let hole_fn: Box<dyn StdLibFn> = Box::new(crate::std::sketch::Hole);
let snippet = hole_fn.to_autocomplete_snippet().unwrap();
assert_eq!(snippet, r#"hole(${0:holeSketch}, ${1:%})${}"#);
assert_eq!(snippet, r#"hole(${0:holeSketch}, ${1:%})"#);
}
#[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_helix() {
let data = kcl_doc::walk_prelude();
let DocData::Fn(helix_fn) = data.into_iter().find(|d| d.name() == "helix").unwrap() else {
@ -1036,36 +1028,32 @@ mod tests {
let snippet = helix_fn.to_autocomplete_snippet();
assert_eq!(
snippet,
r#"helix(revolutions = ${0:3.14}, angleStart = ${1:3.14}, radius = ${2:3.14}, axis = ${3:X}, length = ${4:3.14})${}"#
r#"helix(revolutions = ${0:3.14}, angleStart = ${1:3.14}, radius = ${2:3.14}, axis = ${3:X}, length = ${4:3.14})"#
);
}
#[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_union() {
let union_fn: Box<dyn StdLibFn> = Box::new(crate::std::csg::Union);
let snippet = union_fn.to_autocomplete_snippet().unwrap();
assert_eq!(snippet, r#"union(${0:%})${}"#);
assert_eq!(snippet, r#"union([${0:extrude001}, ${1:extrude002}])"#);
}
#[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_subtract() {
let subtract_fn: Box<dyn StdLibFn> = Box::new(crate::std::csg::Subtract);
let snippet = subtract_fn.to_autocomplete_snippet().unwrap();
assert_eq!(snippet, r#"subtract(${0:%}, tools = ${1:%})${}"#);
assert_eq!(snippet, r#"subtract([${0:extrude001}], tools = [${1:extrude002}])"#);
}
#[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_intersect() {
let intersect_fn: Box<dyn StdLibFn> = Box::new(crate::std::csg::Intersect);
let snippet = intersect_fn.to_autocomplete_snippet().unwrap();
assert_eq!(snippet, r#"intersect(${0:%})${}"#);
assert_eq!(snippet, r#"intersect([${0:extrude001}, ${1:extrude002}])"#);
}
#[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_get_common_edge() {
let get_common_edge_fn: Box<dyn StdLibFn> = Box::new(crate::std::edge::GetCommonEdge);
let snippet = get_common_edge_fn.to_autocomplete_snippet().unwrap();
@ -1073,40 +1061,34 @@ mod tests {
snippet,
r#"getCommonEdge(faces = [{
value = ${0:"string"},
}])${}"#
}])"#
);
}
#[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_scale() {
let scale_fn: Box<dyn StdLibFn> = Box::new(crate::std::transform::Scale);
let snippet = scale_fn.to_autocomplete_snippet().unwrap();
assert_eq!(
snippet,
r#"scale(${0:%}, x = ${1:3.14}, y = ${2:3.14}, z = ${3:3.14})${}"#
);
assert_eq!(snippet, r#"scale(${0:%}, x = ${1:3.14}, y = ${2:3.14}, z = ${3:3.14})"#);
}
#[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_translate() {
let translate_fn: Box<dyn StdLibFn> = Box::new(crate::std::transform::Translate);
let snippet = translate_fn.to_autocomplete_snippet().unwrap();
assert_eq!(
snippet,
r#"translate(${0:%}, x = ${1:3.14}, y = ${2:3.14}, z = ${3:3.14})${}"#
r#"translate(${0:%}, x = ${1:3.14}, y = ${2:3.14}, z = ${3:3.14})"#
);
}
#[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_rotate() {
let rotate_fn: Box<dyn StdLibFn> = Box::new(crate::std::transform::Rotate);
let snippet = rotate_fn.to_autocomplete_snippet().unwrap();
assert_eq!(
snippet,
r#"rotate(${0:%}, roll = ${1:3.14}, pitch = ${2:3.14}, yaw = ${3:3.14})${}"#
r#"rotate(${0:%}, roll = ${1:3.14}, pitch = ${2:3.14}, yaw = ${3:3.14})"#
);
}

View File

@ -20,7 +20,6 @@ pub(crate) const SETTINGS_UNIT_ANGLE: &str = "defaultAngleUnit";
pub(super) const NO_PRELUDE: &str = "no_std";
pub(super) const IMPORT_FORMAT: &str = "format";
pub(super) const IMPORT_FORMAT_VALUES: [&str; 9] = ["fbx", "gltf", "glb", "obj", "ply", "sldprt", "stp", "step", "stl"];
pub(super) const IMPORT_COORDS: &str = "coords";
pub(super) const IMPORT_COORDS_VALUES: [(&str, &System); 3] =
[("zoo", KITTYCAD), ("opengl", OPENGL), ("vulkan", VULKAN)];

View File

@ -115,6 +115,30 @@ impl CodeRef {
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[ts(export_to = "Artifact.ts")]
#[serde(rename_all = "camelCase")]
pub struct CompositeSolid {
pub id: ArtifactId,
pub sub_type: CompositeSolidSubType,
/// Constituent solids of the composite solid.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub solid_ids: Vec<ArtifactId>,
/// Tool solids used for asymmetric operations like subtract.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tool_ids: Vec<ArtifactId>,
pub code_ref: CodeRef,
}
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
#[ts(export_to = "Artifact.ts")]
#[serde(rename_all = "camelCase")]
pub enum CompositeSolidSubType {
Intersect,
Subtract,
Union,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[ts(export_to = "Artifact.ts")]
#[serde(rename_all = "camelCase")]
@ -318,6 +342,7 @@ pub struct Helix {
#[ts(export_to = "Artifact.ts")]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum Artifact {
CompositeSolid(CompositeSolid),
Plane(Plane),
Path(Path),
Segment(Segment),
@ -336,6 +361,7 @@ pub enum Artifact {
impl Artifact {
pub(crate) fn id(&self) -> ArtifactId {
match self {
Artifact::CompositeSolid(a) => a.id,
Artifact::Plane(a) => a.id,
Artifact::Path(a) => a.id,
Artifact::Segment(a) => a.id,
@ -355,6 +381,7 @@ impl Artifact {
#[expect(dead_code)]
pub(crate) fn code_ref(&self) -> Option<&CodeRef> {
match self {
Artifact::CompositeSolid(a) => Some(&a.code_ref),
Artifact::Plane(a) => Some(&a.code_ref),
Artifact::Path(a) => Some(&a.code_ref),
Artifact::Segment(a) => Some(&a.code_ref),
@ -375,6 +402,7 @@ impl Artifact {
/// type, return the new artifact which should be used as a replacement.
fn merge(&mut self, new: Artifact) -> Option<Artifact> {
match self {
Artifact::CompositeSolid(a) => a.merge(new),
Artifact::Plane(a) => a.merge(new),
Artifact::Path(a) => a.merge(new),
Artifact::Segment(a) => a.merge(new),
@ -392,6 +420,18 @@ impl Artifact {
}
}
impl CompositeSolid {
fn merge(&mut self, new: Artifact) -> Option<Artifact> {
let Artifact::CompositeSolid(new) = new else {
return Some(new);
};
merge_ids(&mut self.solid_ids, new.solid_ids);
merge_ids(&mut self.tool_ids, new.tool_ids);
None
}
}
impl Plane {
fn merge(&mut self, new: Artifact) -> Option<Artifact> {
let Artifact::Plane(new) = new else {
@ -1047,6 +1087,85 @@ fn artifacts_to_update(
// the helix here, but it's not useful right now.
return Ok(return_arr);
}
ModelingCmd::BooleanIntersection(_) | ModelingCmd::BooleanSubtract(_) | ModelingCmd::BooleanUnion(_) => {
let (sub_type, solid_ids, tool_ids) = match cmd {
ModelingCmd::BooleanIntersection(intersection) => {
let solid_ids = intersection
.solid_ids
.iter()
.copied()
.map(ArtifactId::new)
.collect::<Vec<_>>();
(CompositeSolidSubType::Intersect, solid_ids, Vec::new())
}
ModelingCmd::BooleanSubtract(subtract) => {
let solid_ids = subtract
.target_ids
.iter()
.copied()
.map(ArtifactId::new)
.collect::<Vec<_>>();
let tool_ids = subtract
.tool_ids
.iter()
.copied()
.map(ArtifactId::new)
.collect::<Vec<_>>();
(CompositeSolidSubType::Subtract, solid_ids, tool_ids)
}
ModelingCmd::BooleanUnion(union) => {
let solid_ids = union.solid_ids.iter().copied().map(ArtifactId::new).collect::<Vec<_>>();
(CompositeSolidSubType::Union, solid_ids, Vec::new())
}
_ => unreachable!(),
};
let mut new_solid_ids = vec![id];
match response {
OkModelingCmdResponse::BooleanIntersection(intersection) => intersection
.extra_solid_ids
.iter()
.copied()
.map(ArtifactId::new)
.for_each(|id| new_solid_ids.push(id)),
OkModelingCmdResponse::BooleanSubtract(subtract) => subtract
.extra_solid_ids
.iter()
.copied()
.map(ArtifactId::new)
.for_each(|id| new_solid_ids.push(id)),
OkModelingCmdResponse::BooleanUnion(union) => union
.extra_solid_ids
.iter()
.copied()
.map(ArtifactId::new)
.for_each(|id| new_solid_ids.push(id)),
_ => {}
}
let return_arr = new_solid_ids
.into_iter()
// Extra solid IDs may include the command's ID. Make sure we
// don't create a duplicate.
.filter(|solid_id| *solid_id != id)
.map(|solid_id| {
Artifact::CompositeSolid(CompositeSolid {
id: solid_id,
sub_type,
solid_ids: solid_ids.clone(),
tool_ids: tool_ids.clone(),
code_ref: CodeRef {
range,
path_to_node: path_to_node.clone(),
},
})
})
.collect::<Vec<_>>();
// TODO: Should we add the reverse graph edges?
return Ok(return_arr);
}
_ => {}
}

View File

@ -67,6 +67,11 @@ impl Artifact {
/// the graph. This should be disjoint with `child_ids`.
pub(crate) fn back_edges(&self) -> Vec<ArtifactId> {
match self {
Artifact::CompositeSolid(a) => {
let mut ids = a.solid_ids.clone();
ids.extend(a.tool_ids.iter());
ids
}
Artifact::Plane(_) => Vec::new(),
Artifact::Path(a) => vec![a.plane_id],
Artifact::Segment(a) => vec![a.path_id],
@ -87,6 +92,11 @@ impl Artifact {
/// the graph.
pub(crate) fn child_ids(&self) -> Vec<ArtifactId> {
match self {
Artifact::CompositeSolid(_) => {
// Note: Don't include these since they're parents: solid_ids,
// tool_ids.
Vec::new()
}
Artifact::Plane(a) => a.path_ids.clone(),
Artifact::Path(a) => {
// Note: Don't include these since they're parents: plane_id.
@ -213,6 +223,7 @@ impl ArtifactGraph {
let id = artifact.id();
let grouped = match artifact {
Artifact::CompositeSolid(_) => false,
Artifact::Plane(_) => false,
Artifact::Path(_) => {
groups.entry(id).or_insert_with(Vec::new).push(id);
@ -278,6 +289,15 @@ impl ArtifactGraph {
}
match artifact {
Artifact::CompositeSolid(composite_solid) => {
writeln!(
output,
"{prefix}{}[\"CompositeSolid {:?}<br>{:?}\"]",
id,
composite_solid.sub_type,
code_ref_display(&composite_solid.code_ref)
)?;
}
Artifact::Plane(plane) => {
writeln!(
output,

View File

@ -939,25 +939,40 @@ impl Node<BinaryExpression> {
if self.operator == BinaryOperator::Add || self.operator == BinaryOperator::Or {
if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
let args = crate::std::Args::new(Default::default(), self.into(), ctx.clone(), None);
let result =
crate::std::csg::inner_union(vec![*left.clone(), *right.clone()], exec_state, args).await?;
let result = crate::std::csg::inner_union(
vec![*left.clone(), *right.clone()],
Default::default(),
exec_state,
args,
)
.await?;
return Ok(result.into());
}
} else if self.operator == BinaryOperator::Sub {
// Check if we have solids.
if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
let args = crate::std::Args::new(Default::default(), self.into(), ctx.clone(), None);
let result =
crate::std::csg::inner_subtract(vec![*left.clone()], vec![*right.clone()], exec_state, args)
.await?;
let result = crate::std::csg::inner_subtract(
vec![*left.clone()],
vec![*right.clone()],
Default::default(),
exec_state,
args,
)
.await?;
return Ok(result.into());
}
} else if self.operator == BinaryOperator::And {
// Check if we have solids.
if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
let args = crate::std::Args::new(Default::default(), self.into(), ctx.clone(), None);
let result =
crate::std::csg::inner_intersect(vec![*left.clone(), *right.clone()], exec_state, args).await?;
let result = crate::std::csg::inner_intersect(
vec![*left.clone(), *right.clone()],
Default::default(),
exec_state,
args,
)
.await?;
return Ok(result.into());
}
}

View File

@ -173,7 +173,7 @@ pub(super) fn format_from_annotations(
KclError::Semantic(KclErrorDetails {
message: format!(
"Unknown format for import, expected one of: {}",
annotations::IMPORT_FORMAT_VALUES.join(", ")
crate::IMPORT_FILE_EXTENSIONS.join(", ")
),
source_ranges: vec![p.as_source_range()],
})

View File

@ -11,9 +11,7 @@ pub use cache::{bust_cache, clear_mem_cache};
pub use cad_op::Operation;
pub use geometry::*;
pub use id_generator::IdGenerator;
pub(crate) use import::{
import_foreign, send_to_engine as send_import_to_engine, PreImportedGeometry, ZOO_COORD_SYSTEM,
};
pub(crate) use import::PreImportedGeometry;
use indexmap::IndexMap;
pub use kcl_value::{KclObjectFields, KclValue};
use kcmc::{

View File

@ -7,6 +7,7 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use super::types::NumericType;
use crate::{
errors::{KclError, KclErrorDetails, Severity},
execution::{
@ -22,8 +23,6 @@ use crate::{
CompilationError,
};
use super::types::NumericType;
/// State for executing a program.
#[derive(Debug, Clone)]
pub struct ExecState {

View File

@ -807,6 +807,7 @@ pub enum UnitAngle {
impl UnitAngle {
fn adjust_to(self, value: f64, to: UnitAngle) -> f64 {
use std::f64::consts::PI;
use UnitAngle::*;
if !*CHECK_NUMERIC_TYPES {
@ -1171,9 +1172,8 @@ impl KclValue {
#[cfg(test)]
mod test {
use crate::execution::{parse_execute, ExecTestResults};
use super::*;
use crate::execution::{parse_execute, ExecTestResults};
fn values(exec_state: &mut ExecState) -> Vec<KclValue> {
vec![

View File

@ -131,11 +131,36 @@ pub mod pretty {
pub use crate::{parsing::token::NumericSuffix, unparser::format_number};
}
#[cfg(feature = "cli")]
use clap::ValueEnum;
use serde::{Deserialize, Serialize};
#[allow(unused_imports)]
use crate::log::{log, logln};
lazy_static::lazy_static! {
pub static ref IMPORT_FILE_EXTENSIONS: Vec<String> = {
let mut import_file_extensions = vec!["stp".to_string(), "glb".to_string(), "fbxb".to_string()];
#[cfg(feature = "cli")]
let named_extensions = kittycad::types::FileImportFormat::value_variants()
.iter()
.map(|x| format!("{}", x))
.collect::<Vec<String>>();
#[cfg(not(feature = "cli"))]
let named_extensions = vec![]; // We don't really need this outside of the CLI.
// Add all the default import formats.
import_file_extensions.extend_from_slice(&named_extensions);
import_file_extensions
};
pub static ref RELEVANT_FILE_EXTENSIONS: Vec<String> = {
let mut relevant_extensions = IMPORT_FILE_EXTENSIONS.clone();
relevant_extensions.push("kcl".to_string());
relevant_extensions
};
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Program {
#[serde(flatten)]

View File

@ -3418,3 +3418,148 @@ async fn kcl_test_kcl_lsp_multi_file_error() {
server.executor_ctx().await.clone().unwrap().close().await;
}
#[tokio::test(flavor = "multi_thread")]
async fn test_kcl_lsp_on_hover_untitled_file_scheme() {
let server = kcl_lsp_server(true).await.unwrap();
// Send open file.
server
.did_open(tower_lsp::lsp_types::DidOpenTextDocumentParams {
text_document: tower_lsp::lsp_types::TextDocumentItem {
uri: "untitled:Untitled-1".try_into().unwrap(),
language_id: "kcl".to_string(),
version: 1,
text: r#"startSketchOn(XY)
foo = 42
foo
fn bar(x: string): string {
return x
}
bar("an arg")
startSketchOn(XY)
|> startProfileAt([0, 0], %)
|> line(end = [10, 0])
|> line(end = [0, 10])
"#
.to_string(),
},
})
.await;
// Std lib call
let hover = server
.hover(tower_lsp::lsp_types::HoverParams {
text_document_position_params: tower_lsp::lsp_types::TextDocumentPositionParams {
text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
uri: "untitled:Untitled-1".try_into().unwrap(),
},
position: tower_lsp::lsp_types::Position { line: 0, character: 2 },
},
work_done_progress_params: Default::default(),
})
.await
.unwrap();
match hover.unwrap().contents {
tower_lsp::lsp_types::HoverContents::Markup(tower_lsp::lsp_types::MarkupContent { value, .. }) => {
assert!(value.contains("startSketchOn"));
assert!(value.contains(": SketchSurface"));
assert!(value.contains("Start a new 2-dimensional sketch on a specific"));
}
_ => unreachable!(),
}
// Variable use
let hover = server
.hover(tower_lsp::lsp_types::HoverParams {
text_document_position_params: tower_lsp::lsp_types::TextDocumentPositionParams {
text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
uri: "untitled:Untitled-1".try_into().unwrap(),
},
position: tower_lsp::lsp_types::Position { line: 2, character: 1 },
},
work_done_progress_params: Default::default(),
})
.await
.unwrap();
match hover.unwrap().contents {
tower_lsp::lsp_types::HoverContents::Markup(tower_lsp::lsp_types::MarkupContent { value, .. }) => {
assert!(value.contains("foo: number = 42"));
}
_ => unreachable!(),
}
// User-defined function call.
let hover = server
.hover(tower_lsp::lsp_types::HoverParams {
text_document_position_params: tower_lsp::lsp_types::TextDocumentPositionParams {
text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
uri: "untitled:Untitled-1".try_into().unwrap(),
},
position: tower_lsp::lsp_types::Position { line: 8, character: 1 },
},
work_done_progress_params: Default::default(),
})
.await
.unwrap();
match hover.unwrap().contents {
tower_lsp::lsp_types::HoverContents::Markup(tower_lsp::lsp_types::MarkupContent { value, .. }) => {
assert!(value.contains("bar(x: string): string"));
}
_ => unreachable!(),
}
// Variable inside a function
let hover = server
.hover(tower_lsp::lsp_types::HoverParams {
text_document_position_params: tower_lsp::lsp_types::TextDocumentPositionParams {
text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
uri: "untitled:Untitled-1".try_into().unwrap(),
},
position: tower_lsp::lsp_types::Position { line: 5, character: 9 },
},
work_done_progress_params: Default::default(),
})
.await
.unwrap();
match hover.unwrap().contents {
tower_lsp::lsp_types::HoverContents::Markup(tower_lsp::lsp_types::MarkupContent { value, .. }) => {
assert!(value.contains("x: string"));
}
_ => unreachable!(),
}
// std function KwArg
let hover = server
.hover(tower_lsp::lsp_types::HoverParams {
text_document_position_params: tower_lsp::lsp_types::TextDocumentPositionParams {
text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
uri: "untitled:Untitled-1".try_into().unwrap(),
},
position: tower_lsp::lsp_types::Position {
line: 12,
character: 11,
},
},
work_done_progress_params: Default::default(),
})
.await
.unwrap();
match hover.unwrap().contents {
tower_lsp::lsp_types::HoverContents::Markup(tower_lsp::lsp_types::MarkupContent { value, .. }) => {
assert!(value.contains("end?: [number]"));
assert!(value.contains("How far away (along the X and Y axes) should this line go?"));
}
_ => unreachable!(),
}
server.executor_ctx().await.clone().unwrap().close().await;
}

View File

@ -35,7 +35,7 @@ use crate::{
token::{Token, TokenSlice, TokenType},
PIPE_OPERATOR, PIPE_SUBSTITUTION_OPERATOR,
},
SourceRange,
SourceRange, IMPORT_FILE_EXTENSIONS,
};
thread_local! {
@ -1803,11 +1803,6 @@ fn import_stmt(i: &mut TokenSlice) -> PResult<BoxNode<ImportStatement>> {
end = alias.end;
*selector_alias = Some(alias);
}
ParseContext::warn(CompilationError::err(
SourceRange::new(start, path.end, path.module_id),
"Importing a whole module is experimental, likely to be buggy, and likely to change",
));
}
let path_string = match path.inner.value {
@ -1843,8 +1838,6 @@ fn import_stmt(i: &mut TokenSlice) -> PResult<BoxNode<ImportStatement>> {
))
}
const FOREIGN_IMPORT_EXTENSIONS: [&str; 8] = ["fbx", "gltf", "glb", "obj", "ply", "sldprt", "step", "stl"];
/// Validates the path string in an `import` statement.
///
/// `var_name` is `true` if the path will be used as a variable name.
@ -1909,12 +1902,11 @@ fn validate_path_string(path_string: String, var_name: bool, path_range: SourceR
ImportPath::Std { path: segments }
} else if path_string.contains('.') {
// TODO should allow other extensions if there is a format attribute.
let extn = &path_string[path_string.rfind('.').unwrap() + 1..];
if !FOREIGN_IMPORT_EXTENSIONS.contains(&extn) {
let extn = std::path::Path::new(&path_string).extension().unwrap_or_default();
if !IMPORT_FILE_EXTENSIONS.contains(&extn.to_string_lossy().to_string()) {
ParseContext::warn(CompilationError::err(
path_range,
format!("unsupported import path format. KCL files can be imported from the current project, CAD files with the following formats are supported: {}", FOREIGN_IMPORT_EXTENSIONS.join(", ")),
format!("unsupported import path format. KCL files can be imported from the current project, CAD files with the following formats are supported: {}", IMPORT_FILE_EXTENSIONS.join(", ")),
))
}
ImportPath::Foreign { path: path_string }
@ -1922,7 +1914,7 @@ fn validate_path_string(path_string: String, var_name: bool, path_range: SourceR
return Err(ErrMode::Cut(
CompilationError::fatal(
path_range,
format!("unsupported import path format. KCL files can be imported from the current project, CAD files with the following formats are supported: {}", FOREIGN_IMPORT_EXTENSIONS.join(", ")),
format!("unsupported import path format. KCL files can be imported from the current project, CAD files with the following formats are supported: {}", IMPORT_FILE_EXTENSIONS.join(", ")),
)
.into(),
));
@ -4498,21 +4490,9 @@ export fn cos(num: number(rad)): number(_) {}"#;
#[test]
fn warn_import() {
let some_program_string = r#"import "foo.kcl""#;
let (_, errs) = assert_no_err(some_program_string);
assert_eq!(errs.len(), 1, "{errs:#?}");
let some_program_string = r#"import "foo.obj""#;
let (_, errs) = assert_no_err(some_program_string);
assert_eq!(errs.len(), 1, "{errs:#?}");
let some_program_string = r#"import "foo.sldprt""#;
let (_, errs) = assert_no_err(some_program_string);
assert_eq!(errs.len(), 1, "{errs:#?}");
let some_program_string = r#"import "foo.bad""#;
let (_, errs) = assert_no_err(some_program_string);
assert_eq!(errs.len(), 2, "{errs:#?}");
assert_eq!(errs.len(), 1, "{errs:#?}");
}
#[test]

View File

@ -241,15 +241,9 @@ pub async fn appearance(exec_state: &mut ExecState, args: Args) -> Result<KclVal
/// sweepPath = startSketchOn(XZ)
/// |> startProfileAt([0.05, 0.05], %)
/// |> line(end = [0, 7])
/// |> tangentialArc({
/// offset: 90,
/// radius: 5
/// }, %)
/// |> tangentialArc(angle = 90, radius = 5)
/// |> line(end = [-3, 0])
/// |> tangentialArc({
/// offset: -90,
/// radius: 5
/// }, %)
/// |> tangentialArc(angle = -90, radius = 5)
/// |> line(end = [0, 7])
///
/// pipeHole = startSketchOn(XY)

View File

@ -151,6 +151,10 @@ impl Args {
let Some(arg) = self.kw_args.labeled.get(label) else {
return Ok(None);
};
if let KclValue::KclNone { .. } = arg.value {
// It is set, but it's an optional parameter that wasn't provided.
return Ok(None);
}
T::from_kcl_val(&arg.value).map(Some).ok_or_else(|| {
KclError::Type(KclErrorDetails {
@ -664,10 +668,6 @@ impl Args {
FromArgs::from_args(self, 0)
}
pub(crate) fn get_import_data(&self) -> Result<(String, Option<crate::std::import::ImportFormat>), KclError> {
FromArgs::from_args(self, 0)
}
pub(crate) fn get_sketch_data_and_optional_tag(
&self,
) -> Result<(super::sketch::SketchData, Option<FaceTag>), KclError> {
@ -1077,35 +1077,6 @@ macro_rules! let_field_of {
};
}
impl<'a> FromKclValue<'a> for crate::std::import::ImportFormat {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?;
let_field_of!(obj, typ "format");
match typ {
"fbx" => Some(Self::Fbx {}),
"gltf" => Some(Self::Gltf {}),
"sldprt" => Some(Self::Sldprt {}),
"step" => Some(Self::Step {}),
"stl" => {
let_field_of!(obj, coords?);
let_field_of!(obj, units);
Some(Self::Stl { coords, units })
}
"obj" => {
let_field_of!(obj, coords?);
let_field_of!(obj, units);
Some(Self::Obj { coords, units })
}
"ply" => {
let_field_of!(obj, coords?);
let_field_of!(obj, units);
Some(Self::Ply { coords, units })
}
_ => None,
}
}
}
impl<'a> FromKclValue<'a> for super::sketch::AngledLineThatIntersectsData {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?;

View File

@ -2,6 +2,13 @@
use anyhow::Result;
use kcl_derive_docs::stdlib;
use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, ModelingCmd};
use kittycad_modeling_cmds::{
self as kcmc,
ok_response::OkModelingCmdResponse,
output::{BooleanIntersection, BooleanSubtract, BooleanUnion},
websocket::OkWebSocketResponseData,
};
use crate::{
errors::{KclError, KclErrorDetails},
@ -9,10 +16,13 @@ use crate::{
std::Args,
};
use super::DEFAULT_TOLERANCE;
/// Union two or more solids into a single solid.
pub async fn union(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let solids: Vec<Solid> =
args.get_unlabeled_kw_arg_typed("solids", &RuntimeType::Union(vec![RuntimeType::solids()]), exec_state)?;
let tolerance = args.get_kw_arg_opt("tolerance")?;
if solids.len() < 2 {
return Err(KclError::UndefinedValue(KclErrorDetails {
@ -21,7 +31,7 @@ pub async fn union(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
}));
}
let solids = inner_union(solids, exec_state, args).await?;
let solids = inner_union(solids, tolerance, exec_state, args).await?;
Ok(solids.into())
}
@ -30,18 +40,19 @@ pub async fn union(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
/// ```no_run
/// // Union two cubes using the stdlib functions.
///
/// fn cube(center) {
/// fn cube(center, size) {
/// return startSketchOn('XY')
/// |> startProfileAt([center[0] - 10, center[1] - 10], %)
/// |> line(endAbsolute = [center[0] + 10, center[1] - 10])
/// |> line(endAbsolute = [center[0] + 10, center[1] + 10])
/// |> line(endAbsolute = [center[0] - 10, center[1] + 10])
/// |> startProfileAt([center[0] - size, center[1] - size], %)
/// |> line(endAbsolute = [center[0] + size, center[1] - size])
/// |> line(endAbsolute = [center[0] + size, center[1] + size])
/// |> line(endAbsolute = [center[0] - size, center[1] + size])
/// |> close()
/// |> extrude(length = 10)
/// }
///
/// part001 = cube([0, 0])
/// part002 = cube([20, 10])
/// part001 = cube([0, 0], 10)
/// part002 = cube([7, 3], 5)
/// |> translate(z = 1)
///
/// unionedPart = union([part001, part002])
/// ```
@ -51,18 +62,19 @@ pub async fn union(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
/// // NOTE: This will not work when using codemods through the UI.
/// // Codemods will generate the stdlib function call instead.
///
/// fn cube(center) {
/// fn cube(center, size) {
/// return startSketchOn('XY')
/// |> startProfileAt([center[0] - 10, center[1] - 10], %)
/// |> line(endAbsolute = [center[0] + 10, center[1] - 10])
/// |> line(endAbsolute = [center[0] + 10, center[1] + 10])
/// |> line(endAbsolute = [center[0] - 10, center[1] + 10])
/// |> startProfileAt([center[0] - size, center[1] - size], %)
/// |> line(endAbsolute = [center[0] + size, center[1] - size])
/// |> line(endAbsolute = [center[0] + size, center[1] + size])
/// |> line(endAbsolute = [center[0] - size, center[1] + size])
/// |> close()
/// |> extrude(length = 10)
/// }
///
/// part001 = cube([0, 0])
/// part002 = cube([20, 10])
/// part001 = cube([0, 0], 10)
/// part002 = cube([7, 3], 5)
/// |> translate(z = 1)
///
/// // This is the equivalent of: union([part001, part002])
/// unionedPart = part001 + part002
@ -73,18 +85,19 @@ pub async fn union(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
/// // NOTE: This will not work when using codemods through the UI.
/// // Codemods will generate the stdlib function call instead.
///
/// fn cube(center) {
/// fn cube(center, size) {
/// return startSketchOn('XY')
/// |> startProfileAt([center[0] - 10, center[1] - 10], %)
/// |> line(endAbsolute = [center[0] + 10, center[1] - 10])
/// |> line(endAbsolute = [center[0] + 10, center[1] + 10])
/// |> line(endAbsolute = [center[0] - 10, center[1] + 10])
/// |> startProfileAt([center[0] - size, center[1] - size], %)
/// |> line(endAbsolute = [center[0] + size, center[1] - size])
/// |> line(endAbsolute = [center[0] + size, center[1] + size])
/// |> line(endAbsolute = [center[0] - size, center[1] + size])
/// |> close()
/// |> extrude(length = 10)
/// }
///
/// part001 = cube([0, 0])
/// part002 = cube([20, 10])
/// part001 = cube([0, 0], 10)
/// part002 = cube([7, 3], 5)
/// |> translate(z = 1)
///
/// // This is the equivalent of: union([part001, part002])
/// // Programmers will understand `|` as a union operation, but mechanical engineers
@ -96,31 +109,64 @@ pub async fn union(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
feature_tree_operation = true,
keywords = true,
unlabeled_first = true,
deprecated = true,
args = {
solids = {docs = "The solids to union."},
tolerance = {docs = "The tolerance to use for the union operation."},
}
}]
pub(crate) async fn inner_union(
solids: Vec<Solid>,
tolerance: Option<f64>,
exec_state: &mut ExecState,
args: Args,
) -> Result<Vec<Solid>, KclError> {
let solid_out_id = exec_state.next_uuid();
let mut solid = solids[0].clone();
solid.id = solid_out_id;
let mut new_solids = vec![solid.clone()];
if args.ctx.no_engine_commands().await {
return Ok(new_solids);
}
// Flush the fillets for the solids.
args.flush_batch_for_solids(exec_state, &solids).await?;
// TODO: call the engine union operation.
// TODO: figure out all the shit after for the faces etc.
let result = args
.send_modeling_cmd(
solid_out_id,
ModelingCmd::from(mcmd::BooleanUnion {
solid_ids: solids.iter().map(|s| s.id).collect(),
tolerance: LengthUnit(tolerance.unwrap_or(DEFAULT_TOLERANCE)),
}),
)
.await?;
// For now just return the first solid.
// Til we have a proper implementation.
Ok(vec![solids[0].clone()])
let OkWebSocketResponseData::Modeling {
modeling_response: OkModelingCmdResponse::BooleanUnion(BooleanUnion { extra_solid_ids }),
} = result
else {
return Err(KclError::Internal(KclErrorDetails {
message: "Failed to get the result of the union operation.".to_string(),
source_ranges: vec![args.source_range],
}));
};
// If we have more solids, set those as well.
if !extra_solid_ids.is_empty() {
solid.id = extra_solid_ids[0];
new_solids.push(solid.clone());
}
Ok(new_solids)
}
/// Intersect returns the shared volume between multiple solids, preserving only
/// overlapping regions.
pub async fn intersect(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let solids: Vec<Solid> = args.get_unlabeled_kw_arg_typed("solids", &RuntimeType::solids(), exec_state)?;
let tolerance = args.get_kw_arg_opt("tolerance")?;
if solids.len() < 2 {
return Err(KclError::UndefinedValue(KclErrorDetails {
@ -129,7 +175,7 @@ pub async fn intersect(exec_state: &mut ExecState, args: Args) -> Result<KclValu
}));
}
let solids = inner_intersect(solids, exec_state, args).await?;
let solids = inner_intersect(solids, tolerance, exec_state, args).await?;
Ok(solids.into())
}
@ -144,18 +190,19 @@ pub async fn intersect(exec_state: &mut ExecState, args: Args) -> Result<KclValu
/// ```no_run
/// // Intersect two cubes using the stdlib functions.
///
/// fn cube(center) {
/// fn cube(center, size) {
/// return startSketchOn('XY')
/// |> startProfileAt([center[0] - 10, center[1] - 10], %)
/// |> line(endAbsolute = [center[0] + 10, center[1] - 10])
/// |> line(endAbsolute = [center[0] + 10, center[1] + 10])
/// |> line(endAbsolute = [center[0] - 10, center[1] + 10])
/// |> startProfileAt([center[0] - size, center[1] - size], %)
/// |> line(endAbsolute = [center[0] + size, center[1] - size])
/// |> line(endAbsolute = [center[0] + size, center[1] + size])
/// |> line(endAbsolute = [center[0] - size, center[1] + size])
/// |> close()
/// |> extrude(length = 10)
/// }
///
/// part001 = cube([0, 0])
/// part002 = cube([8, 8])
/// part001 = cube([0, 0], 10)
/// part002 = cube([7, 3], 5)
/// |> translate(z = 1)
///
/// intersectedPart = intersect([part001, part002])
/// ```
@ -165,18 +212,19 @@ pub async fn intersect(exec_state: &mut ExecState, args: Args) -> Result<KclValu
/// // NOTE: This will not work when using codemods through the UI.
/// // Codemods will generate the stdlib function call instead.
///
/// fn cube(center) {
/// fn cube(center, size) {
/// return startSketchOn('XY')
/// |> startProfileAt([center[0] - 10, center[1] - 10], %)
/// |> line(endAbsolute = [center[0] + 10, center[1] - 10])
/// |> line(endAbsolute = [center[0] + 10, center[1] + 10])
/// |> line(endAbsolute = [center[0] - 10, center[1] + 10])
/// |> startProfileAt([center[0] - size, center[1] - size], %)
/// |> line(endAbsolute = [center[0] + size, center[1] - size])
/// |> line(endAbsolute = [center[0] + size, center[1] + size])
/// |> line(endAbsolute = [center[0] - size, center[1] + size])
/// |> close()
/// |> extrude(length = 10)
/// }
///
/// part001 = cube([0, 0])
/// part002 = cube([8, 8])
/// part001 = cube([0, 0], 10)
/// part002 = cube([7, 3], 5)
/// |> translate(z = 1)
///
/// // This is the equivalent of: intersect([part001, part002])
/// intersectedPart = part001 & part002
@ -186,25 +234,57 @@ pub async fn intersect(exec_state: &mut ExecState, args: Args) -> Result<KclValu
feature_tree_operation = true,
keywords = true,
unlabeled_first = true,
deprecated = true,
args = {
solids = {docs = "The solids to intersect."},
tolerance = {docs = "The tolerance to use for the intersection operation."},
}
}]
pub(crate) async fn inner_intersect(
solids: Vec<Solid>,
tolerance: Option<f64>,
exec_state: &mut ExecState,
args: Args,
) -> Result<Vec<Solid>, KclError> {
let solid_out_id = exec_state.next_uuid();
let mut solid = solids[0].clone();
solid.id = solid_out_id;
let mut new_solids = vec![solid.clone()];
if args.ctx.no_engine_commands().await {
return Ok(new_solids);
}
// Flush the fillets for the solids.
args.flush_batch_for_solids(exec_state, &solids).await?;
// TODO: call the engine union operation.
// TODO: figure out all the shit after for the faces etc.
let result = args
.send_modeling_cmd(
solid_out_id,
ModelingCmd::from(mcmd::BooleanIntersection {
solid_ids: solids.iter().map(|s| s.id).collect(),
tolerance: LengthUnit(tolerance.unwrap_or(DEFAULT_TOLERANCE)),
}),
)
.await?;
// For now just return the first solid.
// Til we have a proper implementation.
Ok(vec![solids[0].clone()])
let OkWebSocketResponseData::Modeling {
modeling_response: OkModelingCmdResponse::BooleanIntersection(BooleanIntersection { extra_solid_ids }),
} = result
else {
return Err(KclError::Internal(KclErrorDetails {
message: "Failed to get the result of the intersection operation.".to_string(),
source_ranges: vec![args.source_range],
}));
};
// If we have more solids, set those as well.
if !extra_solid_ids.is_empty() {
solid.id = extra_solid_ids[0];
new_solids.push(solid.clone());
}
Ok(new_solids)
}
/// Subtract removes tool solids from base solids, leaving the remaining material.
@ -212,7 +292,23 @@ pub async fn subtract(exec_state: &mut ExecState, args: Args) -> Result<KclValue
let solids: Vec<Solid> = args.get_unlabeled_kw_arg_typed("solids", &RuntimeType::solids(), exec_state)?;
let tools: Vec<Solid> = args.get_kw_arg_typed("tools", &RuntimeType::solids(), exec_state)?;
let solids = inner_subtract(solids, tools, exec_state, args).await?;
if solids.len() > 1 {
return Err(KclError::UndefinedValue(KclErrorDetails {
message: "Only one solid is allowed for a subtract operation, currently.".to_string(),
source_ranges: vec![args.source_range],
}));
}
if tools.len() > 1 {
return Err(KclError::UndefinedValue(KclErrorDetails {
message: "Only one tool is allowed for a subtract operation, currently.".to_string(),
source_ranges: vec![args.source_range],
}));
}
let tolerance = args.get_kw_arg_opt("tolerance")?;
let solids = inner_subtract(solids, tools, tolerance, exec_state, args).await?;
Ok(solids.into())
}
@ -227,20 +323,19 @@ pub async fn subtract(exec_state: &mut ExecState, args: Args) -> Result<KclValue
/// ```no_run
/// // Subtract a cylinder from a cube using the stdlib functions.
///
/// fn cube(center) {
/// fn cube(center, size) {
/// return startSketchOn('XY')
/// |> startProfileAt([center[0] - 10, center[1] - 10], %)
/// |> line(endAbsolute = [center[0] + 10, center[1] - 10])
/// |> line(endAbsolute = [center[0] + 10, center[1] + 10])
/// |> line(endAbsolute = [center[0] - 10, center[1] + 10])
/// |> startProfileAt([center[0] - size, center[1] - size], %)
/// |> line(endAbsolute = [center[0] + size, center[1] - size])
/// |> line(endAbsolute = [center[0] + size, center[1] + size])
/// |> line(endAbsolute = [center[0] - size, center[1] + size])
/// |> close()
/// |> extrude(length = 10)
/// }
///
/// part001 = cube([0, 0])
/// part002 = startSketchOn('XY')
/// |> circle(center = [0, 0], radius = 2)
/// |> extrude(length = 10)
/// part001 = cube([0, 0], 10)
/// part002 = cube([7, 3], 5)
/// |> translate(z = 1)
///
/// subtractedPart = subtract([part001], tools=[part002])
/// ```
@ -250,20 +345,19 @@ pub async fn subtract(exec_state: &mut ExecState, args: Args) -> Result<KclValue
/// // NOTE: This will not work when using codemods through the UI.
/// // Codemods will generate the stdlib function call instead.
///
/// fn cube(center) {
/// fn cube(center, size) {
/// return startSketchOn('XY')
/// |> startProfileAt([center[0] - 10, center[1] - 10], %)
/// |> line(endAbsolute = [center[0] + 10, center[1] - 10])
/// |> line(endAbsolute = [center[0] + 10, center[1] + 10])
/// |> line(endAbsolute = [center[0] - 10, center[1] + 10])
/// |> startProfileAt([center[0] - size, center[1] - size], %)
/// |> line(endAbsolute = [center[0] + size, center[1] - size])
/// |> line(endAbsolute = [center[0] + size, center[1] + size])
/// |> line(endAbsolute = [center[0] - size, center[1] + size])
/// |> close()
/// |> extrude(length = 10)
/// }
///
/// part001 = cube([0, 0])
/// part002 = startSketchOn('XY')
/// |> circle(center = [0, 0], radius = 2)
/// |> extrude(length = 10)
/// part001 = cube([0, 0], 10)
/// part002 = cube([7, 3], 5)
/// |> translate(z = 1)
///
/// // This is the equivalent of: subtract([part001], tools=[part002])
/// subtractedPart = part001 - part002
@ -273,26 +367,59 @@ pub async fn subtract(exec_state: &mut ExecState, args: Args) -> Result<KclValue
feature_tree_operation = true,
keywords = true,
unlabeled_first = true,
deprecated = true,
args = {
solids = {docs = "The solids to use as the base to subtract from."},
tools = {docs = "The solids to subtract."},
tolerance = {docs = "The tolerance to use for the subtraction operation."},
}
}]
pub(crate) async fn inner_subtract(
solids: Vec<Solid>,
tools: Vec<Solid>,
tolerance: Option<f64>,
exec_state: &mut ExecState,
args: Args,
) -> Result<Vec<Solid>, KclError> {
let solid_out_id = exec_state.next_uuid();
let mut solid = solids[0].clone();
solid.id = solid_out_id;
let mut new_solids = vec![solid.clone()];
if args.ctx.no_engine_commands().await {
return Ok(new_solids);
}
// Flush the fillets for the solids and the tools.
let combined_solids = solids.iter().chain(tools.iter()).cloned().collect::<Vec<Solid>>();
args.flush_batch_for_solids(exec_state, &combined_solids).await?;
// TODO: call the engine union operation.
// TODO: figure out all the shit after for the faces etc.
let result = args
.send_modeling_cmd(
solid_out_id,
ModelingCmd::from(mcmd::BooleanSubtract {
target_ids: solids.iter().map(|s| s.id).collect(),
tool_ids: tools.iter().map(|s| s.id).collect(),
tolerance: LengthUnit(tolerance.unwrap_or(DEFAULT_TOLERANCE)),
}),
)
.await?;
// For now just return the first solid.
// Til we have a proper implementation.
Ok(vec![solids[0].clone()])
let OkWebSocketResponseData::Modeling {
modeling_response: OkModelingCmdResponse::BooleanSubtract(BooleanSubtract { extra_solid_ids }),
} = result
else {
return Err(KclError::Internal(KclErrorDetails {
message: "Failed to get the result of the subtract operation.".to_string(),
source_ranges: vec![args.source_range],
}));
};
// If we have more solids, set those as well.
if !extra_solid_ids.is_empty() {
solid.id = extra_solid_ids[0];
new_solids.push(solid.clone());
}
Ok(new_solids)
}

View File

@ -9,8 +9,7 @@ use kcmc::{
length_unit::LengthUnit,
ok_response::OkModelingCmdResponse,
output::ExtrusionFaceInfo,
shared::ExtrusionFaceCapType,
shared::Opposite,
shared::{ExtrusionFaceCapType, Opposite},
websocket::{ModelingCmdReq, OkWebSocketResponseData},
ModelingCmd,
};

View File

@ -1,181 +0,0 @@
//! Standard library functions involved in importing files.
use anyhow::Result;
use kcl_derive_docs::stdlib;
use kcmc::{coord::System, format::InputFormat3d, units::UnitLength};
use kittycad_modeling_cmds as kcmc;
use crate::{
errors::{KclError, KclErrorDetails},
execution::{import_foreign, send_import_to_engine, ExecState, ImportedGeometry, KclValue, ZOO_COORD_SYSTEM},
std::Args,
};
/// Import format specifier
#[derive(serde :: Serialize, serde :: Deserialize, PartialEq, Debug, Clone, schemars :: JsonSchema)]
#[cfg_attr(feature = "tabled", derive(tabled::Tabled))]
#[serde(tag = "format")]
pub enum ImportFormat {
/// Autodesk Filmbox (FBX) format
#[serde(rename = "fbx")]
Fbx {},
/// Binary glTF 2.0. We refer to this as glTF since that is how our customers refer to
/// it, but this can also import binary glTF (glb).
#[serde(rename = "gltf")]
Gltf {},
/// Wavefront OBJ format.
#[serde(rename = "obj")]
Obj {
/// Co-ordinate system of input data.
/// Defaults to the [KittyCAD co-ordinate system.
coords: Option<System>,
/// The units of the input data. This is very important for correct scaling and when
/// calculating physics properties like mass, etc.
/// Defaults to millimeters.
units: UnitLength,
},
/// The PLY Polygon File Format.
#[serde(rename = "ply")]
Ply {
/// Co-ordinate system of input data.
/// Defaults to the [KittyCAD co-ordinate system.
coords: Option<System>,
/// The units of the input data. This is very important for correct scaling and when
/// calculating physics properties like mass, etc.
/// Defaults to millimeters.
units: UnitLength,
},
/// SolidWorks part (SLDPRT) format.
#[serde(rename = "sldprt")]
Sldprt {},
/// ISO 10303-21 (STEP) format.
#[serde(rename = "step")]
Step {},
/// *ST**ereo**L**ithography format.
#[serde(rename = "stl")]
Stl {
/// Co-ordinate system of input data.
/// Defaults to the [KittyCAD co-ordinate system.
coords: Option<System>,
/// The units of the input data. This is very important for correct scaling and when
/// calculating physics properties like mass, etc.
/// Defaults to millimeters.
units: UnitLength,
},
}
impl From<ImportFormat> for InputFormat3d {
fn from(format: ImportFormat) -> Self {
match format {
ImportFormat::Fbx {} => InputFormat3d::Fbx(Default::default()),
ImportFormat::Gltf {} => InputFormat3d::Gltf(Default::default()),
ImportFormat::Obj { coords, units } => InputFormat3d::Obj(kcmc::format::obj::import::Options {
coords: coords.unwrap_or(ZOO_COORD_SYSTEM),
units,
}),
ImportFormat::Ply { coords, units } => InputFormat3d::Ply(kcmc::format::ply::import::Options {
coords: coords.unwrap_or(ZOO_COORD_SYSTEM),
units,
}),
ImportFormat::Sldprt {} => InputFormat3d::Sldprt(kcmc::format::sldprt::import::Options {
split_closed_faces: false,
}),
ImportFormat::Step {} => InputFormat3d::Step(kcmc::format::step::import::Options {
split_closed_faces: false,
}),
ImportFormat::Stl { coords, units } => InputFormat3d::Stl(kcmc::format::stl::import::Options {
coords: coords.unwrap_or(ZOO_COORD_SYSTEM),
units,
}),
}
}
}
/// Import a CAD file.
/// For formats lacking unit data (STL, OBJ, PLY), the default import unit is millimeters.
/// Otherwise you can specify the unit by passing in the options parameter.
/// If you import a gltf file, we will try to find the bin file and import it as well.
///
/// Import paths are relative to the current project directory. This only works in the desktop app
/// not in browser.
pub async fn import(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (file_path, options): (String, Option<ImportFormat>) = args.get_import_data()?;
let imported_geometry = inner_import(file_path, options, exec_state, args).await?;
Ok(KclValue::ImportedGeometry(imported_geometry))
}
/// Import a CAD file.
///
/// **DEPRECATED** Prefer to use import statements.
///
/// For formats lacking unit data (such as STL, OBJ, or PLY files), the default
/// unit of measurement is millimeters. Alternatively you may specify the unit
/// by passing your desired measurement unit in the options parameter. When
/// importing a GLTF file, the bin file will be imported as well. Import paths
/// are relative to the current project directory.
///
/// Note: The import command currently only works when using the native
/// Design Studio.
///
/// ```no_run
/// model = import("tests/inputs/cube.obj")
/// ```
///
/// ```no_run
/// model = import("tests/inputs/cube.obj", {format: "obj", units: "m"})
/// ```
///
/// ```no_run
/// model = import("tests/inputs/cube.gltf")
/// ```
///
/// ```no_run
/// model = import("tests/inputs/cube.sldprt")
/// ```
///
/// ```no_run
/// model = import("tests/inputs/cube.step")
/// ```
///
/// ```no_run
/// import height, buildSketch from 'common.kcl'
///
/// plane = 'XZ'
/// margin = 2
/// s1 = buildSketch(plane, [0, 0])
/// s2 = buildSketch(plane, [0, height() + margin])
/// ```
#[stdlib {
name = "import",
feature_tree_operation = true,
deprecated = true,
tags = [],
}]
async fn inner_import(
file_path: String,
options: Option<ImportFormat>,
exec_state: &mut ExecState,
args: Args,
) -> Result<ImportedGeometry, KclError> {
if file_path.is_empty() {
return Err(KclError::Semantic(KclErrorDetails {
message: "No file path was provided.".to_string(),
source_ranges: vec![args.source_range],
}));
}
let format = options.map(InputFormat3d::from);
send_import_to_engine(
import_foreign(
std::path::Path::new(&file_path),
format,
exec_state,
&args.ctx,
args.source_range,
)
.await?,
&args.ctx,
)
.await
}

View File

@ -12,7 +12,6 @@ pub mod edge;
pub mod extrude;
pub mod fillet;
pub mod helix;
pub mod import;
pub mod loft;
pub mod math;
pub mod mirror;
@ -86,8 +85,6 @@ lazy_static! {
Box::new(crate::std::sketch::Arc),
Box::new(crate::std::sketch::ArcTo),
Box::new(crate::std::sketch::TangentialArc),
Box::new(crate::std::sketch::TangentialArcTo),
Box::new(crate::std::sketch::TangentialArcToRelative),
Box::new(crate::std::sketch::BezierCurve),
Box::new(crate::std::sketch::Hole),
Box::new(crate::std::patterns::PatternLinear2D),
@ -111,7 +108,6 @@ lazy_static! {
Box::new(crate::std::sweep::Sweep),
Box::new(crate::std::loft::Loft),
Box::new(crate::std::planes::OffsetPlane),
Box::new(crate::std::import::Import),
Box::new(crate::std::math::Acos),
Box::new(crate::std::math::Asin),
Box::new(crate::std::math::Atan),

View File

@ -824,7 +824,7 @@ pub async fn pattern_linear_3d(exec_state: &mut ExecState, args: Args) -> Result
/// |> startProfileAt([-size, -size], %)
/// |> line(end = [2 * size, 0])
/// |> line(end = [0, 2 * size])
/// |> tangentialArcTo([-size, size], %)
/// |> tangentialArc(endAbsolute = [-size, size])
/// |> close(%)
/// |> extrude(length = 65)
///
@ -852,7 +852,7 @@ pub async fn pattern_linear_3d(exec_state: &mut ExecState, args: Args) -> Result
/// |> startProfileAt([-size, -size], %)
/// |> line(end = [2 * size, 0])
/// |> line(end = [0, 2 * size])
/// |> tangentialArcTo([-size, size], %)
/// |> tangentialArc(endAbsolute = [-size, size])
/// |> close(%)
/// |> extrude(length = 65)
///

View File

@ -1,7 +1,12 @@
//! Standard library revolution surfaces.
use anyhow::Result;
use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, shared::Angle, shared::Opposite, ModelingCmd};
use kcmc::{
each_cmd as mcmd,
length_unit::LengthUnit,
shared::{Angle, Opposite},
ModelingCmd,
};
use kittycad_modeling_cmds::{self as kcmc, shared::Point3d};
use super::DEFAULT_TOLERANCE;

View File

@ -393,10 +393,7 @@ pub async fn segment_length(exec_state: &mut ExecState, args: Args) -> Result<Kc
/// length = 10,
/// tag = $thing,
/// )
/// |> tangentialArc({
/// offset = -120,
/// radius = 5,
/// }, %)
/// |> tangentialArc(angle = -120, radius = 5)
/// |> angledLine(
/// angle = -60,
/// length = segLen(thing),
@ -485,12 +482,12 @@ pub async fn tangent_to_end(exec_state: &mut ExecState, args: Args) -> Result<Kc
/// pillSketch = startSketchOn('XZ')
/// |> startProfileAt([0, 0], %)
/// |> line(end = [20, 0])
/// |> tangentialArcToRelative([0, 10], %, $arc1)
/// |> tangentialArc(end = [0, 10], tag = $arc1)
/// |> angledLine(
/// angle = tangentToEnd(arc1),
/// length = 20,
/// )
/// |> tangentialArcToRelative([0, -10], %)
/// |> tangentialArc(end = [0, -10])
/// |> close()
///
/// pillExtrude = extrude(pillSketch, length = 10)
@ -501,12 +498,12 @@ pub async fn tangent_to_end(exec_state: &mut ExecState, args: Args) -> Result<Kc
/// pillSketch = startSketchOn('XZ')
/// |> startProfileAt([0, 0], %)
/// |> line(end = [0, 20])
/// |> tangentialArcTo([10, 20], %, $arc1)
/// |> tangentialArc(endAbsolute = [10, 20], tag = $arc1)
/// |> angledLine(
/// angle = tangentToEnd(arc1),
/// length = 20,
/// )
/// |> tangentialArcToRelative([-10, 0], %)
/// |> tangentialArc(end = [-10, 0])
/// |> close()
///
/// pillExtrude = extrude(pillSketch, length = 10)

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