Compare commits

...

44 Commits

Author SHA1 Message Date
1dad2c208d Merge branch 'main' into nicboone8-patch-3 2025-06-14 08:11:02 -07:00
2b0ced179a Add files via upload (#7429)
* Add files via upload

* Update snapshots

* Update snapshots

* Update snapshots

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jace Browning <jacebrowning@gmail.com>
2025-06-14 02:27:27 +00:00
caea13fe91 Update kcl-samples simulation test output 2025-06-14 00:33:58 +00:00
2a02e0eacf Merge branch 'main' into nicboone8-patch-3 2025-06-13 17:25:56 -07:00
c2f6ce065d KCL: New extrudeTwist endpoint (#7480)
This does not include feature tree editing support.
2025-06-13 21:17:01 +00:00
b3bdc35da2 Prevent query params from getting eaten by redirect (#7479)
* Fix URL generated

* Prevent query params from getting eaten by redirect
2025-06-13 16:42:59 -04:00
8fe4f67a29 Fix URL generated (#7478) 2025-06-13 15:31:51 -04:00
1f58f31cf3 Merge branch 'main' into nicboone8-patch-3 2025-06-13 12:02:48 -07:00
c6b1d11700 Fix to not panic when using the wrong argument type (#7474) 2025-06-13 14:02:14 -04:00
2ef84382a6 Show a countdown on reconnect (#7470)
* Show a countdown on reconnect

* Reduce visual jank
2025-06-13 15:05:13 +00:00
ff5f10c9cc Merge branch 'main' into nicboone8-patch-3 2025-06-13 07:49:53 -07:00
939c2c77b0 the settings docs have a stutter with 2 h1s this fixes it (#7472)
* the settings docs have a stutter with 2 h1s this fixes it

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

* regenerate

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

---------

Signed-off-by: Jessie Frazelle <github@jessfraz.com>
2025-06-13 16:33:28 +10:00
f34d3ab8bc Update kcl-samples simulation test output 2025-06-12 23:09:41 +00:00
61ae2c9bcd Merge branch 'main' into nicboone8-patch-3 2025-06-12 16:02:30 -07:00
31ec0184a1 Switch to plain Markdown template for releases (#7471) 2025-06-12 22:36:04 +00:00
62c4546658 Create an issue template for releases (#7467)
* Create an issue template for releases

* Remove invalid syntax

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>

---------

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
2025-06-12 19:28:26 +00:00
18959c8585 uncommenting assert in countersunk plate 2025-06-12 09:52:32 -07:00
e036ad37ad T slot 2025-06-12 09:42:51 -07:00
f7dafed7a4 Fixing comments and descriptions 2025-06-12 09:40:06 -07:00
383b38c2d2 Add operations for variable declarations (#7451)
* Add operations for variable declarations

* Update output

* Change to use OpKclValue

* Update output after merge
2025-06-12 16:38:12 +00:00
e0025f7fad Release KCL 81 (#7463) 2025-06-12 16:20:41 +00:00
2a13888c54 Checkbox for variable creation in command bar flows (#7452)
* Draft: Constrain with Expression: prevent throwaway variables
Fixes #7252

* WIP tests
2025-06-12 10:58:07 -04:00
1443f3ab39 Improve error messages around PI and other numbers with unknown units (#7457)
* Improve docs around PI

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Refactor and polish type error messages

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Add suggestion to fix unknown numbers error

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Don't warn so often about unknown units

Signed-off-by: Nick Cameron <nrc@ncameron.org>

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-06-12 10:20:04 -04:00
bf87c23ea8 [Fix]: All 404 links in the repo (#7448)
* fix: 404 fixes only

* fix: auto fmt
2025-06-12 09:18:57 -05:00
5d23b0e487 add broken tests (#7445)
* add broken tests

* spelling work around
2025-06-12 11:33:46 +00:00
df6c81b0b4 Units bug fix with modulo (#7446)
* Add degrees annotations to examples

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Fix a units bug with the modulo operation

Signed-off-by: Nick Cameron <nrc@ncameron.org>

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-06-12 08:44:55 +12:00
5f1f579d4b Quick follow up on share link tests (#7401)
* pierremtb/adhoc-quick-sharelink-follow-up

* Update snapshots

* Update snapshots

* Remove Linux from 2000 tests

* Revert "Remove Linux from 2000 tests"

This reverts commit ad9bc58dd7.

* Remove Linux from 2000 tests and guard linux in the app too

* Revert "Remove Linux from 2000 tests and guard linux in the app too"

This reverts commit e13983eb12.

* Break out test in two

* Not

* Add debug prints

* Try to force userAgent in pw config

* Revert "Try to force userAgent in pw config"

This reverts commit d1e6d7c7be.

* Clean up

* Add sad fix

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-06-11 13:24:38 -04:00
9a549ff379 Track artifact commands and operations per-module (#7426)
* Change so that operations are stored per module

* Refactor so that all modeling commands go through ExecState

* Remove unneeded PartialOrd implementations

* Remove artifact_commands from KclError since it was only for debugging

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-06-11 01:30:48 +00:00
851ea28bd3 Change trig functions to return number with Default units (#7425)
* Change trig functions to return number with Default units

* Update docs

* Update output
2025-06-11 00:24:48 +00:00
ff15c7b9db Extend idle threshold to 8 hours if execution takes longer than 5 min. (#7440)
Extend idle threshold to 8 hours if execution takes longer than 5 minutes
2025-06-10 16:27:54 -04:00
f304577d5d Clean up build-apps upload artifact names (#7427) 2025-06-10 10:41:08 -04:00
b03b0d3b53 #7318 Remember desktop app size, position on screen (#7322)
* desktop app should remember the last window size

* localStorage doesnt work in electron, use a settings file

* save bounds (position too)

* only restore saved bounds if its still valid for current displays

* typo

* remove logs

* cleanup

* typo

* add version to LastWindowConfig

* rename window_config.json to device_state.json
2025-06-10 11:38:44 +02:00
dd4d0f6d98 Rename nightly to staging and have it point to dev infra (#7422)
* Rename nightly to staging and have it point to dev infra
Fixes #7421

* To revert: force IS_STAGING

* chmod +x ./scripts/flip-files-to-staging.sh

* Fix mix up dev and prod

* Revert "To revert: force IS_STAGING"

This reverts commit 0178604a55.
2025-06-09 15:43:48 -04:00
1cd742df5d Ensure all E2E artifacts have unique names (#7417) 2025-06-09 11:05:56 -04:00
6460ed8ea8 bump the client API response timeout 60 -> 300 (#7406) 2025-06-06 16:02:42 -07:00
5c51b27f29 Use namespace for build-apps macos and windows (#7400)
* WIP: Use namespace windows-2-cores for build-apps

* WIP: 8-cores to see if it's different

* WIP: macos-6-cores and windows-4-cores

* To revert: test IS_RELEASE

* Change d drive to c for cert

* Maintain job naming

* Revert "To revert: test IS_RELEASE"

This reverts commit 5d4eadce6c.

* Change job names
2025-06-06 17:03:24 -04:00
77690b4419 Add touch support to camera while in modeling mode (#7384)
* Add HammerJS

* Fmt and little type cleanup

* Implement multi-touch through HammerJS

* Add velocity-decay "flick" behavior for orbit

* Update src/clientSideScene/CameraControls.ts

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>

* tsc fix

* Update src/clientSideScene/CameraControls.ts

Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>

* Release KCL 80 (#7391)

* Check for updates button in moar menus & toasts (#7369)

* Check for update button in more menus
Fixes #7368

* Add menubar item

* Another one

* Add Checking for updates... and No new update toasts

* Lint

* Trigger CI

* Update src/main.ts

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>

* Update electron-builder.yml

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>

* Update electron-builder.yml

* Moar clean up

---------

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>

* Format examples in docs (#7378)

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Fix some typos in previous PR (#7392)

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Remove the untyped getters from std::args (#7377)

* Move last uses of untypeed arg getters

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Rename _typed functions

Signed-off-by: Nick Cameron <nrc@ncameron.org>

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* WIP #7226 Fix remove constraints (#7304)

* handle if graphSelections is empty

* fix removeConstrainingValuesInfo by using pathToNodes if available instead of selectionRanges: current selection should not be required to remove constraints

* selectionRanges not needed for removeConstrainingValuesInfo anymore

* fix remove constraint unit test: pass line's pathToNode instead of argument to remove constraint

* Change to use artifact pathToNode (#7361)

* Change to use artifact pathToNode

* Fix to do bounds checking

* move TTC capture to unit test (#7268)

* move TTC capture to unit test

* progress with artifact

* fmt

* abstract cases

* add another case

* add another test

* update snapshots with proper file names

* force to JSON

* fmt

* make jest happy

* add another example and other tweaks

* fix

* tweak

* add logs

* more logs

* strip out kcl version

* remove logs

* add comment explainer

* more comments

* more comment

* remove package-lock line

* Add support for tag on close segment when the last sketch edge is missing (#7375)

* add test

* fix

* Update snapshots

* Update snapshots

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* Use namespace for windows e2e tests (#7398)

* Use namespace for windows e2e tests

* Change to the new profile

* Remove TODO

* Commit new snapshots even if some tests failed (#7399)

* Commit new snapshots even if some tests failed

* Update snapshots

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* Clean up share link tests (#7372)

* pierremtb/adhoc/clean-up-share-link-tests

* Lint

* WIP labels

* Trigger CI

* Change to skips

* Remove old docs files (#7381)

* Remove old files; no longer generated.

* Update snapshots

* Update snapshots

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jace Browning <jacebrowning@gmail.com>

* #7199 Fix broken links in docs (#7397)

* update broken links

* update github discussion links, fmt

* update comment

---------

Co-authored-by: Jace Browning <jacebrowning@gmail.com>

* Inline engine issue from @Irev-Dev

* Add commented-out test to be implemented later https://github.com/KittyCAD/modeling-app/issues/7403

* Update e2e/playwright/test-utils.ts

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
Co-authored-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: Andrew Varga <grizzly33@gmail.com>
Co-authored-by: max <margorskyi@gmail.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jace Browning <jacebrowning@gmail.com>
Co-authored-by: Nick McCleery <34814836+nickmccleery@users.noreply.github.com>
2025-06-06 20:04:20 +00:00
6996670020 fix edge case of sketch not being exited out of properly (#7396)
* fix edge case of sketch not being exited out of properly

* Update e2e/playwright/sketch-tests.spec.ts

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>

---------

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Co-authored-by: Jace Browning <jacebrowning@gmail.com>
2025-06-07 03:20:06 +10:00
1fd4e93091 reexecute KCL code when reloading due to external file modification (#7293)
Co-authored-by: Lucas Kent <rubickent@gmail.com>
2025-06-06 12:44:40 -04:00
a1ac029333 #7199 Fix broken links in docs (#7397)
* update broken links

* update github discussion links, fmt

* update comment

---------

Co-authored-by: Jace Browning <jacebrowning@gmail.com>
2025-06-06 11:52:56 -04:00
29cf16d744 Remove old docs files (#7381)
* Remove old files; no longer generated.

* Update snapshots

* Update snapshots

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jace Browning <jacebrowning@gmail.com>
2025-06-06 15:47:40 +00:00
9b3afccf53 Clean up share link tests (#7372)
* pierremtb/adhoc/clean-up-share-link-tests

* Lint

* WIP labels

* Trigger CI

* Change to skips
2025-06-06 11:46:55 -04:00
231ca0fa35 Commit new snapshots even if some tests failed (#7399)
* Commit new snapshots even if some tests failed

* Update snapshots

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-06-06 10:01:07 -04:00
4608c02442 Use namespace for windows e2e tests (#7398)
* Use namespace for windows e2e tests

* Change to the new profile

* Remove TODO
2025-06-06 09:40:14 -04:00
679 changed files with 11688707 additions and 11566676 deletions

38
.github/ISSUE_TEMPLATE/release.md vendored Normal file
View File

@ -0,0 +1,38 @@
---
name: Release
about: Create a new release for the Zoo Design Studio
title: "Cut release v1.?.?"
labels: [release]
---
> Instructions: https://github.com/KittyCAD/modeling-app/blob/main/CONTRIBUTING.md#shipping-releases
---
# Manual Checklist
Release builds URL: ???
## Windows via ???
* [ ] Download the release build for this platform
* [ ] Confirm the application opens (dismiss the updater)
* [ ] Create a project with a basic Text-to-CAD prompt
* [ ] Confirm the result is viewable in an engine stream
* [ ] Open the application again and confirm the updater can downgrade
## macOS via ???
* [ ] Download the release build for this platform
* [ ] Confirm the application opens (dismiss the updater)
* [ ] Create a project with a basic Text-to-CAD prompt
* [ ] Confirm the result is viewable in an engine stream
* [ ] Open the application again and confirm the updater can downgrade
## Linux via ???
* [ ] Download the release build for this platform
* [ ] Confirm the application opens (dismiss the updater)
* [ ] Create a project with a basic Text-to-CAD prompt
* [ ] Confirm the result is viewable in an engine stream
* [ ] Open the application again and confirm the updater can downgrade

View File

@ -10,7 +10,7 @@ on:
env:
IS_RELEASE: ${{ github.ref_type == 'tag' && startsWith(github.ref_name, 'v') }}
IS_NIGHTLY: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
IS_STAGING: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@ -91,14 +91,14 @@ jobs:
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
run: "npm run build:wasm"
- name: Set nightly version, product name, release notes, and icons
if: ${{ env.IS_NIGHTLY == 'true' }}
- name: Set staging version, product name, release notes, and icons
if: ${{ env.IS_STAGING == 'true' }}
run: |
COMMIT=$(git rev-parse --short HEAD)
DATE=$(date +'%-y.%-m.%-d')
export VERSION=$DATE-main.$COMMIT
npm run files:set-version
npm run files:flip-to-nightly
npm run files:flip-to-staging
- name: Set release version
if: ${{ env.IS_RELEASE == 'true' }}
@ -130,13 +130,14 @@ jobs:
fail-fast: false
matrix:
include:
- os: macos-14
platform: mac
- os: windows-2022
platform: win
- os: namespace-profile-macos-6-cores
platform: macos
- os: namespace-profile-windows-4-cores
platform: windows
- os: ubuntu-22.04
platform: linux
runs-on: ${{ matrix.os }}
name: build-apps (${{ matrix.platform }})
env:
VERSION_NO_V: ${{ needs.prepare-files.outputs.version }}
steps:
@ -166,14 +167,14 @@ jobs:
- run: npm install
- name: Prepare certificate and variables (Windows only)
if: ${{ (env.IS_RELEASE == 'true' || env.IS_NIGHTLY == 'true') && matrix.os == 'windows-2022' }}
if: ${{ (env.IS_RELEASE == 'true' || env.IS_STAGING == 'true') && matrix.platform == 'windows' }}
run: |
echo "${{secrets.SM_CLIENT_CERT_FILE_B64 }}" | base64 --decode > /d/Certificate_pkcs12.p12
cat /d/Certificate_pkcs12.p12
echo "${{secrets.SM_CLIENT_CERT_FILE_B64 }}" | base64 --decode > /c/Certificate_pkcs12.p12
cat /c/Certificate_pkcs12.p12
echo "::set-output name=version::${GITHUB_REF#refs/tags/v}"
echo "SM_HOST=${{ secrets.SM_HOST }}" >> "$GITHUB_ENV"
echo "SM_API_KEY=${{ secrets.SM_API_KEY }}" >> "$GITHUB_ENV"
echo "SM_CLIENT_CERT_FILE=D:\\Certificate_pkcs12.p12" >> "$GITHUB_ENV"
echo "SM_CLIENT_CERT_FILE=C:\\Certificate_pkcs12.p12" >> "$GITHUB_ENV"
echo "SM_CLIENT_CERT_PASSWORD=${{ secrets.SM_CLIENT_CERT_PASSWORD }}" >> "$GITHUB_ENV"
echo "C:\Program Files (x86)\Windows Kits\10\App Certification Kit" >> $GITHUB_PATH
echo "C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools" >> $GITHUB_PATH
@ -181,7 +182,7 @@ jobs:
shell: bash
- name: Setup certicate with SSM KSP (Windows only)
if: ${{ (env.IS_RELEASE == 'true' || env.IS_NIGHTLY == 'true') && matrix.os == 'windows-2022' }}
if: ${{ (env.IS_RELEASE == 'true' || env.IS_STAGING == 'true') && matrix.platform == 'windows' }}
run: |
curl -X GET https://one.digicert.com/signingmanager/api-ui/v1/releases/smtools-windows-x64.msi/download -H "x-api-key:%SM_API_KEY%" -o smtools-windows-x64.msi
msiexec /i smtools-windows-x64.msi /quiet /qn
@ -191,7 +192,7 @@ jobs:
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,
# with staging-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.
@ -199,13 +200,13 @@ jobs:
shell: cmd
- name: Build the app (debug)
if: ${{ env.IS_RELEASE == 'false' && env.IS_NIGHTLY == 'false' }}
if: ${{ env.IS_RELEASE == 'false' && env.IS_STAGING == 'false' }}
# electron-builder doesn't have a concept of release vs debug,
# this is just not doing any codesign or release yml generation, and points to dev infra
run: npm run tronb:package:dev
- name: Build the app (release)
if: ${{ env.IS_RELEASE == 'true' || env.IS_NIGHTLY == 'true' }}
if: ${{ env.IS_RELEASE == 'true' || env.IS_STAGING == 'true' }}
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
@ -215,7 +216,7 @@ jobs:
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }}
WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }}
run: npm run tronb:package:prod
run: npm run tronb:package:${{ env.IS_STAGING == 'true' && 'dev' || 'prod' }}
- name: List artifacts in out/
run: ls -R out
@ -239,20 +240,20 @@ jobs:
out/*-x86_64-linux.*
- uses: actions/upload-artifact@v4
if: ${{ env.IS_RELEASE == 'true' || env.IS_NIGHTLY == 'true' }}
if: ${{ env.IS_RELEASE == 'true' || env.IS_STAGING == 'true' }}
with:
name: out-yml-${{ matrix.platform }}
path: |
out/latest*.yml
# TODO: add the 'Build for Mac TestFlight (nightly)' stage back
# TODO: add the 'Build for Mac TestFlight' stage back
upload-apps-release:
runs-on: ubuntu-22.04
permissions:
contents: write
# Equivalent to IS_RELEASE || IS_NIGHTLY (but we can't access those env vars here)
# Equivalent to IS_RELEASE || IS_STAGING (but we can't access those env vars here)
if: ${{ (github.ref_type == 'tag' && startsWith(github.ref_name, 'v')) || (github.event_name == 'push' && github.ref == 'refs/heads/main') }}
env:
VERSION_NO_V: ${{ needs.prepare-files.outputs.version }}
@ -263,32 +264,32 @@ jobs:
- uses: actions/download-artifact@v4
with:
name: out-arm64-win
name: out-arm64-windows
path: out
- uses: actions/download-artifact@v4
with:
name: out-x64-win
name: out-x64-windows
path: out
- uses: actions/download-artifact@v4
with:
name: out-yml-win
name: out-yml-windows
path: out
- uses: actions/download-artifact@v4
with:
name: out-arm64-mac
name: out-arm64-macos
path: out
- uses: actions/download-artifact@v4
with:
name: out-x64-mac
name: out-x64-macos
path: out
- uses: actions/download-artifact@v4
with:
name: out-yml-mac
name: out-yml-macos
path: out
- uses: actions/download-artifact@v4
@ -310,8 +311,8 @@ jobs:
env:
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%20Design%20Studio%20%28Nightly%29' || 'Zoo%20Design%20Studio' }}
WEBSITE_DIR: ${{ env.IS_STAGING == 'true' && 'dl.zoo.dev/releases/modeling-app/staging' || 'dl.zoo.dev/releases/modeling-app' }}
URL_CODED_NAME: ${{ env.IS_STAGING == 'true' && 'Zoo%20Design%20Studio%20%28Staging%29' || 'Zoo%20Design%20Studio' }}
run: |
RELEASE_DIR=https://${WEBSITE_DIR}
jq --null-input \
@ -360,26 +361,26 @@ jobs:
run: "ls -R out"
- name: Authenticate to Google Cloud
if: ${{ env.IS_NIGHTLY == 'true' }}
if: ${{ env.IS_STAGING == 'true' }}
uses: 'google-github-actions/auth@v2.1.8'
with:
credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}'
- name: Set up Google Cloud SDK
if: ${{ env.IS_NIGHTLY == 'true' }}
if: ${{ env.IS_STAGING == 'true' }}
uses: google-github-actions/setup-gcloud@v2.1.4
with:
project_id: ${{ env.GOOGLE_CLOUD_PROJECT_ID }}
- name: Upload nightly files to public bucket
if: ${{ env.IS_NIGHTLY == 'true' }}
- name: Upload staging files to public bucket
if: ${{ env.IS_STAGING == 'true' }}
uses: google-github-actions/upload-cloud-storage@v2.2.2
with:
path: out
glob: '*'
parent: false
destination: 'dl.kittycad.io/releases/modeling-app/nightly'
destination: 'dl.kittycad.io/releases/modeling-app/staging'
- name: Invalidate bucket cache on latest*.yml and last_download.json files
if: ${{ env.IS_NIGHTLY == 'true' }}
run: npm run files:invalidate-bucket:nightly
if: ${{ env.IS_STAGING == 'true' }}
run: npm run files:invalidate-bucket:staging

View File

@ -177,16 +177,16 @@ jobs:
TARGET: web
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() && (success() || failure()) }}
if: ${{ !cancelled() }}
with:
name: playwright-report-ubuntu-snapshot-${{ github.sha }}
name: playwright-report-snapshot-${{ github.sha }}
path: playwright-report/
include-hidden-files: true
retention-days: 30
overwrite: true
- name: Check diff
if: ${{ github.ref != 'refs/heads/main' }}
if: ${{ always() && github.ref != 'refs/heads/main' }}
shell: bash
id: git-check
run: |
@ -197,7 +197,7 @@ jobs:
fi
- name: Commit changes
if: ${{ steps.git-check.outputs.modified == 'true' }}
if: ${{ always() && steps.git-check.outputs.modified == 'true' }}
shell: bash
run: |
git add e2e/playwright/snapshot-tests.spec.ts-snapshots e2e/playwright/snapshots
@ -220,7 +220,7 @@ jobs:
include:
- os: "runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64"
- os: namespace-profile-macos-8-cores
- os: windows-latest-8-cores
- os: namespace-profile-windows-8-cores
runs-on: ${{ matrix.os }}
name: e2e:web (${{ contains(matrix.os, 'ubuntu') && 'ubuntu' || (contains(matrix.os, 'windows') && 'windows' || 'macos') }})
env:
@ -293,6 +293,7 @@ jobs:
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() && (success() || failure()) }}
with:
name: playwright-report-web-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }}
path: playwright-report/
include-hidden-files: true
retention-days: 30
@ -304,7 +305,6 @@ jobs:
strategy:
fail-fast: false
matrix:
# TODO: enable namespace-profile-windows-latest once available
include:
- os: "runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64"
shardIndex: 1
@ -336,10 +336,10 @@ jobs:
- os: namespace-profile-macos-8-cores
shardIndex: 2
shardTotal: 2
- os: windows-latest-8-cores
- os: namespace-profile-windows-8-cores
shardIndex: 1
shardTotal: 2
- os: windows-latest-8-cores
- os: namespace-profile-windows-8-cores
shardIndex: 2
shardTotal: 2
runs-on: ${{ matrix.os }}
@ -418,7 +418,7 @@ jobs:
- uses: actions/upload-artifact@v4
if: always()
with:
name: test-results-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }}
name: test-results-desktop-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }}
path: test-results/
include-hidden-files: true
retention-days: 30
@ -427,7 +427,7 @@ jobs:
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }}
name: playwright-report-desktop-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }}
path: playwright-report/
include-hidden-files: true
retention-days: 30

View File

@ -31,42 +31,42 @@ jobs:
- uses: actions/download-artifact@v4
with:
name: out-arm64-win
name: out-arm64-windows
path: out
run-id: ${{ steps.tag_workflow_id.outputs.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/download-artifact@v4
with:
name: out-x64-win
name: out-x64-windows
path: out
run-id: ${{ steps.tag_workflow_id.outputs.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/download-artifact@v4
with:
name: out-yml-win
name: out-yml-windows
path: out
run-id: ${{ steps.tag_workflow_id.outputs.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/download-artifact@v4
with:
name: out-arm64-mac
name: out-arm64-macos
path: out
run-id: ${{ steps.tag_workflow_id.outputs.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/download-artifact@v4
with:
name: out-x64-mac
name: out-x64-macos
path: out
run-id: ${{ steps.tag_workflow_id.outputs.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/download-artifact@v4
with:
name: out-yml-mac
name: out-yml-macos
path: out
run-id: ${{ steps.tag_workflow_id.outputs.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}

View File

@ -251,7 +251,8 @@ Before you submit a contribution PR to this repo, please ensure that:
#### 1. Create a 'Cut release $VERSION' issue
It will be used to document changelog discussions and release testing.
Use the **Release** issue template.
This will be used to facilitate changelog discussions and release testing.
https://github.com/KittyCAD/modeling-app/issues/new
@ -270,27 +271,9 @@ The workflow should be listed right away [in this list](https://github.com/Kitty
#### 3. Manually test artifacts
##### Release builds
The release builds can be found under the `out-{arch}-{platform}` zip files, at the very bottom of the `build-apps` summary page for the workflow (triggered by the tag in step 2).
Manually test against [this list](https://github.com/KittyCAD/modeling-app/issues/3588) across Windows, MacOS, Linux and posting results as comments in the issue.
A prompt should show up asking for a downgrade to the last release version. Running through that at the end of testing
and making sure the current release candidate has the ability to be updated to what electron-updater points to is critical,
but what is actually being downloaded and installed isn't.
If the prompt doesn't show up, start the app in command line to grab the electron-updater logs. This is likely an issue with the current build that needs addressing.
```
# Windows (PowerShell)
& 'C:\Program Files\Zoo Design Studio\Zoo Design Studio.exe'
# macOS
/Applications/Zoo\ Modeling\ App.app/Contents/MacOS/Zoo\ Modeling\ App
# Linux
./Zoo Design Studio-{version}-{arch}-linux.AppImage
```
Assign someone to each section of the manual checklist generated by the issue template.
#### 4. Bump the KCL version

View File

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 119 KiB

View File

Before

Width:  |  Height:  |  Size: 259 KiB

After

Width:  |  Height:  |  Size: 259 KiB

View File

@ -46,3 +46,7 @@ KCL has no support for area, volume, or other higher dimension units. When inter
## Explicit conversions
You might sometimes need to convert from one unit to another for some calculation. You can do this implicitly when calling a function (see above), but if you can't or don't want to, then you can use the explicit conversion functions in the [`std::units`](/docs/kcl-std/modules/std-units) module.
KCL cannot know about changes to units caused by arithmetic. For example, you may intend for `10in * 25.4` to be the value `254mm` (i.e., `10in` in mm), however, the result of that computation in KCL is `254in`. It is always better to rely on automatic conversion or to use the explicit conversion functions, where possible.
Converting between degrees and radians using π ([`PI`](/docs/kcl-std/consts/std-math-PI) in KCL) is especially prone to this error and so the `PI` constant always requires specifying units of any computation it is used with. E.g., `radius = (circumference / (2 * PI)): number(mm)`.

View File

@ -4,8 +4,6 @@ excerpt: "Project specific settings for the app. These live in `project.toml` in
layout: manual
---
# Project Settings
Project specific settings for the app. These live in `project.toml` in the base of the project directory. Updating the settings for the project in the app will update this file automatically. Do not edit this file manually, as it may be overwritten by the app. Manual edits can cause corruption of the settings file.
## Project Configuration Structure
@ -184,4 +182,4 @@ color = 240.0
# Use inches as the default measurement unit
base_unit = "in"
```
```

View File

@ -4,8 +4,6 @@ excerpt: "User specific settings for the app. These live in `user.toml` in the a
layout: manual
---
# User Settings
User specific settings for the app. These live in `user.toml` in the app's configuration directory. Updating the settings in the app will update this file automatically. Do not edit this file manually, as it may be overwritten by the app. Manual edits can cause corruption of the settings file.
## User Configuration Structure
@ -234,4 +232,4 @@ base_unit = "mm"
# Disable text wrapping in the editor
text_wrapping = false
```
```

File diff suppressed because one or more lines are too long

View File

@ -1,33 +0,0 @@
---
title: "KCL Constants"
excerpt: "Documentation for the KCL constants."
layout: manual
---
## Table of Contents
### `std`
- [`END`](/docs/kcl/consts/std-END)
- [`START`](/docs/kcl/consts/std-START)
- [`X`](/docs/kcl/consts/std-X)
- [`XY`](/docs/kcl/consts/std-XY)
- [`XZ`](/docs/kcl/consts/std-XZ)
- [`Y`](/docs/kcl/consts/std-Y)
- [`YZ`](/docs/kcl/consts/std-YZ)
- [`Z`](/docs/kcl/consts/std-Z)
### `std::math`
- [`E`](/docs/kcl/consts/std-math-E)
- [`PI`](/docs/kcl/consts/std-math-PI)
- [`TAU`](/docs/kcl/consts/std-math-TAU)
### `std::turns`
- [`HALF_TURN`](/docs/kcl/consts/std-turns-HALF_TURN)
- [`QUARTER_TURN`](/docs/kcl/consts/std-turns-QUARTER_TURN)
- [`THREE_QUARTER_TURN`](/docs/kcl/consts/std-turns-THREE_QUARTER_TURN)
- [`ZERO`](/docs/kcl/consts/std-turns-ZERO)

View File

@ -18,7 +18,7 @@ E: number = 2.71828182845904523536028747135266250_
```kcl
exampleSketch = startSketchOn(XZ)
|> startProfile(at = [0, 0])
|> angledLine(angle = 30, length = 2 * E ^ 2)
|> angledLine(angle = 30deg, length = 2 * E ^ 2)
|> yLine(endAbsolute = 0)
|> close()

View File

@ -16,8 +16,8 @@ However, `PI` is nearly always used for converting between different units - usu
from radians. Therefore, `PI` is treated a bit specially by KCL and always has unknown units. This
means that if you use `PI`, you will need to give KCL some extra information about the units of numbers.
Usually you should use type ascription on the result of calculations, e.g., `(2 * PI): number(rad)`.
You might prefer to use `units::toRadians` or `units::toDegrees` to convert between angles with
different units.
It is better to use `units::toRadians` or `units::toDegrees` to convert between angles with
different units where possible.
### Examples

View File

@ -18,7 +18,7 @@ TAU: number = 6.28318530717958647692528676655900577_
```kcl
exampleSketch = startSketchOn(XZ)
|> startProfile(at = [0, 0])
|> angledLine(angle = 50, length = 10 * TAU)
|> angledLine(angle = 50deg, length = 10 * TAU)
|> yLine(endAbsolute = 0)
|> close()

View File

@ -8,7 +8,7 @@ layout: manual
No turn, zero degrees/radians.
```kcl
turns::ZERO: number = 0
turns::ZERO
```

View File

@ -27,7 +27,7 @@ abs(@input: number): number
### Examples
```kcl
myAngle = -120
myAngle = -120deg
sketch001 = startSketchOn(XZ)
|> startProfile(at = [0, 0])

View File

@ -8,7 +8,7 @@ layout: manual
Compute the cosine of a number.
```kcl
cos(@num: number(Angle)): number(_)
cos(@num: number(Angle)): number
```
@ -21,7 +21,7 @@ cos(@num: number(Angle)): number(_)
### Returns
[`number(_)`](/docs/kcl-std/types/std-types-number) - A number.
[`number`](/docs/kcl-std/types/std-types-number) - A number.
### Examples
@ -29,7 +29,7 @@ cos(@num: number(Angle)): number(_)
```kcl
exampleSketch = startSketchOn(XZ)
|> startProfile(at = [0, 0])
|> angledLine(angle = 30, length = 3 / cos(30deg))
|> angledLine(angle = 30deg, length = 3 / cos(30deg))
|> yLine(endAbsolute = 0)
|> close()

View File

@ -29,7 +29,7 @@ max(@input: [number; 1+]): number
```kcl
exampleSketch = startSketchOn(XZ)
|> startProfile(at = [0, 0])
|> angledLine(angle = 70, length = max([15, 31, 4, 13, 22]))
|> angledLine(angle = 70deg, length = max([15, 31, 4, 13, 22]))
|> line(end = [20, 0])
|> close()

View File

@ -29,7 +29,7 @@ min(@input: [number; 1+]): number
```kcl
exampleSketch = startSketchOn(XZ)
|> startProfile(at = [0, 0])
|> angledLine(angle = 70, length = min([15, 31, 4, 13, 22]))
|> angledLine(angle = 70deg, length = min([15, 31, 4, 13, 22]))
|> line(end = [20, 0])
|> close()

View File

@ -33,7 +33,7 @@ polar(
```kcl
exampleSketch = startSketchOn(XZ)
|> startProfile(at = [0, 0])
|> line(end = polar(angle = 30, length = 5), tag = $thing)
|> line(end = polar(angle = 30deg, length = 5), tag = $thing)
|> line(end = [0, 5])
|> line(end = [segEndX(thing), 0])
|> line(end = [-20, 10])

View File

@ -33,7 +33,7 @@ pow(
```kcl
exampleSketch = startSketchOn(XZ)
|> startProfile(at = [0, 0])
|> angledLine(angle = 50, length = pow(5, exp = 2))
|> angledLine(angle = 50deg, length = pow(5, exp = 2))
|> yLine(endAbsolute = 0)
|> close()

View File

@ -8,7 +8,7 @@ layout: manual
Compute the sine of a number.
```kcl
sin(@num: number(Angle)): number(_)
sin(@num: number(Angle)): number
```
@ -21,7 +21,7 @@ sin(@num: number(Angle)): number(_)
### Returns
[`number(_)`](/docs/kcl-std/types/std-types-number) - A number.
[`number`](/docs/kcl-std/types/std-types-number) - A number.
### Examples
@ -29,7 +29,7 @@ sin(@num: number(Angle)): number(_)
```kcl
exampleSketch = startSketchOn(XZ)
|> startProfile(at = [0, 0])
|> angledLine(angle = 50, length = 15 / sin(135deg))
|> angledLine(angle = 50deg, length = 15 / sin(135deg))
|> yLine(endAbsolute = 0)
|> close()

View File

@ -29,7 +29,7 @@ sqrt(@input: number): number
```kcl
exampleSketch = startSketchOn(XZ)
|> startProfile(at = [0, 0])
|> angledLine(angle = 50, length = sqrt(2500))
|> angledLine(angle = 50deg, length = sqrt(2500))
|> yLine(endAbsolute = 0)
|> close()

View File

@ -8,7 +8,7 @@ layout: manual
Compute the tangent of a number.
```kcl
tan(@num: number(Angle)): number(_)
tan(@num: number(Angle)): number
```
@ -21,7 +21,7 @@ tan(@num: number(Angle)): number(_)
### Returns
[`number(_)`](/docs/kcl-std/types/std-types-number) - A number.
[`number`](/docs/kcl-std/types/std-types-number) - A number.
### Examples
@ -29,7 +29,7 @@ tan(@num: number(Angle)): number(_)
```kcl
exampleSketch = startSketchOn(XZ)
|> startProfile(at = [0, 0])
|> angledLine(angle = 50, length = 50 * tan((1 / 2): number(rad)))
|> angledLine(angle = 50deg, length = 50 * tan((1 / 2): number(rad)))
|> yLine(endAbsolute = 0)
|> close()

View File

@ -46,7 +46,7 @@ angledLine(
exampleSketch = startSketchOn(XZ)
|> startProfile(at = [0, 0])
|> yLine(endAbsolute = 15)
|> angledLine(angle = 30, length = 15)
|> angledLine(angle = 30deg, length = 15)
|> line(end = [8, -10])
|> yLine(endAbsolute = 0)
|> close()

View File

@ -42,7 +42,7 @@ exampleSketch = startSketchOn(XZ)
|> line(endAbsolute = [5, 10])
|> line(endAbsolute = [-10, 10], tag = $lineToIntersect)
|> line(endAbsolute = [0, 20])
|> angledLineThatIntersects(angle = 80, intersectTag = lineToIntersect, offset = 10)
|> angledLineThatIntersects(angle = 80deg, intersectTag = lineToIntersect, offset = 10)
|> close()
example = extrude(exampleSketch, length = 10)

View File

@ -53,7 +53,7 @@ for to construct your shape, you're likely looking for tangentialArc.
exampleSketch = startSketchOn(XZ)
|> startProfile(at = [0, 0])
|> line(end = [10, 0])
|> arc(angleStart = 0, angleEnd = 280, radius = 16)
|> arc(angleStart = 0, angleEnd = 280deg, radius = 16)
|> close()
example = extrude(exampleSketch, length = 10)

View File

@ -43,7 +43,7 @@ extruded in the same direction.
example = startSketchOn(XZ)
|> startProfile(at = [0, 0])
|> line(end = [10, 0])
|> arc(angleStart = 120, angleEnd = 0, radius = 5)
|> arc(angleStart = 120deg, angleEnd = 0, radius = 5)
|> line(end = [5, 0])
|> line(end = [0, 10])
|> bezierCurve(control1 = [-10, 0], control2 = [2, 10], end = [-5, 10])
@ -58,7 +58,7 @@ example = startSketchOn(XZ)
```kcl
exampleSketch = startSketchOn(XZ)
|> startProfile(at = [-10, 0])
|> arc(angleStart = 120, angleEnd = -60, radius = 5)
|> arc(angleStart = 120deg, angleEnd = -60deg, radius = 5)
|> line(end = [10, 0])
|> line(end = [5, 0])
|> bezierCurve(control1 = [-3, 0], control2 = [2, 10], end = [-5, 10])
@ -75,7 +75,7 @@ example = extrude(exampleSketch, length = 10)
```kcl
exampleSketch = startSketchOn(XZ)
|> startProfile(at = [-10, 0])
|> arc(angleStart = 120, angleEnd = -60, radius = 5)
|> arc(angleStart = 120deg, angleEnd = -60deg, radius = 5)
|> line(end = [10, 0])
|> line(end = [5, 0])
|> bezierCurve(control1 = [-3, 0], control2 = [2, 10], end = [-5, 10])
@ -92,7 +92,7 @@ example = extrude(exampleSketch, length = 20, symmetric = true)
```kcl
exampleSketch = startSketchOn(XZ)
|> startProfile(at = [-10, 0])
|> arc(angleStart = 120, angleEnd = -60, radius = 5)
|> arc(angleStart = 120deg, angleEnd = -60deg, radius = 5)
|> line(end = [10, 0])
|> line(end = [5, 0])
|> bezierCurve(control1 = [-3, 0], control2 = [2, 10], end = [-5, 10])

File diff suppressed because one or more lines are too long

View File

@ -30,10 +30,10 @@ getNextAdjacentEdge(@edge: tag): Edge
exampleSketch = startSketchOn(XZ)
|> startProfile(at = [0, 0])
|> line(end = [10, 0])
|> angledLine(angle = 60, length = 10)
|> angledLine(angle = 120, length = 10)
|> angledLine(angle = 60deg, length = 10)
|> angledLine(angle = 120deg, length = 10)
|> line(end = [-10, 0])
|> angledLine(angle = 240, length = 10, tag = $referenceEdge)
|> angledLine(angle = 240deg, length = 10, tag = $referenceEdge)
|> close()
example = extrude(exampleSketch, length = 5)

View File

@ -30,10 +30,10 @@ getOppositeEdge(@edge: tag): Edge
exampleSketch = startSketchOn(XZ)
|> startProfile(at = [0, 0])
|> line(end = [10, 0])
|> angledLine(angle = 60, length = 10)
|> angledLine(angle = 120, length = 10)
|> angledLine(angle = 60deg, length = 10)
|> angledLine(angle = 120deg, length = 10)
|> line(end = [-10, 0])
|> angledLine(angle = 240, length = 10, tag = $referenceEdge)
|> angledLine(angle = 240deg, length = 10, tag = $referenceEdge)
|> close()
example = extrude(exampleSketch, length = 5)

View File

@ -30,10 +30,10 @@ getPreviousAdjacentEdge(@edge: tag): Edge
exampleSketch = startSketchOn(XZ)
|> startProfile(at = [0, 0])
|> line(end = [10, 0])
|> angledLine(angle = 60, length = 10)
|> angledLine(angle = 120, length = 10)
|> angledLine(angle = 60deg, length = 10)
|> angledLine(angle = 120deg, length = 10)
|> line(end = [-10, 0])
|> angledLine(angle = 240, length = 10, tag = $referenceEdge)
|> angledLine(angle = 240deg, length = 10, tag = $referenceEdge)
|> close()
example = extrude(exampleSketch, length = 5)

View File

@ -43,11 +43,11 @@ a = 10
b = 14
startSketchOn(XZ)
|> startProfile(at = [0, 0])
|> involuteCircular(startRadius = a, endRadius = b, angle = 60)
|> involuteCircular(startRadius = a, endRadius = b, angle = 60deg)
|> involuteCircular(
startRadius = a,
endRadius = b,
angle = 60,
angle = 60deg,
reverse = true,
)

View File

@ -30,7 +30,7 @@ profileStart(@profile: Sketch): Point2d
sketch001 = startSketchOn(XY)
|> startProfile(at = [5, 2])
|> angledLine(angle = 120, length = 50, tag = $seg01)
|> angledLine(angle = segAng(seg01) + 120, length = 50)
|> angledLine(angle = segAng(seg01) + 120deg, length = 50)
|> line(end = profileStart(%))
|> close()
|> extrude(length = 20)

View File

@ -30,8 +30,8 @@ profileStartX(@profile: Sketch): number(Length)
sketch001 = startSketchOn(XY)
|> startProfile(at = [5, 2])
|> angledLine(angle = -26.6, length = 50)
|> angledLine(angle = 90, length = 50)
|> angledLine(angle = 30, endAbsoluteX = profileStartX(%))
|> angledLine(angle = 90deg, length = 50)
|> angledLine(angle = 30deg, endAbsoluteX = profileStartX(%))
```

View File

@ -29,8 +29,8 @@ profileStartY(@profile: Sketch): number(Length)
```kcl
sketch001 = startSketchOn(XY)
|> startProfile(at = [5, 2])
|> angledLine(angle = -60, length = 14)
|> angledLine(angle = 30, endAbsoluteY = profileStartY(%))
|> angledLine(angle = -60deg, length = 14)
|> angledLine(angle = 30deg, endAbsoluteY = profileStartY(%))
```

View File

@ -62,7 +62,7 @@ part001 = startSketchOn(XY)
|> line(end = [0, -5.5])
|> line(end = [-2, 0])
|> close()
|> revolve(axis = Y) // default angle is 360
|> revolve(axis = Y) // default angle is 360deg
```
@ -72,7 +72,7 @@ part001 = startSketchOn(XY)
// A donut shape.
sketch001 = startSketchOn(XY)
|> circle(center = [15, 0], radius = 5)
|> revolve(angle = 360, axis = Y)
|> revolve(angle = 360deg, axis = Y)
```
@ -89,7 +89,7 @@ part001 = startSketchOn(XY)
|> line(end = [0, -5.5])
|> line(end = [-2, 0])
|> close()
|> revolve(axis = Y, angle = 180)
|> revolve(axis = Y, angle = 180deg)
```
@ -106,7 +106,7 @@ part001 = startSketchOn(XY)
|> line(end = [0, -5.5])
|> line(end = [-2, 0])
|> close()
|> revolve(axis = Y, angle = 180)
|> revolve(axis = Y, angle = 180deg)
part002 = startSketchOn(part001, face = END)
|> startProfile(at = [4.5, -5])
@ -131,7 +131,7 @@ box = startSketchOn(XY)
sketch001 = startSketchOn(box, face = END)
|> circle(center = [10, 10], radius = 4)
|> revolve(angle = -90, axis = Y)
|> revolve(angle = -90deg, axis = Y)
```
@ -148,7 +148,7 @@ box = startSketchOn(XY)
sketch001 = startSketchOn(box, face = END)
|> circle(center = [10, 10], radius = 4)
|> revolve(angle = 90, axis = getOppositeEdge(revolveAxis))
|> revolve(angle = 90deg, axis = getOppositeEdge(revolveAxis))
```
@ -165,7 +165,7 @@ box = startSketchOn(XY)
sketch001 = startSketchOn(box, face = END)
|> circle(center = [10, 10], radius = 4)
|> revolve(angle = 90, axis = getOppositeEdge(revolveAxis), tolerance = 0.0001)
|> revolve(angle = 90deg, axis = getOppositeEdge(revolveAxis), tolerance = 0.0001)
```
@ -229,7 +229,7 @@ profile001 = startSketchOn(XY)
sketch001 = startSketchOn(XY)
|> circle(center = [-10, 10], radius = 4)
|> revolve(angle = 90, axis = revolveAxis)
|> revolve(angle = 90deg, axis = revolveAxis)
```
@ -246,7 +246,7 @@ profile001 = startSketchOn(XY)
sketch001 = startSketchOn(XY)
|> circle(center = [-10, 10], radius = 4)
|> revolve(angle = 90, axis = revolveAxis)
|> revolve(angle = 90deg, axis = revolveAxis)
```
@ -263,7 +263,7 @@ profile001 = startSketchOn(XY)
sketch001 = startSketchOn(XY)
|> circle(center = [-10, 10], radius = 4)
|> revolve(angle = 90, axis = revolveAxis, symmetric = true)
|> revolve(angle = 90deg, axis = revolveAxis, symmetric = true)
```
@ -280,7 +280,7 @@ profile001 = startSketchOn(XY)
sketch001 = startSketchOn(XY)
|> circle(center = [-10, 10], radius = 4)
|> revolve(angle = 90, axis = revolveAxis, bidirectionalAngle = 50)
|> revolve(angle = 90deg, axis = revolveAxis, bidirectionalAngle = 50)
```

View File

@ -30,8 +30,8 @@ segLen(@tag: tag): number(Length)
exampleSketch = startSketchOn(XZ)
|> startProfile(at = [0, 0])
|> angledLine(angle = 60, length = 10, tag = $thing)
|> tangentialArc(angle = -120, radius = 5)
|> angledLine(angle = -60, length = segLen(thing))
|> tangentialArc(angle = -120deg, radius = 5)
|> angledLine(angle = -60deg, length = segLen(thing))
|> close()
example = extrude(exampleSketch, length = 5)

View File

@ -156,7 +156,7 @@ exampleSketch = startSketchOn(XY)
|> line(end = [-2, 0])
|> close()
example = revolve(exampleSketch, axis = Y, angle = 180)
example = revolve(exampleSketch, axis = Y, angle = 180deg)
exampleSketch002 = startSketchOn(example, face = END)
|> startProfile(at = [4.5, -5])
@ -189,7 +189,7 @@ exampleSketch = startSketchOn(XY)
example = revolve(
exampleSketch,
axis = Y,
angle = 180,
angle = 180deg,
tagEnd = $end01,
)

View File

@ -54,9 +54,9 @@ swept along the same path.
sweepPath = startSketchOn(XZ)
|> startProfile(at = [0.05, 0.05])
|> line(end = [0, 7])
|> tangentialArc(angle = 90, radius = 5)
|> tangentialArc(angle = 90deg, radius = 5)
|> line(end = [-3, 0])
|> tangentialArc(angle = -90, radius = 5)
|> tangentialArc(angle = -90deg, radius = 5)
|> line(end = [0, 7])
// Create a hole for the pipe.
@ -101,7 +101,7 @@ springSketch = startSketchOn(XZ)
sketch001 = startSketchOn(XY)
rectangleSketch = startProfile(sketch001, at = [-200, 23.86])
|> angledLine(angle = 0, length = 73.47, tag = $rectangleSegmentA001)
|> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 50.61)
|> angledLine(angle = segAng(rectangleSegmentA001) - 90deg, length = 50.61)
|> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001))
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
@ -111,7 +111,7 @@ circleSketch = circle(sketch001, center = [200, -30.29], radius = 32.63)
sketch002 = startSketchOn(YZ)
sweepPath = startProfile(sketch002, at = [0, 0])
|> yLine(length = 231.81)
|> tangentialArc(radius = 80, angle = -90)
|> tangentialArc(radius = 80, angle = -90deg)
|> xLine(length = 384.93)
sweep([rectangleSketch, circleSketch], path = sweepPath)
@ -130,7 +130,7 @@ circleSketch = circle(sketch001, center = [200, -30.29], radius = 32.63)
sketch002 = startSketchOn(YZ)
sweepPath = startProfile(sketch002, at = [0, 0])
|> yLine(length = 231.81)
|> tangentialArc(radius = 80, angle = -90)
|> tangentialArc(radius = 80, angle = -90deg)
|> xLine(length = 384.93)
sweep(circleSketch, path = sweepPath, sectional = true)

View File

@ -47,7 +47,7 @@ for 'angle' degrees along the imaginary circle.
```kcl
exampleSketch = startSketchOn(XZ)
|> startProfile(at = [0, 0])
|> angledLine(angle = 45, length = 10)
|> angledLine(angle = 45deg, length = 10)
|> tangentialArc(end = [0, -10])
|> line(end = [-10, 0])
|> close()
@ -61,7 +61,7 @@ example = extrude(exampleSketch, length = 10)
```kcl
exampleSketch = startSketchOn(XZ)
|> startProfile(at = [0, 0])
|> angledLine(angle = 60, length = 10)
|> angledLine(angle = 60deg, length = 10)
|> tangentialArc(endAbsolute = [15, 15])
|> line(end = [10, -15])
|> close()
@ -75,9 +75,9 @@ example = extrude(exampleSketch, length = 10)
```kcl
exampleSketch = startSketchOn(XZ)
|> startProfile(at = [0, 0])
|> angledLine(angle = 60, length = 10)
|> tangentialArc(radius = 10, angle = -120)
|> angledLine(angle = -60, length = 10)
|> angledLine(angle = 60deg, length = 10)
|> tangentialArc(radius = 10, angle = -120deg)
|> angledLine(angle = -60deg, length = 10)
|> close()
example = extrude(exampleSketch, length = 10)

View File

@ -38,10 +38,10 @@ xLine(
exampleSketch = startSketchOn(XZ)
|> startProfile(at = [0, 0])
|> xLine(length = 15)
|> angledLine(angle = 80, length = 15)
|> angledLine(angle = 80deg, length = 15)
|> line(end = [8, -10])
|> xLine(length = 10)
|> angledLine(angle = 120, length = 30)
|> angledLine(angle = 120deg, length = 30)
|> xLine(length = -15)
|> close()

View File

@ -38,7 +38,7 @@ yLine(
exampleSketch = startSketchOn(XZ)
|> startProfile(at = [0, 0])
|> yLine(length = 15)
|> angledLine(angle = 30, length = 15)
|> angledLine(angle = 30deg, length = 15)
|> line(end = [8, -10])
|> yLine(length = -5)
|> close()

View File

@ -55,7 +55,7 @@ example = extrude(exampleSketch, length = 5)
// Add color to a revolved solid.
sketch001 = startSketchOn(XY)
|> circle(center = [15, 0], radius = 5)
|> revolve(angle = 360, axis = Y)
|> revolve(angle = 360deg, axis = Y)
|> appearance(color = '#ff0000', metalness = 90, roughness = 90)
```
@ -196,9 +196,9 @@ example = extrude(exampleSketch, length = 1)
sweepPath = startSketchOn(XZ)
|> startProfile(at = [0.05, 0.05])
|> line(end = [0, 7])
|> tangentialArc(angle = 90, radius = 5)
|> tangentialArc(angle = 90deg, radius = 5)
|> line(end = [-3, 0])
|> tangentialArc(angle = -90, radius = 5)
|> tangentialArc(angle = -90deg, radius = 5)
|> line(end = [0, 7])
pipeHole = startSketchOn(XY)

View File

@ -50,7 +50,7 @@ Its properties are:
- `rotation.axis` (a 3D point, defaults to the Z axis)
- `rotation.angle` (number of degrees)
- `rotation.angle`
- `rotation.origin` (either "local" i.e. rotate around its own center, "global" i.e. rotate around the scene's center, or a 3D point, defaults to "local")
@ -135,7 +135,7 @@ fn transform(@i) {
pow(0.9, exp = i)
],
// Turn by 15 degrees each time.
rotation = { angle = 15 * i, origin = "local" }
rotation = { angle = 15deg * i, origin = "local" }
}
}
@ -171,7 +171,7 @@ fn transform(@i) {
return {
translate = [0, 0, -i * width],
rotation = {
angle = 90 * i,
angle = 90deg * i,
// Rotate around the overall scene's origin.
origin = "global"
}
@ -219,7 +219,7 @@ fn transform(@i) {
// Transform functions can return multiple transforms. They'll be applied in order.
return [
{ translate = [30 * i, 0, 0] },
{ rotation = { angle = 45 * i } }
{ rotation = { angle = 45deg * i } }
]
}
startSketchOn(XY)

View File

@ -72,9 +72,9 @@ rotation.
sweepPath = startSketchOn(XZ)
|> startProfile(at = [0.05, 0.05])
|> line(end = [0, 7])
|> tangentialArc(angle = 90, radius = 5)
|> tangentialArc(angle = 90deg, radius = 5)
|> line(end = [-3, 0])
|> tangentialArc(angle = -90, radius = 5)
|> tangentialArc(angle = -90deg, radius = 5)
|> line(end = [0, 7])
// Create a hole for the pipe.
@ -98,9 +98,9 @@ sweepSketch = startSketchOn(XY)
sweepPath = startSketchOn(XZ)
|> startProfile(at = [0.05, 0.05])
|> line(end = [0, 7])
|> tangentialArc(angle = 90, radius = 5)
|> tangentialArc(angle = 90deg, radius = 5)
|> line(end = [-3, 0])
|> tangentialArc(angle = -90, radius = 5)
|> tangentialArc(angle = -90deg, radius = 5)
|> line(end = [0, 7])
// Create a hole for the pipe.
@ -124,9 +124,9 @@ sweepSketch = startSketchOn(XY)
sweepPath = startSketchOn(XZ)
|> startProfile(at = [0.05, 0.05])
|> line(end = [0, 7])
|> tangentialArc(angle = 90, radius = 5)
|> tangentialArc(angle = 90deg, radius = 5)
|> line(end = [-3, 0])
|> tangentialArc(angle = -90, radius = 5)
|> tangentialArc(angle = -90deg, radius = 5)
|> line(end = [0, 7])
// Create a hole for the pipe.
@ -137,7 +137,7 @@ sweepSketch = startSketchOn(XY)
|> circle(center = [0, 0], radius = 2)
|> subtract2d(tool = pipeHole)
|> sweep(path = sweepPath)
|> rotate(axis = Z, angle = 90)
|> rotate(axis = Z, angle = 90deg)
```
@ -150,7 +150,7 @@ sweepSketch = startSketchOn(XY)
import "tests/inputs/cube.sldprt" as cube
cube
|> rotate(axis = [0, 0, 1.0], angle = 9)
|> rotate(axis = [0, 0, 1.0], angle = 9deg)
```
@ -163,9 +163,9 @@ cube
sweepPath = startSketchOn(XZ)
|> startProfile(at = [0.05, 0.05])
|> line(end = [0, 7])
|> tangentialArc(angle = 90, radius = 5)
|> tangentialArc(angle = 90deg, radius = 5)
|> line(end = [-3, 0])
|> tangentialArc(angle = -90, radius = 5)
|> tangentialArc(angle = -90deg, radius = 5)
|> line(end = [0, 7])
// Create a hole for the pipe.
@ -176,7 +176,7 @@ sweepSketch = startSketchOn(XY)
|> circle(center = [0, 0], radius = 2)
|> subtract2d(tool = pipeHole)
|> sweep(path = sweepPath)
|> rotate(axis = [0, 0, 1.0], angle = 90)
|> rotate(axis = [0, 0, 1.0], angle = 90deg)
```
@ -199,13 +199,13 @@ circleSketch = circle(sketch001, center = [200, -30.29], radius = 32.63)
sketch002 = startSketchOn(YZ)
sweepPath = startProfile(sketch002, at = [0, 0])
|> yLine(length = 231.81)
|> tangentialArc(radius = 80, angle = -90)
|> tangentialArc(radius = 80, angle = -90deg)
|> xLine(length = 384.93)
parts = sweep([rectangleSketch, circleSketch], path = sweepPath)
// Rotate the sweeps.
rotate(parts, axis = [0, 0, 1.0], angle = 90)
rotate(parts, axis = [0, 0, 1.0], angle = 90deg)
```
@ -228,7 +228,7 @@ profile001 = square()
profile002 = square()
|> translate(x = 0, y = 0, z = 20)
|> rotate(axis = [0, 0, 1.0], angle = 45)
|> rotate(axis = [0, 0, 1.0], angle = 45deg)
loft([profile001, profile002])

View File

@ -54,9 +54,9 @@ look like the model moves and gets bigger at the same time. Say you have a squar
sweepPath = startSketchOn(XZ)
|> startProfile(at = [0.05, 0.05])
|> line(end = [0, 7])
|> tangentialArc(angle = 90, radius = 5)
|> tangentialArc(angle = 90deg, radius = 5)
|> line(end = [-3, 0])
|> tangentialArc(angle = -90, radius = 5)
|> tangentialArc(angle = -90deg, radius = 5)
|> line(end = [0, 7])
// Create a hole for the pipe.
@ -93,7 +93,7 @@ cube
sketch001 = startSketchOn(XY)
rectangleSketch = startProfile(sketch001, at = [-200, 23.86])
|> angledLine(angle = 0, length = 73.47, tag = $rectangleSegmentA001)
|> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 50.61)
|> angledLine(angle = segAng(rectangleSegmentA001) - 90deg, length = 50.61)
|> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001))
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
@ -103,7 +103,7 @@ circleSketch = circle(sketch001, center = [200, -30.29], radius = 32.63)
sketch002 = startSketchOn(YZ)
sweepPath = startProfile(sketch002, at = [0, 0])
|> yLine(length = 231.81)
|> tangentialArc(radius = 80, angle = -90)
|> tangentialArc(radius = 80, angle = -90deg)
|> xLine(length = 384.93)
parts = sweep([rectangleSketch, circleSketch], path = sweepPath)

View File

@ -47,9 +47,9 @@ and then rotate it using the `rotate` function to create a loft.
sweepPath = startSketchOn(XZ)
|> startProfile(at = [0.05, 0.05])
|> line(end = [0, 7])
|> tangentialArc(angle = 90, radius = 5)
|> tangentialArc(angle = 90deg, radius = 5)
|> line(end = [-3, 0])
|> tangentialArc(angle = -90, radius = 5)
|> tangentialArc(angle = -90deg, radius = 5)
|> line(end = [0, 7])
// Create a hole for the pipe.
@ -91,7 +91,7 @@ cube
sketch001 = startSketchOn(XY)
rectangleSketch = startProfile(sketch001, at = [-200, 23.86])
|> angledLine(angle = 0, length = 73.47, tag = $rectangleSegmentA001)
|> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 50.61)
|> angledLine(angle = segAng(rectangleSegmentA001) - 90deg, length = 50.61)
|> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001))
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
@ -101,7 +101,7 @@ circleSketch = circle(sketch001, center = [200, -30.29], radius = 32.63)
sketch002 = startSketchOn(YZ)
sweepPath = startProfile(sketch002, at = [0, 0])
|> yLine(length = 231.81)
|> tangentialArc(radius = 80, angle = -90)
|> tangentialArc(radius = 80, angle = -90deg)
|> xLine(length = 384.93)
parts = sweep([rectangleSketch, circleSketch], path = sweepPath)
@ -162,7 +162,7 @@ profile001 = square()
profile002 = square()
|> translate(z = 20)
|> rotate(axis = [0, 0, 1.0], angle = 45)
|> rotate(axis = [0, 0, 1.0], angle = 45deg)
loft([profile001, profile002])

View File

@ -29,7 +29,7 @@ units::toDegrees(@num: number(Angle)): number(deg)
```kcl
exampleSketch = startSketchOn(XZ)
|> startProfile(at = [0, 0])
|> angledLine(angle = 50, length = 70 * cos(units::toDegrees((PI / 4): number(rad))))
|> angledLine(angle = 50deg, length = 70 * cos(units::toDegrees((PI / 4): number(rad))))
|> yLine(endAbsolute = 0)
|> close()

View File

@ -29,7 +29,7 @@ units::toRadians(@num: number(Angle)): number(rad)
```kcl
exampleSketch = startSketchOn(XZ)
|> startProfile(at = [0, 0])
|> angledLine(angle = 50, length = 70 * cos(units::toRadians(45)))
|> angledLine(angle = 50deg, length = 70 * cos(units::toRadians(45deg)))
|> yLine(endAbsolute = 0)
|> close()

View File

@ -55,6 +55,7 @@ layout: manual
* [`circleThreePoint`](/docs/kcl-std/functions/std-sketch-circleThreePoint)
* [`close`](/docs/kcl-std/functions/std-sketch-close)
* [`extrude`](/docs/kcl-std/functions/std-sketch-extrude)
* [`extrudeTwist`](/docs/kcl-std/functions/std-sketch-extrudeTwist)
* [`getCommonEdge`](/docs/kcl-std/functions/std-sketch-getCommonEdge)
* [`getNextAdjacentEdge`](/docs/kcl-std/functions/std-sketch-getNextAdjacentEdge)
* [`getOppositeEdge`](/docs/kcl-std/functions/std-sketch-getOppositeEdge)

View File

@ -20,6 +20,7 @@ This module contains functions for creating and manipulating sketches, and makin
* [`circleThreePoint`](/docs/kcl-std/functions/std-sketch-circleThreePoint)
* [`close`](/docs/kcl-std/functions/std-sketch-close)
* [`extrude`](/docs/kcl-std/functions/std-sketch-extrude)
* [`extrudeTwist`](/docs/kcl-std/functions/std-sketch-extrudeTwist)
* [`getCommonEdge`](/docs/kcl-std/functions/std-sketch-getCommonEdge)
* [`getNextAdjacentEdge`](/docs/kcl-std/functions/std-sketch-getNextAdjacentEdge)
* [`getOppositeEdge`](/docs/kcl-std/functions/std-sketch-getOppositeEdge)

File diff suppressed because one or more lines are too long

View File

@ -17,7 +17,7 @@ startSketchOn(XZ)
|> startProfile(at = origin)
|> angledLine(angle = 0, length = 191.26, tag = $rectangleSegmentA001)
|> angledLine(
angle = segAng(rectangleSegmentA001) - 90,
angle = segAng(rectangleSegmentA001) - 90deg,
length = 196.99,
tag = $rectangleSegmentB001,
)
@ -80,7 +80,7 @@ fn rect(origin) {
|> startProfile(at = origin)
|> angledLine(angle = 0, length = 191.26, tag = $rectangleSegmentA001)
|> angledLine(
angle = segAng(rectangleSegmentA001) - 90,
angle = segAng(rectangleSegmentA001) - 90deg,
length = 196.99
tag = $rectangleSegmentB001,
)

View File

@ -302,7 +302,7 @@ test.describe('Command bar tests', () => {
// Assert that the an alternative variable name is chosen,
// since the default variable name is already in use (distance)
await page.getByRole('button', { name: 'Create new variable' }).click()
await cmdBar.variableCheckbox.click()
await expect(page.getByPlaceholder('Variable name')).toHaveValue(
'length001'
)

View File

@ -281,10 +281,7 @@ test.describe('Feature Tree pane', () => {
await test.step('Add a named constant for distance argument and submit', async () => {
await expect(cmdBar.currentArgumentInput).toBeVisible()
const addVariableButton = page.getByRole('button', {
name: 'Create new variable',
})
await addVariableButton.click()
await cmdBar.variableCheckbox.click()
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',

View File

@ -172,6 +172,10 @@ export class CmdBarFixture {
return this.page.getByTestId('cmd-bar-arg-value')
}
get variableCheckbox() {
return this.page.getByTestId('cmd-bar-variable-checkbox')
}
get cmdOptions() {
return this.page.getByTestId('cmd-bar-option')
}
@ -191,7 +195,7 @@ export class CmdBarFixture {
* Clicks the Create new variable button for kcl input
*/
createNewVariable = async () => {
await this.page.getByRole('button', { name: 'Create new variable' }).click()
await this.variableCheckbox.click()
}
/**

View File

@ -3807,7 +3807,7 @@ sketch002 = startSketchOn(extrude001, face = rectangleSegmentA001)
stage: 'arguments',
})
await page.keyboard.insertText(newAngle)
await page.getByRole('button', { name: 'Create new variable' }).click()
await cmdBar.variableCheckbox.click()
await expect(page.getByPlaceholder('Variable name')).toHaveValue(
'angle001'
)

View File

@ -170,7 +170,7 @@ test(
// error text on hover
await page.hover('.cm-lint-marker-error')
const crypticErrorText =
'tag requires a value with type `tag`, but found string'
'tag requires a value with type `tag`, but found a value with type `string`.'
await expect(page.getByText(crypticErrorText).first()).toBeVisible()
// black pixel means the scene has been cleared.
@ -369,7 +369,7 @@ test(
// error text on hover
await page.hover('.cm-lint-marker-error')
const crypticErrorText =
'tag requires a value with type `tag`, but found string'
'tag requires a value with type `tag`, but found a value with type `string`.'
await expect(page.getByText(crypticErrorText).first()).toBeVisible()
// black pixel means the scene has been cleared.
@ -408,7 +408,7 @@ test(
// error text on hover
await page.hover('.cm-lint-marker-error')
const crypticErrorText =
'tag requires a value with type `tag`, but found string'
'tag requires a value with type `tag`, but found a value with type `string`.'
await expect(page.getByText(crypticErrorText).first()).toBeVisible()
}
)

View File

@ -1,45 +1,54 @@
import { expect, test } from '@e2e/playwright/zoo-test'
import type { Page } from '@playwright/test'
async function navigateAndClickOpenInDesktopApp(
page: Page,
codeLength: number
) {
const code = Array(codeLength).fill('0').join('')
const targetURL = `?create-file=true&browser=test&code=${code}&ask-open-desktop=true`
expect(targetURL.length).toEqual(codeLength + 58)
await page.goto(page.url() + targetURL)
expect(page.url()).toContain(targetURL)
const button = page.getByRole('button', { name: 'Open in desktop app' })
await button.click()
}
function getToastError(page: Page) {
return page.getByText('The URL is too long to open in the desktop app')
}
const isWindows =
navigator.platform === 'Windows' || navigator.platform === 'Win32'
test.describe('Share link tests', () => {
;[
{
codeLength: 1000,
showsErrorOnWindows: false,
},
{
codeLength: 2000,
showsErrorOnWindows: true,
},
].forEach(({ codeLength, showsErrorOnWindows }) => {
test(
`Open in desktop app with ${codeLength}-long code ${isWindows && showsErrorOnWindows ? 'shows error' : "doesn't show error"}`,
{ tag: ['@web'] },
async ({ page }) => {
if (process.env.TARGET !== 'web') {
// This test is web-only
// TODO: re-enable on CI as part of a new @web test suite
return
}
test(
`Open in desktop app with 2000-long code works non-Windows`,
{ tag: ['@web', '@macos', '@linux'] },
async ({ page }) => {
test.skip(process.platform === 'win32')
const codeLength = 2000
await navigateAndClickOpenInDesktopApp(page, codeLength)
await expect(getToastError(page)).not.toBeVisible()
}
)
const code = Array(codeLength).fill('0').join('')
const targetURL = `?create-file=true&browser=test&code=${code}&ask-open-desktop=true`
expect(targetURL.length).toEqual(codeLength + 58)
await page.goto(page.url() + targetURL)
expect(page.url()).toContain(targetURL)
const button = page.getByRole('button', { name: 'Open in desktop app' })
await button.click()
const toastError = page.getByText(
'The URL is too long to open in the desktop app on Windows'
)
if (isWindows && showsErrorOnWindows) {
await expect(toastError).toBeVisible()
} else {
await expect(toastError).not.toBeVisible()
// TODO: check if we could verify the deep link dialog shows up
}
}
)
})
test(
`Open in desktop app with 1000-long code works on Windows`,
{ tag: ['@web', '@windows'] },
async ({ page }) => {
test.skip(process.platform !== 'win32')
const codeLength = 1000
await navigateAndClickOpenInDesktopApp(page, codeLength)
await expect(getToastError(page)).not.toBeVisible()
}
)
test(
`Open in desktop app with 2000-long code doesn't work on Windows`,
{ tag: ['@web', '@windows'] },
async ({ page }) => {
test.skip(process.platform !== 'win32')
const codeLength = 2000
await navigateAndClickOpenInDesktopApp(page, codeLength)
await expect(getToastError(page)).toBeVisible()
}
)
})

View File

@ -3433,6 +3433,71 @@ profile003 = startProfile(sketch002, at = [-201.08, 254.17])
).toBeVisible()
})
})
test('Will exit out of sketch mode when all code is nuked', async ({
page,
context,
homePage,
scene,
editor,
toolbar,
cmdBar,
}) => {
const initialCode = `myVar1 = 5
myVar2 = 6
sketch001 = startSketchOn(XZ)
profile001 = startProfile(sketch001, at = [106.68, 89.77])
|> line(end = [132.34, 157.8])
|> line(end = [67.65, -460.55], tag = $seg01)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
extrude001 = extrude(profile001, length = 500)
sketch002 = startSketchOn(extrude001, face = seg01)
profile002 = startProfile(sketch002, at = [83.39, 329.15])
|> angledLine(angle = 0, length = 119.61, tag = $rectangleSegmentA001)
|> angledLine(length = 156.54, angle = -28)
|> angledLine(
angle = -151,
length = 116.27,
)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
profile003 = startProfile(sketch002, at = [-201.08, 254.17])
|> line(end = [103.55, 33.32])
|> line(end = [48.8, -153.54])`
await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode)
}, initialCode)
await homePage.goToModelingScene()
await scene.connectionEstablished()
await scene.settled(cmdBar)
const expectSketchOriginToBeDrawn = async () => {
await scene.expectPixelColor(TEST_COLORS.WHITE, { x: 672, y: 193 }, 15)
}
await test.step('Open feature tree and edit second sketch', async () => {
await toolbar.openFeatureTreePane()
const sketchButton = await toolbar.getFeatureTreeOperation('Sketch', 1)
await sketchButton.dblclick()
await page.waitForTimeout(700) // Wait for engine animation
await expectSketchOriginToBeDrawn()
})
await test.step('clear editor content while in sketch mode', async () => {
await editor.replaceCode('', '')
await page.waitForTimeout(100)
await expect(
page.getByText('Unable to maintain sketch mode')
).toBeVisible()
await scene.expectPixelColorNotToBe(
TEST_COLORS.WHITE,
{ x: 672, y: 193 },
15
)
})
})
test('empty draft sketch is cleaned up properly', async ({
scene,
toolbar,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View File

@ -1198,3 +1198,174 @@ export async function enableConsoleLogEverything({
console.log(`[Main] ${msg.type()}: ${msg.text()}`)
})
}
/**
* Simulate a pan touch gesture from the center of an element.
*
* Adapted from Playwright docs: https://playwright.dev/docs/touch-events
*/
export async function panFromCenter(
locator: Locator,
deltaX = 0,
deltaY = 0,
steps = 5
) {
const { centerX, centerY } = await locator.evaluate((target: HTMLElement) => {
const bounds = target.getBoundingClientRect()
const centerX = bounds.left + bounds.width / 2
const centerY = bounds.top + bounds.height / 2
return { centerX, centerY }
})
// Providing only clientX and clientY as the app only cares about those.
const touches = [
{
identifier: 0,
clientX: centerX,
clientY: centerY,
},
]
await locator.dispatchEvent('touchstart', {
touches,
changedTouches: touches,
targetTouches: touches,
})
for (let j = 1; j <= steps; j++) {
const touches = [
{
identifier: 0,
clientX: centerX + (deltaX * j) / steps,
clientY: centerY + (deltaY * j) / steps,
},
]
await locator.dispatchEvent('touchmove', {
touches,
changedTouches: touches,
targetTouches: touches,
})
}
await locator.dispatchEvent('touchend')
}
/**
* Simulate a 2-finger pan touch gesture from the center of an element.
* with {touchSpacing} pixels between.
*
* Adapted from Playwright docs: https://playwright.dev/docs/touch-events
*/
export async function panTwoFingerFromCenter(
locator: Locator,
deltaX = 0,
deltaY = 0,
steps = 5,
spacingX = 20
) {
const { centerX, centerY } = await locator.evaluate((target: HTMLElement) => {
const bounds = target.getBoundingClientRect()
const centerX = bounds.left + bounds.width / 2
const centerY = bounds.top + bounds.height / 2
return { centerX, centerY }
})
// Providing only clientX and clientY as the app only cares about those.
const touches = [
{
identifier: 0,
clientX: centerX,
clientY: centerY,
},
{
identifier: 1,
clientX: centerX + spacingX,
clientY: centerY,
},
]
await locator.dispatchEvent('touchstart', {
touches,
changedTouches: touches,
targetTouches: touches,
})
for (let j = 1; j <= steps; j++) {
const touches = [
{
identifier: 0,
clientX: centerX + (deltaX * j) / steps,
clientY: centerY + (deltaY * j) / steps,
},
{
identifier: 1,
clientX: centerX + spacingX + (deltaX * j) / steps,
clientY: centerY + (deltaY * j) / steps,
},
]
await locator.dispatchEvent('touchmove', {
touches,
changedTouches: touches,
targetTouches: touches,
})
}
await locator.dispatchEvent('touchend')
}
/**
* Simulate a pinch touch gesture from the center of an element.
* Touch points are set horizontally from each other, separated by {startDistance} pixels.
*/
export async function pinchFromCenter(
locator: Locator,
startDistance = 100,
delta = 0,
steps = 5
) {
const { centerX, centerY } = await locator.evaluate((target: HTMLElement) => {
const bounds = target.getBoundingClientRect()
const centerX = bounds.left + bounds.width / 2
const centerY = bounds.top + bounds.height / 2
return { centerX, centerY }
})
// Providing only clientX and clientY as the app only cares about those.
const touches = [
{
identifier: 0,
clientX: centerX - startDistance / 2,
clientY: centerY,
},
{
identifier: 1,
clientX: centerX + startDistance / 2,
clientY: centerY,
},
]
await locator.dispatchEvent('touchstart', {
touches,
changedTouches: touches,
targetTouches: touches,
})
for (let i = 1; i <= steps; i++) {
const touches = [
{
identifier: 0,
clientX: centerX - startDistance / 2 + (delta * i) / steps,
clientY: centerY,
},
{
identifier: 1,
clientX: centerX + startDistance / 2 + (delta * i) / steps,
clientY: centerY,
},
]
await locator.dispatchEvent('touchmove', {
touches,
changedTouches: touches,
targetTouches: touches,
})
}
await locator.dispatchEvent('touchend')
}

View File

@ -0,0 +1,146 @@
import { getUtils } from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
import { type Page } from '@playwright/test'
import type { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture'
test.use({
hasTouch: true,
})
test.describe('Testing Camera Movement (Touch Only)', () => {
/**
* DUPLICATED FROM `testing-camera-movement.spec.ts`, might need to become a util.
*
* hack that we're implemented our own retry instead of using retries built into playwright.
* however each of these camera drags can be flaky, because of udp
* and so putting them together means only one needs to fail to make this test extra flaky.
* this way we can retry within the test
* We could break them out into separate tests, but the longest past of the test is waiting
* for the stream to start, so it can be good to bundle related things together.
*/
const _bakeInRetries = async ({
mouseActions,
afterPosition,
beforePosition,
retryCount = 0,
page,
scene,
}: {
mouseActions: () => Promise<void>
beforePosition: [number, number, number]
afterPosition: [number, number, number]
retryCount?: number
page: Page
scene: SceneFixture
}) => {
const acceptableCamError = 5
const u = await getUtils(page)
await test.step('Set up initial camera position', async () =>
await scene.moveCameraTo({
x: beforePosition[0],
y: beforePosition[1],
z: beforePosition[2],
}))
await test.step('Do actions and watch for changes', async () =>
u.doAndWaitForImageDiff(async () => {
await mouseActions()
await u.openAndClearDebugPanel()
await u.closeDebugPanel()
await page.waitForTimeout(100)
}, 300))
await u.openAndClearDebugPanel()
await expect(page.getByTestId('cam-x-position')).toBeAttached()
const vals = await Promise.all([
page.getByTestId('cam-x-position').inputValue(),
page.getByTestId('cam-y-position').inputValue(),
page.getByTestId('cam-z-position').inputValue(),
])
const errors = vals.map((v, i) => Math.abs(Number(v) - afterPosition[i]))
let shouldRetry = false
if (errors.some((e) => e > acceptableCamError)) {
if (retryCount > 2) {
console.log('xVal', vals[0], 'xError', errors[0])
console.log('yVal', vals[1], 'yError', errors[1])
console.log('zVal', vals[2], 'zError', errors[2])
throw new Error('Camera position not as expected', {
cause: {
vals,
errors,
},
})
}
shouldRetry = true
}
if (shouldRetry) {
await _bakeInRetries({
mouseActions,
afterPosition: afterPosition,
beforePosition: beforePosition,
retryCount: retryCount + 1,
page,
scene,
})
}
}
// test(
// 'Touch camera controls',
// {
// tag: '@web',
// },
// async ({ page, homePage, scene, cmdBar }) => {
// const u = await getUtils(page)
// const camInitialPosition: [number, number, number] = [0, 85, 85]
//
// await homePage.goToModelingScene()
// await scene.settled(cmdBar)
// const stream = page.getByTestId('stream')
//
// await u.openAndClearDebugPanel()
// await u.closeKclCodePanel()
//
// await test.step('Orbit', async () => {
// await bakeInRetries({
// mouseActions: async () => {
// await panFromCenter(stream, 200, 200)
// await page.waitForTimeout(200)
// },
// afterPosition: [19, 85, 85],
// beforePosition: camInitialPosition,
// page,
// scene,
// })
// })
//
// await test.step('Pan', async () => {
// await bakeInRetries({
// mouseActions: async () => {
// await panTwoFingerFromCenter(stream, 200, 200)
// await page.waitForTimeout(200)
// },
// afterPosition: [19, 85, 85],
// beforePosition: camInitialPosition,
// page,
// scene,
// })
// })
//
// await test.step('Zoom', async () => {
// await bakeInRetries({
// mouseActions: async () => {
// await pinchFromCenter(stream, 300, -100, 5)
// },
// afterPosition: [0, 118, 118],
// beforePosition: camInitialPosition,
// page,
// scene,
// })
// })
// }
// )
})

18
package-lock.json generated
View File

@ -47,6 +47,7 @@
"diff": "^7.0.0",
"electron-updater": "^6.6.2",
"fuse.js": "^7.1.0",
"hammerjs": "^2.0.8",
"html2canvas-pro": "^1.5.8",
"isomorphic-fetch": "^3.0.0",
"json-rpc-2.0": "^1.6.0",
@ -93,6 +94,7 @@
"@testing-library/react": "^15.0.7",
"@types/diff": "^7.0.2",
"@types/electron": "^1.6.10",
"@types/hammerjs": "^2.0.46",
"@types/isomorphic-fetch": "^0.0.39",
"@types/jest": "^29.5.14",
"@types/minimist": "^1.2.5",
@ -7491,6 +7493,13 @@
"@types/node": "*"
}
},
"node_modules/@types/hammerjs": {
"version": "2.0.46",
"resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.46.tgz",
"integrity": "sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/http-cache-semantics": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz",
@ -15174,6 +15183,15 @@
"node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0"
}
},
"node_modules/hammerjs": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz",
"integrity": "sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==",
"license": "MIT",
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/happy-dom": {
"version": "17.4.4",
"resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-17.4.4.tgz",

View File

@ -49,6 +49,7 @@
"diff": "^7.0.0",
"electron-updater": "^6.6.2",
"fuse.js": "^7.1.0",
"hammerjs": "^2.0.8",
"html2canvas-pro": "^1.5.8",
"isomorphic-fetch": "^3.0.0",
"json-rpc-2.0": "^1.6.0",
@ -115,10 +116,10 @@
"circular-deps:diff:nodejs": "npm run circular-deps:diff || node ./scripts/diff.js",
"files:set-version": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json",
"files:set-notes": "./scripts/set-files-notes.sh",
"files:flip-to-nightly": "./scripts/flip-files-to-nightly.sh",
"files:flip-to-nightly:windows": "powershell -ExecutionPolicy Bypass -File ./scripts/flip-files-to-nightly.ps1",
"files:flip-to-staging": "./scripts/flip-files-to-staging.sh",
"files:flip-to-staging:windows": "powershell -ExecutionPolicy Bypass -File ./scripts/flip-files-to-staging.ps1",
"files:invalidate-bucket": "./scripts/invalidate-files-bucket.sh",
"files:invalidate-bucket:nightly": "./scripts/invalidate-files-bucket.sh --nightly",
"files:invalidate-bucket:staging": "./scripts/invalidate-files-bucket.sh --staging",
"postinstall": "electron-rebuild",
"generate:machine-api": "npx openapi-typescript ./openapi/machine-api.json -o src/lib/machine-api.d.ts",
"tron:start": "electron-forge start",
@ -168,6 +169,7 @@
"@testing-library/react": "^15.0.7",
"@types/diff": "^7.0.2",
"@types/electron": "^1.6.10",
"@types/hammerjs": "^2.0.46",
"@types/isomorphic-fetch": "^0.0.39",
"@types/jest": "^29.5.14",
"@types/minimist": "^1.2.5",

View File

@ -109,6 +109,8 @@ When you submit a PR to add or modify KCL samples, images will be generated and
[![makeup-mirror](screenshots/makeup-mirror.png)](makeup-mirror/main.kcl)
#### [mounting-plate](mounting-plate/main.kcl) ([screenshot](screenshots/mounting-plate.png))
[![mounting-plate](screenshots/mounting-plate.png)](mounting-plate/main.kcl)
#### [mug](mug/main.kcl) ([screenshot](screenshots/mug.png))
[![mug](screenshots/mug.png)](mug/main.kcl)
#### [multi-axis-robot](multi-axis-robot/main.kcl) ([screenshot](screenshots/multi-axis-robot.png))
[![multi-axis-robot](screenshots/multi-axis-robot.png)](multi-axis-robot/main.kcl)
#### [pdu-faceplate](pdu-faceplate/main.kcl) ([screenshot](screenshots/pdu-faceplate.png))
@ -139,6 +141,8 @@ When you submit a PR to add or modify KCL samples, images will be generated and
[![socket-head-cap-screw](screenshots/socket-head-cap-screw.png)](socket-head-cap-screw/main.kcl)
#### [spinning-highrise-tower](spinning-highrise-tower/main.kcl) ([screenshot](screenshots/spinning-highrise-tower.png))
[![spinning-highrise-tower](screenshots/spinning-highrise-tower.png)](spinning-highrise-tower/main.kcl)
#### [spool](spool/main.kcl) ([screenshot](screenshots/spool.png))
[![spool](screenshots/spool.png)](spool/main.kcl)
#### [spur-gear](spur-gear/main.kcl) ([screenshot](screenshots/spur-gear.png))
[![spur-gear](screenshots/spur-gear.png)](spur-gear/main.kcl)
#### [spur-reduction-gearset](spur-reduction-gearset/main.kcl) ([screenshot](screenshots/spur-reduction-gearset.png))
@ -147,6 +151,8 @@ When you submit a PR to add or modify KCL samples, images will be generated and
[![surgical-drill-guide](screenshots/surgical-drill-guide.png)](surgical-drill-guide/main.kcl)
#### [t-slot-rail](t-slot-rail/main.kcl) ([screenshot](screenshots/t-slot-rail.png))
[![t-slot-rail](screenshots/t-slot-rail.png)](t-slot-rail/main.kcl)
#### [teapot](teapot/main.kcl) ([screenshot](screenshots/teapot.png))
[![teapot](screenshots/teapot.png)](teapot/main.kcl)
#### [telemetry-antenna](telemetry-antenna/main.kcl) ([screenshot](screenshots/telemetry-antenna.png))
[![telemetry-antenna](screenshots/telemetry-antenna.png)](telemetry-antenna/main.kcl)
#### [thermal-block-insert](thermal-block-insert/main.kcl) ([screenshot](screenshots/thermal-block-insert.png))

View File

@ -1,4 +1,4 @@
// PC Fan
// Axial Fan
// A small axial fan, used to push or draw airflow over components to remove excess heat
// Set units

View File

@ -11,7 +11,7 @@ centerHoleDiameter = 1 + 3 / 4
plateThickness = 0.375
// Check that the plate is thick enough to countersink a hole
// assertGreaterThan(plateThickness, boltDiameter, "This plate is not thick enough for the necessary countersink dimensions")
assert(plateThickness, isGreaterThan = boltDiameter, error = "This plate is not thick enough for the necessary countersink dimensions")
// A bit of math to calculate the tangent line between the two diameters
r1 = centerHoleDiameter / 2 * 1.5 + .35

View File

@ -3,7 +3,7 @@
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "axial-fan/main.kcl",
"multipleFiles": true,
"title": "PC Fan",
"title": "Axial Fan",
"description": "A small axial fan, used to push or draw airflow over components to remove excess heat",
"files": [
"fan-housing.kcl",
@ -447,6 +447,16 @@
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "mug/main.kcl",
"multipleFiles": false,
"title": "Mug",
"description": "A cup with straight sides and a handle, typically used for hot drinks",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "multi-axis-robot/main.kcl",
@ -612,6 +622,16 @@
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "spool/main.kcl",
"multipleFiles": false,
"title": "Spool",
"description": "A spool is a winding device for storing large batches of filament, wire, tape, etc",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "spur-gear/main.kcl",
@ -647,7 +667,17 @@
"pathFromProjectDirectoryToFirstFile": "t-slot-rail/main.kcl",
"multipleFiles": false,
"title": "T-Slotted Framing Rail",
"description": "A T-slotted framing rail, or T-slot extrusion, is a rectangular or square aluminum profile with a \"T\" shaped slot along one or more sides. These slots allow for easy attachment of various hardware components like brackets, connectors, and fasteners, making it a versatile and customizable framing system.",
"description": "A T-slotted framing rail, commonly known as an 80/20 rail, is a rectangular or square aluminum extrusion with a \"T\" shaped slot along one or more sides. These slots allow for easy attachment of various hardware components like brackets, connectors, and fasteners, making it a versatile and customizable framing system.",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "teapot/main.kcl",
"multipleFiles": false,
"title": "Teapot",
"description": "A vessel with a spout and a handle used for serving tea and boiling water on a stovetop",
"files": [
"main.kcl"
]

View File

@ -0,0 +1,81 @@
// Mug
// A cup with straight sides and a handle, typically used for hot drinks
// Set units
@settings(defaultLengthUnit = mm)
// Define parameters
mugDiameter = 80
mugHeight = 95
wallThickness = 6
handleWidth = 18
handleThickness = 5
// Revolve the outer perimeter of the mug body. This will be used to subtract material from the handle, so that it fits snug to the body
tools = startSketchOn(XZ)
|> startProfile(at = [0, mugDiameter * 0.04])
|> xLine(length = mugDiameter * 0.1)
|> tangentialArc(endAbsolute = [mugDiameter * 0.3, mugDiameter * 0.01])
|> arc(angleStart = -180, angleEnd = -90, radius = mugDiameter * 0.01)
|> xLine(length = mugDiameter * 0.025)
|> arc(angleStart = -90, angleEnd = 0, radius = mugDiameter * 0.01)
|> xLine(endAbsolute = mugDiameter * 0.35)
|> tangentialArc(angle = 90, radius = mugDiameter * 0.15)
|> yLine(endAbsolute = mugHeight - (wallThickness / 2))
|> tangentialArc(angle = 90, diameter = wallThickness)
|> xLine(endAbsolute = profileStartX())
|> yLine(endAbsolute = profileStartY())
|> close()
|> revolve(axis = Y)
// Draw the path of the mug handle
handlePath = startSketchOn(XZ)
|> startProfile(at = [mugDiameter / 3, mugHeight * 0.25])
|> angledLine(angle = -5, length = mugDiameter / 3)
|> tangentialArc(angle = 90, radius = mugHeight * 0.15)
|> tangentialArc(end = [0, mugHeight * 0.425])
|> tangentialArc(angle = 90, radius = mugHeight * 0.15, tag = $seg02)
|> angledLine(angle = tangentToEnd(seg02), endAbsoluteX = profileStartX())
// Sweep the handle profile along the path
handle = startSketchOn(offsetPlane(YZ, offset = mugDiameter / 3))
|> startProfile(at = [
-handleWidth / 2,
mugHeight * 0.25 + handleThickness / 2
])
|> arc(
interiorAbsolute = [
0,
profileStartY() + handleThickness / 4
],
endAbsolute = [handleWidth / 2, profileStartY()],
)
|> tangentialArc(end = [0, -handleThickness])
|> tangentialArc(end = [-handleWidth, 0])
|> tangentialArc(endAbsolute = profileStart())
|> close()
|> sweep(path = handlePath)
|> subtract(tools)
// Create the body of the mug
mug = startSketchOn(XZ)
|> startProfile(at = [0, mugDiameter * 0.04])
|> xLine(length = mugDiameter * 0.1)
|> tangentialArc(endAbsolute = [mugDiameter * 0.3, mugDiameter * 0.01])
|> arc(angleStart = -180, angleEnd = -90, radius = mugDiameter * 0.01)
|> xLine(length = mugDiameter * 0.025)
|> arc(angleStart = -90, angleEnd = 0, radius = mugDiameter * 0.01)
|> xLine(endAbsolute = mugDiameter * 0.35)
|> tangentialArc(angle = 90, radius = mugDiameter * 0.15)
|> yLine(endAbsolute = mugHeight - (wallThickness / 2))
|> tangentialArc(angle = 180, diameter = wallThickness)
|> yLine(endAbsolute = mugDiameter * 0.15)
|> tangentialArc(angle = -90, radius = lastSegX() * 0.2)
|> xLine(endAbsolute = profileStartX())
|> yLine(endAbsolute = profileStartY())
|> close()
|> revolve(axis = Y)
// Join the mug and handle, then add an appearance
[handle, mug]
|> appearance(color = "#9b1212", metalness = 40, roughness = 30)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

View File

@ -0,0 +1,68 @@
// Spool
// A spool is a winding device for storing large batches of filament, wire, tape, etc
// Set units
@settings(defaultLengthUnit = mm)
// Define shaft parameters
hubDiameter = 2in
spoolDiameter = 305
spoolThickness = 267
coreDiameter = 150
webThickness = 10
stockDiameter = 5
// Sketch the revolved profile of one side of the spool
spoolBase = startSketchOn(XY)
|> startProfile(at = [0, coreDiameter / 2])
|> xLine(length = spoolThickness / 2)
|> yLine(endAbsolute = spoolDiameter / 2 - webThickness)
|> tangentialArc(angle = -270, radius = webThickness)
|> yLine(endAbsolute = hubDiameter / 2, tag = $seg01)
|> xLine(endAbsolute = profileStartX())
|> yLine(endAbsolute = profileStartY())
|> close()
|> revolve(axis = X)
|> appearance(color = "#010101", metalness = 10, roughness = 70)
// Create a function that draws an indent on the outer faces of the spool
fn score(radius, length, depth) {
scoreFn = startSketchOn(spoolBase, face = seg01)
|> startProfile(at = [0, radius])
|> arc(angleStart = 90, angleEnd = 55, radius = profileStartY(%))
|> angledLine(angle = 55, length = length)
|> arc(
%,
angleStart = 55,
angleEnd = 90,
radius = profileStartY(%) + length,
)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> patternCircular2d(instances = 8, center = [0, 0])
|> extrude(length = -depth)
return scoreFn
}
// Circular pattern indents around each side face of the spool
scoreSketch01 = score(radius = hubDiameter / 2 + webThickness, length = (coreDiameter - hubDiameter) / 2 - (2 * webThickness), depth = spoolThickness / 2)
scoreSketch02 = score(radius = coreDiameter / 2, length = (spoolDiameter - coreDiameter) / 2 - (webThickness * 3), depth = webThickness / 2)
// Mirror the spool base piece across the Y axis
|> patternCircular3d(instances = 2, axis = [0, 1, 0], center = [0, 0, 0])
// Model a wound coil on the spool diameter
hel01 = helix(
revolutions = spoolThickness / stockDiameter / 1.5,
angleStart = 0,
radius = (coreDiameter + stockDiameter) / 2,
axis = X,
length = spoolThickness,
)
// Sweep and color the coil
stock = startSketchOn(XZ)
|> circle(center = [0, -(coreDiameter + stockDiameter) / 2], diameter = stockDiameter)
|> sweep(path = hel01)
|> translate(x = -spoolThickness / 2)
|> appearance(color = "#f2471c", metalness = 70, roughness = 30)

View File

@ -1,5 +1,5 @@
// T-Slotted Framing Rail
// A T-slotted framing rail, or T-slot extrusion, is a rectangular or square aluminum profile with a "T" shaped slot along one or more sides. These slots allow for easy attachment of various hardware components like brackets, connectors, and fasteners, making it a versatile and customizable framing system.
// A T-slotted framing rail, commonly known as an 80/20 rail, is a rectangular or square aluminum extrusion with a "T" shaped slot along one or more sides. These slots allow for easy attachment of various hardware components like brackets, connectors, and fasteners, making it a versatile and customizable framing system.
// Set units
@settings(defaultLengthUnit = in, kclVersion = 1.0)

View File

@ -0,0 +1,106 @@
// Teapot
// A vessel with a spout and a handle used for serving tea and boiling water on a stovetop
// Set units
@settings(defaultLengthUnit = mm)
// Define parameters
teapotHeight = 130
beltlineDiameter = 160
wallThickness = 6
outletHeight = 26
spoutDiameter = 12
// Sketch the path of the teapot spout
spoutPath = startSketchOn(XZ)
|> startProfile(at = [-beltlineDiameter / 2.5, outletHeight])
|> xLine(length = -15.05)
|> tangentialArc(angle = -110, radius = 30, tag = $seg01)
|> angledLine(angle = tangentToEnd(seg01), length = 16.84)
|> tangentialArc(angle = 100, radius = 30)
// Sweep a hollow spout
spout = startSketchOn(offsetPlane(YZ, offset = -beltlineDiameter / 2.5))
|> circle(center = [0, outletHeight], diameter = spoutDiameter)
|> subtract2d(tool = circle(center = [0, outletHeight], diameter = spoutDiameter * 0.8))
|> sweep(path = spoutPath)
// Model the perimeter of the teapot body, to be used to trim the handle so that it fits snug to the body
tools = startSketchOn(YZ)
|> startProfile(at = [0, 0])
|> xLine(length = beltlineDiameter / 5)
|> tangentialArc(endAbsolute = [
beltlineDiameter / 2.1,
teapotHeight / 2.5
])
|> tangentialArc(endAbsolute = [
beltlineDiameter / 3,
teapotHeight / 1.1
])
|> tangentialArc(endAbsolute = [beltlineDiameter / 4, teapotHeight])
|> xLine(endAbsolute = profileStartX(%))
|> line(endAbsolute = profileStart(%))
|> close()
|> revolve(axis = Y)
// Sketch the path of the handle
handlePath = startSketchOn(XZ)
|> startProfile(at = [0, outletHeight])
|> xLine(endAbsolute = 76)
|> tangentialArc(end = [12.98, 6.64])
|> tangentialArc(end = [28.39, 63.11])
|> tangentialArc(end = [-10.27, 14.8])
|> tangentialArc(end = [-48.01, 1.81], tag = $seg02)
|> angledLine(angle = tangentToEnd(seg02), endAbsoluteX = 0)
// Sweep the handle
handle = startSketchOn(YZ)
|> startProfile(at = [-10, outletHeight + 3.5])
|> arc(interiorAbsolute = [0, outletHeight + 5], endAbsolute = [10, profileStartY(%)])
|> tangentialArc(end = [0, -7])
|> tangentialArc(end = [-20, 0])
|> tangentialArc(endAbsolute = profileStart())
|> close()
|> sweep(path = handlePath)
|> subtract(tools)
// Create a cutout in the body for the spout outlet
spoutHole = startSketchOn(YZ)
|> circle(center = [0, outletHeight], diameter = spoutDiameter)
|> extrude(length = -beltlineDiameter / 2)
// Model the body of the teapot
body = startSketchOn(YZ)
|> startProfile(at = [0, 0])
|> xLine(length = beltlineDiameter / 5)
|> tangentialArc(endAbsolute = [
beltlineDiameter / 2.1,
teapotHeight / 2.5
])
|> tangentialArc(endAbsolute = [
beltlineDiameter / 3,
teapotHeight / 1.1
])
|> tangentialArc(endAbsolute = [beltlineDiameter / 4, teapotHeight])
|> tangentialArc(angle = 190, diameter = wallThickness)
|> tangentialArc(endAbsolute = [
beltlineDiameter / 3 - wallThickness,
teapotHeight / 1.1
])
|> tangentialArc(endAbsolute = [
beltlineDiameter / 2.1 - wallThickness,
teapotHeight / 2.5
])
|> tangentialArc(endAbsolute = [
beltlineDiameter / 5,
profileStartY() + wallThickness
])
|> xLine(endAbsolute = profileStartX(%))
|> line(endAbsolute = profileStart(%))
|> close()
|> revolve(axis = Y)
|> subtract(tools = [spoutHole])
// Join each component of the teapot and add an appearance
[body, handle, spout]
|> appearance(color = "#1f9896", metalness = 40, roughness = 30)

24
rust/Cargo.lock generated
View File

@ -1792,7 +1792,7 @@ dependencies = [
[[package]]
name = "kcl-bumper"
version = "0.1.80"
version = "0.1.81"
dependencies = [
"anyhow",
"clap",
@ -1803,7 +1803,7 @@ dependencies = [
[[package]]
name = "kcl-derive-docs"
version = "0.1.80"
version = "0.1.81"
dependencies = [
"proc-macro2",
"quote",
@ -1812,7 +1812,7 @@ dependencies = [
[[package]]
name = "kcl-directory-test-macro"
version = "0.1.80"
version = "0.1.81"
dependencies = [
"convert_case",
"proc-macro2",
@ -1822,7 +1822,7 @@ dependencies = [
[[package]]
name = "kcl-language-server"
version = "0.2.80"
version = "0.2.81"
dependencies = [
"anyhow",
"clap",
@ -1843,7 +1843,7 @@ dependencies = [
[[package]]
name = "kcl-language-server-release"
version = "0.1.80"
version = "0.1.81"
dependencies = [
"anyhow",
"clap",
@ -1863,7 +1863,7 @@ dependencies = [
[[package]]
name = "kcl-lib"
version = "0.2.80"
version = "0.2.81"
dependencies = [
"anyhow",
"approx 0.5.1",
@ -1940,7 +1940,7 @@ dependencies = [
[[package]]
name = "kcl-python-bindings"
version = "0.3.80"
version = "0.3.81"
dependencies = [
"anyhow",
"kcl-lib",
@ -1955,7 +1955,7 @@ dependencies = [
[[package]]
name = "kcl-test-server"
version = "0.1.80"
version = "0.1.81"
dependencies = [
"anyhow",
"hyper 0.14.32",
@ -1968,7 +1968,7 @@ dependencies = [
[[package]]
name = "kcl-to-core"
version = "0.1.80"
version = "0.1.81"
dependencies = [
"anyhow",
"async-trait",
@ -1982,7 +1982,7 @@ dependencies = [
[[package]]
name = "kcl-wasm-lib"
version = "0.1.80"
version = "0.1.81"
dependencies = [
"anyhow",
"bson",
@ -2049,9 +2049,9 @@ dependencies = [
[[package]]
name = "kittycad-modeling-cmds"
version = "0.2.121"
version = "0.2.123"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94ba95c22493d79ec8a1faab963d8903f6de0e373efedf2bc3bb76a0ddbab036"
checksum = "b8f3c1b4b4ddb9aa336a09933f2550f9882552e321187b7bcff47f006379c3aa"
dependencies = [
"anyhow",
"chrono",

View File

@ -36,7 +36,7 @@ dashmap = { version = "6.1.0" }
http = "1"
indexmap = "2.9.0"
kittycad = { version = "0.3.37", default-features = false, features = ["js", "requests"] }
kittycad-modeling-cmds = { version = "0.2.120", features = ["ts-rs", "websocket"] }
kittycad-modeling-cmds = { version = "0.2.123", features = ["ts-rs", "websocket"] }
lazy_static = "1.5.0"
miette = "7.5.0"
pyo3 = { version = "0.24.1" }

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-bumper"
version = "0.1.80"
version = "0.1.81"
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.80"
version = "0.1.81"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"

View File

@ -117,6 +117,7 @@ pub const TEST_NAMES: &[&str] = &[
"std-sketch-getOppositeEdge-0",
"std-sketch-getPreviousAdjacentEdge-0",
"std-sketch-getNextAdjacentEdge-0",
"std-sketch-extrudeTwist-0",
"std-sketch-extrude-0",
"std-sketch-extrude-1",
"std-sketch-extrude-2",

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.80"
version = "0.1.81"
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.80"
version = "0.1.81"
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.80"
version = "0.2.81"
edition = "2021"
license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

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

View File

@ -300,12 +300,6 @@ extrude001 = extrude(profile001, length = 4)
let first = &result.first().unwrap().2;
let second = &result.last().unwrap().2;
assert!(
first.artifact_commands.len() < second.artifact_commands.len(),
"Second should have all the artifact commands of the first, plus more. first={:?}, second={:?}",
first.artifact_commands.len(),
second.artifact_commands.len()
);
assert!(
first.artifact_graph.len() < second.artifact_graph.len(),
"Second should have all the artifacts of the first, plus more. first={:?}, second={:?}",

View File

@ -1230,7 +1230,10 @@ secondSketch = startSketchOn(part001, face = '')
let result = execute_and_snapshot(code, None).await;
let err = result.unwrap_err();
let err = err.as_kcl_error().unwrap();
assert_eq!(err.message(), "face requires a value with type `tag`, but found string");
assert_eq!(
err.message(),
"face requires a value with type `tag`, but found a value with type `string`."
);
}
#[tokio::test(flavor = "multi_thread")]
@ -1962,7 +1965,7 @@ someFunction('INVALID')
let err = err.as_kcl_error().unwrap();
assert_eq!(
err.message(),
"The input argument of `startSketchOn` requires a value with type `Solid | Plane`, but found string"
"The input argument of `startSketchOn` requires a value with type `Solid` or a value with type `Plane` (`Solid | Plane`), but found a value with type `string`."
);
assert_eq!(
err.source_ranges(),
@ -2087,7 +2090,7 @@ async fn kcl_test_better_type_names() {
},
None => todo!(),
};
assert_eq!(err, "This function expected the input argument to be one or more Solids or imported geometry but it's actually of type Sketch. You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`");
assert_eq!(err, "This function expected the input argument to be one or more Solids or ImportedGeometry but it's actually of type Sketch. You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`");
}
#[tokio::test(flavor = "multi_thread")]

View File

@ -508,8 +508,9 @@ impl EngineManager for EngineConnection {
.await?;
// Wait for the response.
let response_timeout = 300;
let current_time = std::time::Instant::now();
while current_time.elapsed().as_secs() < 60 {
while current_time.elapsed().as_secs() < response_timeout {
let guard = self.socket_health.read().await;
if *guard == SocketHealth::Inactive {
// Check if we have any pending errors.

View File

@ -135,8 +135,10 @@ pub struct KclErrorWithOutputs {
pub non_fatal: Vec<CompilationError>,
#[cfg(feature = "artifact-graph")]
pub operations: Vec<Operation>,
// TODO: Remove this field. Doing so breaks the ts-rs output for some
// reason.
#[cfg(feature = "artifact-graph")]
pub artifact_commands: Vec<ArtifactCommand>,
pub _artifact_commands: Vec<ArtifactCommand>,
#[cfg(feature = "artifact-graph")]
pub artifact_graph: ArtifactGraph,
pub filenames: IndexMap<ModuleId, ModulePath>,
@ -162,7 +164,7 @@ impl KclErrorWithOutputs {
#[cfg(feature = "artifact-graph")]
operations,
#[cfg(feature = "artifact-graph")]
artifact_commands,
_artifact_commands: artifact_commands,
#[cfg(feature = "artifact-graph")]
artifact_graph,
filenames,
@ -177,7 +179,7 @@ impl KclErrorWithOutputs {
#[cfg(feature = "artifact-graph")]
operations: Default::default(),
#[cfg(feature = "artifact-graph")]
artifact_commands: Default::default(),
_artifact_commands: Default::default(),
#[cfg(feature = "artifact-graph")]
artifact_graph: Default::default(),
filenames: Default::default(),
@ -781,6 +783,7 @@ impl Severity {
pub enum Tag {
Deprecated,
Unnecessary,
UnknownNumericUnits,
None,
}

View File

@ -45,26 +45,6 @@ pub struct ArtifactCommand {
pub command: ModelingCmd,
}
impl PartialOrd for ArtifactCommand {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
// Order by the source range.
let range = self.range.cmp(&other.range);
if range != std::cmp::Ordering::Equal {
return Some(range);
}
#[cfg(test)]
{
// If the ranges are equal, order by the serde variant.
Some(
crate::variant_name::variant_name(&self.command)
.cmp(&crate::variant_name::variant_name(&other.command)),
)
}
#[cfg(not(test))]
self.cmd_id.partial_cmp(&other.cmd_id)
}
}
pub type DummyPathToNode = Vec<()>;
fn serialize_dummy_path_to_node<S>(_path_to_node: &DummyPathToNode, serializer: S) -> Result<S::Ok, S::Error>
@ -190,6 +170,7 @@ pub struct Sweep {
#[serde(rename_all = "camelCase")]
pub enum SweepSubType {
Extrusion,
ExtrusionTwist,
Revolve,
RevolveAboutEdge,
Loft,
@ -1102,11 +1083,13 @@ fn artifacts_to_update(
return Ok(return_arr);
}
ModelingCmd::Extrude(kcmc::Extrude { target, .. })
| ModelingCmd::TwistExtrude(kcmc::TwistExtrude { target, .. })
| ModelingCmd::Revolve(kcmc::Revolve { target, .. })
| ModelingCmd::RevolveAboutEdge(kcmc::RevolveAboutEdge { target, .. })
| ModelingCmd::Sweep(kcmc::Sweep { target, .. }) => {
let sub_type = match cmd {
ModelingCmd::Extrude(_) => SweepSubType::Extrusion,
ModelingCmd::TwistExtrude(_) => SweepSubType::ExtrusionTwist,
ModelingCmd::Revolve(_) => SweepSubType::Revolve,
ModelingCmd::RevolveAboutEdge(_) => SweepSubType::RevolveAboutEdge,
ModelingCmd::Sweep(_) => SweepSubType::Sweep,

View File

@ -111,8 +111,6 @@ impl GlobalState {
#[cfg(feature = "artifact-graph")]
operations: self.exec_state.artifacts.operations,
#[cfg(feature = "artifact-graph")]
artifact_commands: self.exec_state.artifacts.commands,
#[cfg(feature = "artifact-graph")]
artifact_graph: self.exec_state.artifacts.graph,
errors: self.exec_state.errors,
default_planes: ctx.engine.get_default_planes().read().await.clone(),

View File

@ -2,7 +2,9 @@ use indexmap::IndexMap;
use serde::Serialize;
use super::{types::NumericType, ArtifactId, KclValue};
use crate::{ModuleId, NodePath, SourceRange};
#[cfg(feature = "artifact-graph")]
use crate::parsing::ast::types::{Node, Program};
use crate::{parsing::ast::types::ItemVisibility, ModuleId, NodePath, SourceRange};
/// A CAD modeling operation for display in the feature tree, AKA operations
/// timeline.
@ -26,6 +28,20 @@ pub enum Operation {
is_error: bool,
},
#[serde(rename_all = "camelCase")]
VariableDeclaration {
/// The variable name.
name: String,
/// The value of the variable.
value: OpKclValue,
/// The visibility modifier of the variable, e.g. `export`. `Default`
/// means there is no visibility modifier.
visibility: ItemVisibility,
/// The node path of the operation in the source code.
node_path: NodePath,
/// The source range of the operation in the source code.
source_range: SourceRange,
},
#[serde(rename_all = "camelCase")]
GroupBegin {
/// The details of the group.
group: Group,
@ -37,32 +53,36 @@ pub enum Operation {
GroupEnd,
}
/// A way for sorting the operations in the timeline. This is used to sort
/// operations in the timeline and to determine the order of operations.
/// We use this for the multi-threaded snapshotting, so that we can have deterministic
/// output.
impl PartialOrd for Operation {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(match (self, other) {
(Self::StdLibCall { source_range: a, .. }, Self::StdLibCall { source_range: b, .. }) => a.cmp(b),
(Self::StdLibCall { source_range: a, .. }, Self::GroupBegin { source_range: b, .. }) => a.cmp(b),
(Self::StdLibCall { .. }, Self::GroupEnd) => std::cmp::Ordering::Less,
(Self::GroupBegin { source_range: a, .. }, Self::GroupBegin { source_range: b, .. }) => a.cmp(b),
(Self::GroupBegin { source_range: a, .. }, Self::StdLibCall { source_range: b, .. }) => a.cmp(b),
(Self::GroupBegin { .. }, Self::GroupEnd) => std::cmp::Ordering::Less,
(Self::GroupEnd, Self::StdLibCall { .. }) => std::cmp::Ordering::Greater,
(Self::GroupEnd, Self::GroupBegin { .. }) => std::cmp::Ordering::Greater,
(Self::GroupEnd, Self::GroupEnd) => std::cmp::Ordering::Equal,
})
}
}
impl Operation {
/// If the variant is `StdLibCall`, set the `is_error` field.
pub(crate) fn set_std_lib_call_is_error(&mut self, is_err: bool) {
match self {
Self::StdLibCall { ref mut is_error, .. } => *is_error = is_err,
Self::GroupBegin { .. } | Self::GroupEnd => {}
Self::VariableDeclaration { .. } | Self::GroupBegin { .. } | Self::GroupEnd => {}
}
}
#[cfg(feature = "artifact-graph")]
pub(crate) fn fill_node_paths(&mut self, program: &Node<Program>, cached_body_items: usize) {
match self {
Operation::StdLibCall {
node_path,
source_range,
..
}
| Operation::VariableDeclaration {
node_path,
source_range,
..
}
| Operation::GroupBegin {
node_path,
source_range,
..
} => {
node_path.fill_placeholder(program, cached_body_items, *source_range);
}
Operation::GroupEnd => {}
}
}
}

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