Compare commits

...

29 Commits

Author SHA1 Message Date
7d6427ab64 Add cursor-not-allowed to onboarding backdrops that block clicks (#6789)
* Add `cursor-not-allowed` to onboarding backdrops that block clicks

Some follow-up feedback after #6714 from @jacebrowning, so that users
know why they can't click around.

* Make anything in the onboarding card `cursor-auto`
2025-05-09 13:28:45 -04:00
4abbe0d57a Result of npm prune (#6807)
pierremtb/adhoc/npm-prune
2025-05-09 17:07:06 +00:00
a631ff689f Remove unnecessary checks for execution completion from onboarding test (#6804)
We don't care if the axial fan loads under 15s on a cheap CI machine, at
least not in this test.
2025-05-09 17:01:42 +00:00
e1d401adfe Remove snapshottoken variable and playwright-secrets.env file (#6801)
* Remove snapshottoken
Fixes #6800

* Add placeholder in .env.development

* Clean up language

* Update CONTRIBUTING.md

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

* Add dotenv to secrets for local testing

* Lint

* Reorg things

* Quick fix

* Last one for windows

---------

Co-authored-by: Jace Browning <jacebrowning@gmail.com>
2025-05-09 12:32:35 -04:00
6f49c88382 Remove dev.zoo.dev from contributing (#6799) 2025-05-09 10:20:22 -04:00
374d07b995 Turn on Billing UI in releases (#6788)
* Turn on Billing UI in releases

* Update most snapshots but one, and new masks
2025-05-09 09:04:45 -04:00
3481252082 fix subtract test (#6791)
* fix subtract test

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

* fix subtract test

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

* fixups

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-05-08 20:28:41 -07:00
035f3b6aed Update known-issues.md (#6790) 2025-05-09 03:21:25 +00:00
923feadfa5 Suggest a list of possible arg labels when an argument is unlabelled (#6755)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-05-09 14:28:04 +12:00
1ea66d6f23 [Fix]: Always show stack trace on the page if an Error shows up (#6785)
* fix: show stack traces

* fix: update GH with report a bug
2025-05-08 21:17:34 -05:00
3b7b4f85a1 Update onboarding to V1 browser and desktop flows (#6714)
* Remove unused `telemetryLoader`

* Remove onboarding redirect behavior

* Allow subRoute to be passed to navigateToProject

* Replace warning dialog routes with toasts

* Wire up new utilities and toasts to UI components

* Add home sidebar buttons for tutorial flow

* Rename menu item

* Add flex-1 so home-layout fills available space

* Remove onboarding avatar tests, they are becoming irrelevant

* Consolidate onboarding tests to one longer one

and update it to not use pixel color checks, and use fixtures.

* Shorten warning toast button text

* tsc, lint, and circular deps

* Update circular dep file

* Fix mistakes made in circular update tweaking

* One more dumb created circular dep

* Update src/routes/Onboarding/utils.tsx

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

* Fix narrow screen home layout breaking

* fix: kevin, navigation routes fixed

* fix: filename parsing is correct now for onboarding with the last file sep

* Fix e2e test state checks that are diff on Linux

* Create onboarding project entirely through systemIOMachine

* Fix Windows path construction

* Make utility to verify a string is an onboarding value

* Little biome formatting suggestion fix

* Units onboarding step was not using OnboardingButtons

* Add type checking of next and previous status, fix useNextClick

* Update `OnboardingStatus` type on WASM side

* Make onboarding different on browser and web, placeholder component

* Show proof of concept with custom content per route

* Make text type args not insta dismiss when you click anywhere

* Make some utility hooks for the onboarding

* Update requestedProjectName along with requestedProjectName

* Build out a rough draft of desktop onboarding

* Remove unused onboarding route files

* Build out rough draft of browser onboarding content

* @jgomez720 browser flow feedback

* @jgomez420 desktop feedback

* tsc and lints

* Tweaks

* Import is dead, long live Add files

* What's up with my inability to type "highlight"?

* Codespell and String casting

* Update browser sample to be axial fan

* lint and tsc

* codespell again

* Remove unused nightmare function `useDemoCode`

* Add a few unit tests

* Update desktop to use bulk file creation from #6747

* Oops overwrote main.kcl on the modify with text-to-cad step

* Undo the dumb use of `sep` that I introduced

* Fix up project test

which was fragile to the number of steps in the onboarding smh

* Fix up onboarding flow test

* typo

---------

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Co-authored-by: Kevin Nadro <kevin@zoo.dev>
2025-05-09 00:37:21 +00:00
9853353512 Update text-to-cad experimental label (#6786)
update text-to-cad label
2025-05-08 22:06:01 +00:00
7b8585f3c3 Add @web tag to new web-only tests for later CI use (#6784)
pierremtb/adhoc/add-web-tag-for-later
2025-05-08 17:58:05 -04:00
fc3ce4cda8 Release KCL 68 (#6783) 2025-05-08 20:41:26 +00:00
a7f5c56ba1 Error on "Open in desktop" click if URL is too long on Windows (#6768)
* pierremtb/issue6200-toast-error-if-windows-and-length-over-2046

* Add test for web
2025-05-08 16:22:36 -04:00
max
c8747bd55a Extend point-and-click edit flow to non-pipe Chamfer and Fillet (#6767)
* enable non-piped fillets and chamfers

* reorder chamferAstMod

* tsc

* editEdgeTreatment + refactor + hookup

* remove unused stuff

* test

* typos

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

* else else else

* Apply suggestions from code review

pierre edits

Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>

* const

* parameterName

* fmt

* efficiency !

* graphite being helpful

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: Pierre Jacquier <pierrejacquier39@gmail.com>
2025-05-08 20:16:36 +00:00
e2fd3948f5 [Feature] Create assembly samples from home page (#6747)
* fix: how?

* fix: 0 byte thumbnail png loading bug

* fix: adding navigate to single file back

* fix: cargo fmt

* fix: sorting files to match manifest and unit test

* fix: restoring back to main

* fix: cargo fmt

* fix: ope, I forgot I deleted some code that renamed single files to the samples name to track easier within the file tree

* fix: ope

* Update src/lib/commandBarConfigs/applicationCommandConfig.ts

Co-authored-by: Frank Noirot <frank@zoo.dev>

* fix: unique name for project, ope

* fix: filtered samples for web and skeleton create a sample command

* fix: Create A Sample specifically desktop home page instead of overloading the add to file

* fix: hiding source

* fix: gotcha on add to file with existing project default args and assemblies

---------

Co-authored-by: Frank Noirot <frank@zoo.dev>
2025-05-08 19:41:29 +00:00
e960d4d8a4 BREAKING: Change array functions to call user function with keyword args (#6779)
* Change array functions to call user function with keyword args

* Fix KCL to use keyword params

* Remove unneeded positional call code

* Update docs

* Update output
2025-05-08 19:10:47 +00:00
1ccf8d4dd4 Change display of mixed array to be clear what it is (#6757) 2025-05-08 14:47:15 -04:00
b65ea8e0a9 Change pattern functions to call user function with keyword args (#6772)
* Change pattern functions to call user function with keyword args

* Fix KCL code to use unlabeled syntax

* Update generated output
2025-05-08 13:43:50 -05:00
90cb26c6d9 Fix just lint to check all targets (#6777) 2025-05-08 11:28:33 -07:00
max
3562076b83 Removes "fillets cannot touch" warning message (#6771)
rm warningMessage
2025-05-08 20:11:01 +02:00
6230747b51 Remove test that can never run in our current setup (#6774)
Remove test that can never run with our current setup
2025-05-08 17:02:41 +00:00
479179dd9b #6734 Clean up unused code (#6736)
* remove unused code in modelingMachine

* remove unused actions in featureTreeMachine

* video.pause is not async

* remove unused param in Toolbar

* remove unused rectangleOrigin from getRectangleCallExpressions

* fmt

* parseProjectRoute is not async anymore

* prefix unused params with underscore

* insertNewStartProfileAt/sketchEntryNodePath param is not used

* remove unused constraintType parameter from getRemoveConstraintsTransform

* underscore unused params

* remove unused scale param in segment.ts

* remove unused for in sceneInfra

* remove unused sketchEntryNodePath from sceneEntitiesManager methods

* remove unused shouldTearDown param

* remove unused planeNodePath param from setup draft methods

* remove unused ast param
2025-05-08 06:58:30 -04:00
67f9dba77b #6686 Fix Unable to double click to edit sketch with specific set of actions (#6766)
* Canceling sketch mode into 'undo startSketchOn' should work from all sketch states, not just in Line

* add test
2025-05-08 05:50:25 -04:00
89c345649d dont bust cache on external file change (#6756)
* dont bust cache on external file change

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

* dont bust cache on external file change

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

* dont bust cache on external file change

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

* add to the comment

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-05-08 05:33:32 -04:00
0550eef701 Fix the settings docs links (#6763)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-05-08 16:35:11 +12:00
1c21198499 Fix trackball camera by weaving through EngineStream (#6760)
* Fix trackball camera by weaving through EngineStream

Fixes #6472. Just a missing setting moved in #5312, which wasn't caught
because testing that the orbit maneuver is actually performing a
"trackball-like" movement is nonexistent. I don't know how to test that
reliably, but typing this object provides the red squiggles to reveal
the missing property.

* Update src/components/EngineStream.tsx

---------

Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>
2025-05-07 23:43:08 -04:00
8ac232414d Truncate "text" arg value summaries by default (#6754)
Splitting out some work I had in #6254 that wasn't directly related to
onboarding into its own tiny PR.
2025-05-07 22:22:04 -04:00
190 changed files with 47981 additions and 3558 deletions

View File

@ -9,10 +9,11 @@ VITE_KC_SITE_BASE_URL=https://dev.zoo.dev
VITE_KC_SITE_APP_URL=https://app.dev.zoo.dev
VITE_KC_SKIP_AUTH=false
VITE_KC_CONNECTION_TIMEOUT_MS=5000
#VITE_KC_DEV_TOKEN="optional token from dev.zoo.dev to skip auth in the app"
#VITE_KC_DEV_TOKEN="optional token to skip auth in the app"
#token="required token for playwright. TODO: clean up env vars in #3973"
RUST_BACKTRACE=1
PYO3_PYTHON=/usr/local/bin/python3
#KITTYCAD_API_TOKEN="required token from dev.zoo.dev for engine testing"
#KITTYCAD_API_TOKEN="required token for engine testing"
FAIL_ON_CONSOLE_ERRORS=true

View File

@ -229,7 +229,6 @@ jobs:
max_attempts: 5
env:
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
snapshottoken: ${{ secrets.KITTYCAD_API_TOKEN }}
TAB_API_URL: ${{ secrets.TAB_API_URL }}
TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}

View File

@ -63,7 +63,7 @@ If you're not a Zoo employee you won't be able to access the dev environment, yo
### Development environment variables
The Copilot LSP plugin in the editor requires a Zoo API token to run. In production, we authenticate this with a token via cookie in the browser and device auth token in the desktop environment, but this token is inaccessible in the dev browser version because the cookie is considered "cross-site" (from `localhost` to `dev.zoo.dev`). There is an optional environment variable called `VITE_KC_DEV_TOKEN` that you can populate with a dev token in a `.env.development.local` file to not check it into Git, which will use that token instead of other methods for the LSP service.
The Copilot LSP plugin in the editor requires a Zoo API token to run. In production, we authenticate this with a token via cookie in the browser and device auth token in the desktop environment, but this token is inaccessible in the dev browser version because the cookie is considered "cross-site" (from `localhost` to `zoo.dev`). There is an optional environment variable called `VITE_KC_DEV_TOKEN` that you can populate with a dev token in a `.env.development.local` file to not check it into Git, which will use that token instead of other methods for the LSP service.
### Developing in Chrome
@ -198,15 +198,9 @@ For more information on fuzzing you can check out
### Playwright tests
You will need a `./e2e/playwright/playwright-secrets.env` file:
Prepare these system dependencies:
```bash
$ touch ./e2e/playwright/playwright-secrets.env
$ cat ./e2e/playwright/playwright-secrets.env
token=<dev.zoo.dev/account/api-tokens>
snapshottoken=<zoo.dev/account/api-tokens>
```
or use `export` to set the environment variables `token` and `snapshottoken`.
- Set $token from https://zoo.dev/account/api-tokens
#### Snapshot tests (Google Chrome on Ubuntu only)
@ -302,7 +296,7 @@ Which will run our suite of [Vitest unit](https://vitest.dev/) and [React Testin
Prepare these system dependencies:
- Set `$KITTYCAD_API_TOKEN` from https://dev.zoo.dev/account/api-tokens
- Set `$KITTYCAD_API_TOKEN` from https://zoo.dev/account/api-tokens
- Install `just` following [these instructions](https://just.systems/man/en/packages.html)
then run tests that target the KCL language:

View File

@ -1,5 +1,5 @@
.PHONY: all
all: install build check
all: install check build
###############################################################################
# INSTALL

View File

@ -15,12 +15,6 @@ once fixed in engine will just start working here with no language changes.
- **Import**: Right now you can import a file, even if that file has brep data
you cannot edit it, after v1, the engine will account for this.
- **Fillets**: Fillets cannot intersect, you will get an error. Only simple fillet
cases work currently.
- **Chamfers**: Chamfers cannot intersect, you will get an error. Only simple
chamfer cases work currently.
- **Appearance**: Changing the appearance on a loft does not work.
- **CSG Booleans**: Coplanar (bodies that share a plane) unions, subtractions, and intersections are not currently supported.

View File

@ -8,16 +8,16 @@ layout: manual
There are three levels of settings available in Zoo Design Studio:
1. [User Settings](/docs/kcl/settings/user): Global settings that apply to all projects, stored in `user.toml`
2. [Project Settings](/docs/kcl/settings/project): Settings specific to a project, stored in `project.toml`
1. [User Settings](/docs/kcl-lang/settings/user): Global settings that apply to all projects, stored in `user.toml`
2. [Project Settings](/docs/kcl-lang/settings/project): Settings specific to a project, stored in `project.toml`
3. Per-file Settings: Settings that apply to a single KCL file, specified using the `@settings` attribute
## Configuration Files
Zoo Design Studio uses TOML files for configuration:
* **User Settings**: `user.toml` - See [complete documentation](/docs/kcl/settings/user)
* **Project Settings**: `project.toml` - See [complete documentation](/docs/kcl/settings/project)
* **User Settings**: `user.toml` - See [complete documentation](/docs/kcl-lang/settings/user)
* **Project Settings**: `project.toml` - See [complete documentation](/docs/kcl-lang/settings/project)
## Per-file settings

View File

@ -50,7 +50,7 @@ r = 10 // radius
// Call `map`, using an anonymous function instead of a named one.
circles = map(
[1..3],
f = fn(id) {
f = fn(@id) {
return startSketchOn(XY)
|> circle(center = [id * 2 * r, 0], radius = r)
},

View File

@ -34,8 +34,8 @@ reduce(
```kcl
// This function adds two numbers.
fn add(a, b) {
return a + b
fn add(@a, accum) {
return a + accum
}
// This function adds an array of numbers.
@ -49,7 +49,7 @@ fn sum(@arr) {
fn sum(arr):
sumSoFar = 0
for i in arr:
sumSoFar = add(sumSoFar, i)
sumSoFar = add(i, sumSoFar)
return sumSoFar */
// We use `assert` to check that our `sum` function gives the
@ -72,8 +72,8 @@ arr = [1, 2, 3]
sum = reduce(
arr,
initial = 0,
f = fn(i, result_so_far) {
return i + result_so_far
f = fn(@i, accum) {
return i + accum
},
)
@ -105,11 +105,11 @@ fn decagon(@radius) {
fullDecagon = reduce(
[1..10],
initial = startOfDecagonSketch,
f = fn(i, partialDecagon) {
f = fn(@i, accum) {
// Draw one edge of the decagon.
x = cos(stepAngle * i) * radius
y = sin(stepAngle * i) * radius
return line(partialDecagon, end = [x, y])
return line(accum, end = [x, y])
},
)

View File

@ -133826,7 +133826,7 @@
false
],
[
"r = 10 // radius\n// Call `map`, using an anonymous function instead of a named one.\ncircles = map(\n [1..3],\n f = fn(id) {\n return startSketchOn(XY)\n |> circle(center = [id * 2 * r, 0], radius = r)\n },\n)",
"r = 10 // radius\n// Call `map`, using an anonymous function instead of a named one.\ncircles = map(\n [1..3],\n f = fn(@id) {\n return startSketchOn(XY)\n |> circle(center = [id * 2 * r, 0], radius = r)\n },\n)",
false
]
]
@ -232586,15 +232586,15 @@
"deprecated": false,
"examples": [
[
"// This function adds two numbers.\nfn add(a, b) {\n return a + b\n}\n\n// This function adds an array of numbers.\n// It uses the `reduce` function, to call the `add` function on every\n// element of the `arr` parameter. The starting value is 0.\nfn sum(@arr) {\n return reduce(arr, initial = 0, f = add)\n}\n\n/* The above is basically like this pseudo-code:\nfn sum(arr):\n sumSoFar = 0\n for i in arr:\n sumSoFar = add(sumSoFar, i)\n return sumSoFar */\n\n// We use `assert` to check that our `sum` function gives the\n// expected result. It's good to check your work!\nassert(\n sum([1, 2, 3]),\n isEqualTo = 6,\n tolerance = 0.1,\n error = \"1 + 2 + 3 summed is 6\",\n)",
"// This function adds two numbers.\nfn add(@a, accum) {\n return a + accum\n}\n\n// This function adds an array of numbers.\n// It uses the `reduce` function, to call the `add` function on every\n// element of the `arr` parameter. The starting value is 0.\nfn sum(@arr) {\n return reduce(arr, initial = 0, f = add)\n}\n\n/* The above is basically like this pseudo-code:\nfn sum(arr):\n sumSoFar = 0\n for i in arr:\n sumSoFar = add(i, sumSoFar)\n return sumSoFar */\n\n// We use `assert` to check that our `sum` function gives the\n// expected result. It's good to check your work!\nassert(\n sum([1, 2, 3]),\n isEqualTo = 6,\n tolerance = 0.1,\n error = \"1 + 2 + 3 summed is 6\",\n)",
false
],
[
"// This example works just like the previous example above, but it uses\n// an anonymous `add` function as its parameter, instead of declaring a\n// named function outside.\narr = [1, 2, 3]\nsum = reduce(\n arr,\n initial = 0,\n f = fn(i, result_so_far) {\n return i + result_so_far\n },\n)\n\n// We use `assert` to check that our `sum` function gives the\n// expected result. It's good to check your work!\nassert(\n sum,\n isEqualTo = 6,\n tolerance = 0.1,\n error = \"1 + 2 + 3 summed is 6\",\n)",
"// This example works just like the previous example above, but it uses\n// an anonymous `add` function as its parameter, instead of declaring a\n// named function outside.\narr = [1, 2, 3]\nsum = reduce(\n arr,\n initial = 0,\n f = fn(@i, accum) {\n return i + accum\n },\n)\n\n// We use `assert` to check that our `sum` function gives the\n// expected result. It's good to check your work!\nassert(\n sum,\n isEqualTo = 6,\n tolerance = 0.1,\n error = \"1 + 2 + 3 summed is 6\",\n)",
false
],
[
"// Declare a function that sketches a decagon.\nfn decagon(@radius) {\n // Each side of the decagon is turned this many radians from the previous angle.\n stepAngle = (1 / 10 * TAU): number(rad)\n\n // Start the decagon sketch at this point.\n startOfDecagonSketch = startSketchOn(XY)\n |> startProfile(at = [cos(0) * radius, sin(0) * radius])\n\n // Use a `reduce` to draw the remaining decagon sides.\n // For each number in the array 1..10, run the given function,\n // which takes a partially-sketched decagon and adds one more edge to it.\n fullDecagon = reduce(\n [1..10],\n initial = startOfDecagonSketch,\n f = fn(i, partialDecagon) {\n // Draw one edge of the decagon.\n x = cos(stepAngle * i) * radius\n y = sin(stepAngle * i) * radius\n return line(partialDecagon, end = [x, y])\n },\n )\n\n return fullDecagon\n}\n\n/* The `decagon` above is basically like this pseudo-code:\nfn decagon(radius):\n stepAngle = ((1/10) * TAU): number(rad)\n plane = startSketchOn(XY)\n startOfDecagonSketch = startProfile(plane, at = [(cos(0)*radius), (sin(0) * radius)])\n\n // Here's the reduce part.\n partialDecagon = startOfDecagonSketch\n for i in [1..10]:\n x = cos(stepAngle * i) * radius\n y = sin(stepAngle * i) * radius\n partialDecagon = line(partialDecagon, end = [x, y])\n fullDecagon = partialDecagon // it's now full\n return fullDecagon */\n\n// Use the `decagon` function declared above, to sketch a decagon with radius 5.\ndecagon(5.0)\n |> close()",
"// Declare a function that sketches a decagon.\nfn decagon(@radius) {\n // Each side of the decagon is turned this many radians from the previous angle.\n stepAngle = (1 / 10 * TAU): number(rad)\n\n // Start the decagon sketch at this point.\n startOfDecagonSketch = startSketchOn(XY)\n |> startProfile(at = [cos(0) * radius, sin(0) * radius])\n\n // Use a `reduce` to draw the remaining decagon sides.\n // For each number in the array 1..10, run the given function,\n // which takes a partially-sketched decagon and adds one more edge to it.\n fullDecagon = reduce(\n [1..10],\n initial = startOfDecagonSketch,\n f = fn(@i, accum) {\n // Draw one edge of the decagon.\n x = cos(stepAngle * i) * radius\n y = sin(stepAngle * i) * radius\n return line(accum, end = [x, y])\n },\n )\n\n return fullDecagon\n}\n\n/* The `decagon` above is basically like this pseudo-code:\nfn decagon(radius):\n stepAngle = ((1/10) * TAU): number(rad)\n plane = startSketchOn(XY)\n startOfDecagonSketch = startProfile(plane, at = [(cos(0)*radius), (sin(0) * radius)])\n\n // Here's the reduce part.\n partialDecagon = startOfDecagonSketch\n for i in [1..10]:\n x = cos(stepAngle * i) * radius\n y = sin(stepAngle * i) * radius\n partialDecagon = line(partialDecagon, end = [x, y])\n fullDecagon = partialDecagon // it's now full\n return fullDecagon */\n\n// Use the `decagon` function declared above, to sketch a decagon with radius 5.\ndecagon(5.0)\n |> close()",
false
]
]

View File

@ -514,14 +514,18 @@ test.describe('Command bar tests', () => {
})
})
test(`Zoom to fit to shared model on web`, async ({ page, scene }) => {
if (process.env.PLATFORM !== 'web') {
// This test is web-only
return
}
await test.step(`Prepare and navigate to home page with query params`, async () => {
// a quad in the top left corner of the XZ plane (which is out of the current view)
const code = `sketch001 = startSketchOn(XZ)
test(
`Zoom to fit to shared model on web`,
{ tag: ['@web'] },
async ({ page, scene }) => {
if (process.env.PLATFORM !== 'web') {
// This test is web-only
// TODO: re-enable on CI as part of a new @web test suite
return
}
await test.step(`Prepare and navigate to home page with query params`, async () => {
// a quad in the top left corner of the XZ plane (which is out of the current view)
const code = `sketch001 = startSketchOn(XZ)
profile001 = startProfile(sketch001, at = [-484.34, 484.95])
|> yLine(length = -69.1)
|> xLine(length = 66.84)
@ -529,26 +533,27 @@ profile001 = startProfile(sketch001, at = [-484.34, 484.95])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
`
const targetURL = `?create-file&name=test&units=mm&code=${encodeURIComponent(btoa(code))}&ask-open-desktop`
await page.goto(page.url() + targetURL)
expect(page.url()).toContain(targetURL)
})
const targetURL = `?create-file&name=test&units=mm&code=${encodeURIComponent(btoa(code))}&ask-open-desktop`
await page.goto(page.url() + targetURL)
expect(page.url()).toContain(targetURL)
})
await test.step(`Submit the command`, async () => {
await page.getByTestId('continue-to-web-app-button').click()
await test.step(`Submit the command`, async () => {
await page.getByTestId('continue-to-web-app-button').click()
await scene.connectionEstablished()
await scene.connectionEstablished()
// This makes SystemIOMachineActors.createKCLFile run after EngineStream/firstPlay
await page.waitForTimeout(3000)
// This makes SystemIOMachineActors.createKCLFile run after EngineStream/firstPlay
await page.waitForTimeout(3000)
await page.getByTestId('command-bar-submit').click()
})
await page.getByTestId('command-bar-submit').click()
})
await test.step(`Ensure we created the project and are in the modeling scene`, async () => {
await expectPixelColor(page, [252, 252, 252], { x: 600, y: 260 }, 8)
})
})
await test.step(`Ensure we created the project and are in the modeling scene`, async () => {
await expectPixelColor(page, [252, 252, 252], { x: 600, y: 260 }, 8)
})
}
)
test(`Can add and edit a named parameter or constant`, async ({
page,

View File

@ -1,5 +1,5 @@
import type { Locator, Page } from '@playwright/test'
import { secrets } from '@e2e/playwright/secrets'
import { token } from '@e2e/playwright/test-utils'
export class SignInPageFixture {
public page: Page
@ -25,7 +25,7 @@ export class SignInPageFixture {
// Device flow: stolen from the tauri days
// https://github.com/KittyCAD/modeling-app/blob/d916c7987452e480719004e6d11fd2e595c7d0eb/e2e/tauri/specs/app.spec.ts#L19
const headers = {
Authorization: `Bearer ${secrets.token}`,
Authorization: `Bearer ${token}`,
Accept: 'application/json',
'Content-Type': 'application/json',
}

View File

@ -21,9 +21,8 @@ test.describe('Onboarding tests', () => {
},
})
const bracketComment = '// Shelf Bracket'
const tutorialWelcomeHeading = page.getByText(
'Welcome to Design Studio! This'
'Welcome to Zoo Design Studio'
)
const nextButton = page.getByTestId('onboarding-next')
const prevButton = page.getByTestId('onboarding-prev')
@ -64,7 +63,6 @@ test.describe('Onboarding tests', () => {
shouldNormalise: true,
})
await scene.connectionEstablished()
await expect(toolbar.startSketchBtn).toBeEnabled({ timeout: 15_000 })
})
await test.step('Go home and verify we still see the tutorial button, then begin it.', async () => {
@ -90,11 +88,8 @@ test.describe('Onboarding tests', () => {
// })
await test.step('Ensure we see the welcome screen in a new project', async () => {
await expect(toolbar.projectName).toContainText('Tutorial Project 00')
await expect(toolbar.projectName).toContainText('tutorial-project')
await expect(tutorialWelcomeHeading).toBeVisible()
await editor.expectEditor.toContain(bracketComment)
await scene.connectionEstablished()
await expect(toolbar.startSketchBtn).toBeEnabled({ timeout: 15_000 })
})
await test.step('Test the clicking through the onboarding flow', async () => {
@ -122,7 +117,7 @@ test.describe('Onboarding tests', () => {
})
})
await test.step('Resetting onboarding from inside project should always make a new one', async () => {
await test.step('Resetting onboarding from inside project should always overwrite `tutorial-project`', async () => {
await test.step('Reset onboarding from settings', async () => {
await userMenuButton.click()
await userMenuSettingsButton.click()
@ -131,44 +126,66 @@ test.describe('Onboarding tests', () => {
await restartOnboardingSettingsButton.click()
})
await test.step('Makes a new project', async () => {
await expect(toolbar.projectName).toContainText('Tutorial Project 01')
await test.step('Gets to the onboarding start', async () => {
await expect(toolbar.projectName).toContainText('tutorial-project')
await expect(tutorialWelcomeHeading).toBeVisible()
await editor.expectEditor.toContain(bracketComment)
await scene.connectionEstablished()
await expect(toolbar.startSketchBtn).toBeEnabled({ timeout: 15_000 })
})
await test.step('Dismiss the onboarding', async () => {
await postDismissToast.waitFor({ state: 'detached' })
await postDismissToast.waitFor({ state: 'hidden' })
await page.keyboard.press('Escape')
await expect(postDismissToast).toBeVisible()
await expect(page.getByTestId('onboarding-content')).not.toBeVisible()
await expect.poll(() => page.url()).not.toContain('/onboarding')
})
})
await test.step('Resetting onboarding from home help menu makes a new project', async () => {
await test.step('Go home and reset onboarding from lower-right help menu', async () => {
await test.step('Verify no new projects were created', async () => {
await toolbar.logoLink.click()
await expect(homePage.tutorialBtn).not.toBeVisible()
await expect(
homePage.projectCard.getByText('Tutorial Project 00')
).toBeVisible()
await expect(
homePage.projectCard.getByText('Tutorial Project 01')
).toBeVisible()
await homePage.expectState({
projectCards: [
{ title: 'tutorial-project', fileCount: 7 },
{
title: 'testDefault',
fileCount: 1,
},
],
sortBy: 'last-modified-desc',
})
})
})
await helpMenuButton.click()
await helpMenuRestartOnboardingButton.click()
await test.step('Resetting onboarding from home help menu overwrites the `tutorial-project`', async () => {
await helpMenuButton.click()
await helpMenuRestartOnboardingButton.click()
await test.step('Gets to the onboarding start', async () => {
await expect(toolbar.projectName).toContainText('tutorial-project')
await expect(tutorialWelcomeHeading).toBeVisible()
await scene.connectionEstablished()
})
await test.step('Makes a new project', async () => {
await expect(toolbar.projectName).toContainText('Tutorial Project 02')
await expect(tutorialWelcomeHeading).toBeVisible()
await editor.expectEditor.toContain(bracketComment)
await scene.connectionEstablished()
await expect(toolbar.startSketchBtn).toBeEnabled({ timeout: 15_000 })
await test.step('Dismiss the onboarding', async () => {
await postDismissToast.waitFor({ state: 'hidden' })
await page.keyboard.press('Escape')
await expect(postDismissToast).toBeVisible()
await expect(page.getByTestId('onboarding-content')).not.toBeVisible()
await expect.poll(() => page.url()).not.toContain('/onboarding')
})
await test.step('Verify no new projects were created', async () => {
await toolbar.logoLink.click()
await expect(homePage.tutorialBtn).not.toBeVisible()
await homePage.expectState({
projectCards: [
{ title: 'tutorial-project', fileCount: 7 },
{
title: 'testDefault',
fileCount: 1,
},
],
sortBy: 'last-modified-desc',
})
})
})
})

View File

@ -2321,11 +2321,12 @@ extrude001 = extrude(sketch001, length = -12)
})
})
test(`Fillet point-and-click edit rejected when not in pipe`, async ({
test(`Fillet point-and-click edit standalone expression`, async ({
context,
page,
homePage,
scene,
editor,
toolbar,
cmdBar,
}) => {
@ -2339,23 +2340,44 @@ profile001 = circle(
extrude001 = extrude(profile001, length = 100)
fillet001 = fillet(extrude001, radius = 5, tags = [getOppositeEdge(seg01)])
`
await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode)
}, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene()
await scene.settled(cmdBar)
await test.step('Double-click in feature tree and expect error toast', async () => {
await test.step(`Initial test setup`, async () => {
await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode)
}, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene()
await scene.settled(cmdBar)
})
await test.step('Edit fillet', async () => {
await toolbar.openPane('feature-tree')
await toolbar.closePane('code')
const operationButton = await toolbar.getFeatureTreeOperation('Fillet', 0)
await operationButton.dblclick({ button: 'left' })
await expect(
page.getByText(
'Only chamfer and fillet in pipe expressions are supported for edits'
)
).toBeVisible()
await page.waitForTimeout(1000)
await cmdBar.expectState({
commandName: 'Fillet',
currentArgKey: 'radius',
currentArgValue: '5',
headerArguments: {
Radius: '5',
},
highlightedHeaderArg: 'radius',
stage: 'arguments',
})
await page.keyboard.insertText('20')
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Radius: '20',
},
commandName: 'Fillet',
})
await cmdBar.progressCmdBar()
})
await test.step('Confirm changes', async () => {
await toolbar.openPane('code')
await toolbar.closePane('feature-tree')
await editor.expectEditor.toContain('radius = 20')
})
})

View File

@ -1986,6 +1986,7 @@ test(
'Original project name persist after onboarding',
{ tag: '@electron' },
async ({ page, toolbar }, testInfo) => {
const nextButton = page.getByTestId('onboarding-next')
await page.setBodyDimensions({ width: 1200, height: 500 })
const getAllProjects = () => page.getByTestId('project-link').all()
@ -2000,10 +2001,10 @@ test(
await page.getByTestId('user-settings').click()
await page.getByRole('button', { name: 'Replay Onboarding' }).click()
const numberOfOnboardingSteps = 12
for (let clicks = 0; clicks < numberOfOnboardingSteps; clicks++) {
await page.getByTestId('onboarding-next').click()
while ((await nextButton.innerText()) !== 'Finish') {
await nextButton.click()
}
await nextButton.click()
await page.getByTestId('project-sidebar-toggle').click()
})
@ -2013,7 +2014,7 @@ test(
})
await test.step('Should show the original project called wrist brace', async () => {
const projectNames = ['Tutorial Project 00', 'wrist brace']
const projectNames = ['tutorial-project', 'wrist brace']
for (const [index, projectLink] of (await getAllProjects()).entries()) {
await expect(projectLink).toContainText(projectNames[index])
}

View File

@ -1,28 +0,0 @@
import { readFileSync } from 'fs'
const secrets: Record<string, string> = {}
const secretsPath = './e2e/playwright/playwright-secrets.env'
try {
const file = readFileSync(secretsPath, 'utf8')
file
.split('\n')
.filter((line) => line && line.length > 1)
.forEach((line) => {
// Allow line comments.
if (line.trimStart().startsWith('#')) return
const [key, value] = line.split('=')
// prefer env vars over secrets file
secrets[key] = process.env[key] || (value as any).replaceAll('"', '')
})
} catch (error: unknown) {
void error
// probably running in CI
console.warn(
`Error reading ${secretsPath}; environment variables will be used`
)
}
secrets.token = secrets.token || process.env.token || ''
secrets.snapshottoken = secrets.snapshottoken || process.env.snapshottoken || ''
// add more env vars here to make them available in CI
export { secrets }

View File

@ -0,0 +1,45 @@
import { expect, test } from '@e2e/playwright/zoo-test'
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.PLATFORM !== 'web') {
// This test is web-only
// TODO: re-enable on CI as part of a new @web test suite
return
}
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
}
}
)
})
})

View File

@ -3425,6 +3425,72 @@ profile003 = startProfile(sketch002, at = [-201.08, 254.17])
).toBeVisible()
})
})
test('empty draft sketch is cleaned up properly', async ({
scene,
toolbar,
cmdBar,
page,
homePage,
}) => {
// This is the sketch used in the original report, but any sketch would work
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`yRel002 = 200
lStraight = -200
yRel001 = -lStraight
length001 = lStraight
sketch001 = startSketchOn(XZ)
profile001 = startProfile(sketch001, at = [-102.72, 237.44])
|> yLine(length = lStraight)
|> tangentialArc(endAbsolute = [118.9, 23.57])
|> line(end = [-17.64, yRel002])
`
)
})
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene()
await scene.connectionEstablished()
await scene.settled(cmdBar)
// Ensure start sketch button is enabled
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
// Start a new sketch
const [selectXZPlane] = scene.makeMouseHelpers(650, 150)
await toolbar.startSketchPlaneSelection()
await selectXZPlane()
await page.waitForTimeout(2000) // wait for engine animation
// Switch to a different tool (circle)
await toolbar.circleBtn.click()
await expect(toolbar.circleBtn).toHaveAttribute('aria-pressed', 'true')
// Exit the empty sketch
await page.getByRole('button', { name: 'Exit Sketch' }).click()
// Ensure the feature tree now shows only one sketch
await toolbar.openFeatureTreePane()
await expect(
toolbar.featureTreePane.getByRole('button', { name: 'Sketch' })
).toHaveCount(1)
await toolbar.closeFeatureTreePane()
// Open the first sketch from the feature tree (the existing sketch)
await (await toolbar.getFeatureTreeOperation('Sketch', 0)).dblclick()
// timeout is a bit longer because when the bug happened, it did go into sketch mode for a split second, but returned
// automatically, we want to make sure it stays there.
await page.waitForTimeout(2000)
// Validate we are in sketch mode (Exit Sketch button visible)
await expect(
page.getByRole('button', { name: 'Exit Sketch' })
).toBeVisible()
})
test('adding a syntax error, recovers after fixing', async ({
page,
homePage,

View File

@ -1,20 +1,11 @@
import { spawn } from 'child_process'
import path from 'path'
import type { Models } from '@kittycad/lib'
import { KCL_DEFAULT_LENGTH } from '@src/lib/constants'
import fsp from 'fs/promises'
import JSZip from 'jszip'
import type { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
import type { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture'
import { secrets } from '@e2e/playwright/secrets'
import { TEST_SETTINGS, TEST_SETTINGS_KEY } from '@e2e/playwright/storageStates'
import type { Paths } from '@e2e/playwright/test-utils'
import {
doExport,
getUtils,
headerMasks,
networkingMasks,
lowerRightMasks,
settingsToToml,
} from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
@ -40,275 +31,6 @@ test.afterEach(async ({ page }) => {
test.setTimeout(60_000)
// We test this end to end already - getting this to work on web just to take
// a snapshot of it feels weird. I'd rather our regular tests fail.
// The primary failure is doExport now relies on the filesystem. We can follow
// up with another PR if we want this back.
test(
'exports of each format should work',
{ tag: ['@snapshot'] },
async ({ page, context, scene, cmdBar, tronApp }) => {
if (!tronApp) {
fail()
}
// FYI this test doesn't work with only engine running locally
// And you will need to have the KittyCAD CLI installed
const u = await getUtils(page)
await context.addInitScript(async () => {
;(window as any).playwrightSkipFilePicker = true
localStorage.setItem(
'persistCode',
`topAng = 25
bottomAng = 35
baseLen = 3.5
baseHeight = 1
totalHeightHalf = 2
armThick = 0.5
totalLen = 9.5
part001 = startSketchOn(-XZ)
|> startProfile(at = [0, 0])
|> yLine(length = baseHeight)
|> xLine(length = baseLen)
|> angledLine(
angle = topAng,
endAbsoluteY = totalHeightHalf,
tag = $seg04,
)
|> xLine(endAbsolute = totalLen, tag = $seg03)
|> yLine(length = -armThick, tag = $seg01)
|> angledLineThatIntersects(angle = turns::HALF_TURN, offset = -armThick, intersectTag = seg04)
|> angledLine(angle = segAng(seg04) + 180, endAbsoluteY = turns::ZERO)
|> angledLine(
angle = -bottomAng,
endAbsoluteY = -totalHeightHalf - armThick,
tag = $seg02,
)
|> xLine(length = endAbsolute = segEndX(seg03) + 0)
|> yLine(length = -segLen(seg01))
|> angledLineThatIntersects(angle = turns::HALF_TURN, offset = -armThick, intersectTag = seg02)
|> angledLine(angle = segAng(seg02) + 180, endAbsoluteY = -baseHeight)
|> xLine(endAbsolute = turns::ZERO)
|> close()
|> extrude(length = 4)`
)
})
await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart()
await scene.settled(cmdBar)
const axisDirectionPair: Models['AxisDirectionPair_type'] = {
axis: 'z',
direction: 'positive',
}
const sysType: Models['System_type'] = {
forward: axisDirectionPair,
up: axisDirectionPair,
}
const exportLocations: Paths[] = []
// NOTE it was easiest to leverage existing types and have doExport take Models['OutputFormat_type'] as in input
// just note that only `type` and `storage` are used for selecting the drop downs is the app
// the rest are only there to make typescript happy
// TODO - failing because of an exporter issue, ADD BACK IN WHEN ITS FIXED
// exportLocations.push(
// await doExport(
// {
// type: 'step',
// coords: sysType,
// },
// page
// )
// )
exportLocations.push(
await doExport(
{
type: 'ply',
coords: sysType,
selection: { type: 'default_scene' },
storage: 'ascii',
units: 'in',
},
tronApp.projectDirName,
page
)
)
exportLocations.push(
await doExport(
{
type: 'ply',
storage: 'binary_little_endian',
coords: sysType,
selection: { type: 'default_scene' },
units: 'in',
},
tronApp.projectDirName,
page
)
)
exportLocations.push(
await doExport(
{
type: 'ply',
storage: 'binary_big_endian',
coords: sysType,
selection: { type: 'default_scene' },
units: 'in',
},
tronApp.projectDirName,
page
)
)
exportLocations.push(
await doExport(
{
type: 'stl',
storage: 'ascii',
coords: sysType,
units: 'in',
selection: { type: 'default_scene' },
},
tronApp.projectDirName,
page
)
)
exportLocations.push(
await doExport(
{
type: 'stl',
storage: 'binary',
coords: sysType,
units: 'in',
selection: { type: 'default_scene' },
},
tronApp.projectDirName,
page
)
)
exportLocations.push(
await doExport(
{
// obj seems to be a little flaky, times out tests sometimes
type: 'obj',
coords: sysType,
units: 'in',
},
tronApp.projectDirName,
page
)
)
exportLocations.push(
await doExport(
{
type: 'gltf',
storage: 'embedded',
presentation: 'pretty',
},
tronApp.projectDirName,
page
)
)
exportLocations.push(
await doExport(
{
type: 'gltf',
storage: 'binary',
presentation: 'pretty',
},
tronApp.projectDirName,
page
)
)
exportLocations.push(
await doExport(
{
type: 'gltf',
storage: 'standard',
presentation: 'pretty',
},
tronApp.projectDirName,
page
)
)
// close page to disconnect websocket since we can only have one open atm
await page.close()
// snapshot exports, good compromise to capture that exports are healthy without getting bogged down in "did the formatting change" changes
// context: https://github.com/KittyCAD/modeling-app/issues/1222
for (let { modelPath, imagePath, outputType } of exportLocations) {
// May change depending on the file being dealt with
let cliCommand = `export ZOO_TOKEN=${secrets.snapshottoken} && zoo file snapshot --output-format=png --src-format=${outputType} ${modelPath} ${imagePath}`
const fileSize = (await fsp.stat(modelPath)).size
console.log(`Size of the file at ${modelPath}: ${fileSize} bytes`)
const parentPath = path.dirname(modelPath)
// This is actually a zip file.
if (modelPath.includes('gltf-standard.gltf')) {
console.log('Extracting files from archive')
const readZipFile = fsp.readFile(modelPath)
const unzip = (archive: any) =>
Object.values(archive.files).map((file: any) => ({
name: file.name,
promise: file.async('nodebuffer'),
}))
const writeFiles = (files: any) =>
Promise.all(
files.map((file: any) =>
file.promise.then((data: any) => {
console.log(`Writing ${file.name}`)
return fsp
.writeFile(`${parentPath}/${file.name}`, data)
.then(() => file.name)
})
)
)
const filenames = await readZipFile
.then(JSZip.loadAsync)
.then(unzip)
.then(writeFiles)
const gltfFilename = filenames.filter((t: string) =>
t.includes('.gltf')
)[0]
if (!gltfFilename) throw new Error('No gLTF in this archive')
cliCommand = `export ZOO_TOKEN=${secrets.snapshottoken} && zoo file snapshot --output-format=png --src-format=${outputType} ${parentPath}/${gltfFilename} ${imagePath}`
}
console.log(cliCommand)
const child = spawn(cliCommand, { shell: true })
const result = await new Promise<string>((resolve, reject) => {
child.on('error', (code: any, msg: any) => {
console.log('error', code, msg)
reject('error')
})
child.on('exit', (code, msg) => {
console.log('exit', code, msg)
if (code !== 0) {
reject(`exit code ${code} for model ${modelPath}`)
} else {
resolve('success')
}
})
child.stderr.on('data', (data) => console.log(`stderr: ${data}`))
child.stdout.on('data', (data) => console.log(`stdout: ${data}`))
})
expect(result).toBe('success')
if (result === 'success') {
console.log(`snapshot taken for ${modelPath}`)
} else {
console.log(`snapshot failed for ${modelPath}`)
}
}
}
)
const extrudeDefaultPlane = async (
context: any,
page: any,
@ -366,7 +88,7 @@ const extrudeDefaultPlane = async (
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
mask: networkingMasks(page),
mask: lowerRightMasks(page),
})
await u.openKclCodePanel()
}
@ -451,7 +173,7 @@ test(
await page.waitForTimeout(500)
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
mask: networkingMasks(page),
mask: lowerRightMasks(page),
})
const lineEndClick = () =>
@ -478,7 +200,7 @@ test(
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
mask: networkingMasks(page),
mask: lowerRightMasks(page),
})
await endOfTangentClk()
@ -488,7 +210,7 @@ test(
await threePointArcMidPointMv()
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
mask: networkingMasks(page),
mask: lowerRightMasks(page),
})
await threePointArcMidPointClk()
await page.waitForTimeout(100)
@ -497,7 +219,7 @@ test(
await page.waitForTimeout(500)
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
mask: networkingMasks(page),
mask: lowerRightMasks(page),
})
await threePointArcEndPointClk()
@ -517,7 +239,7 @@ test(
await page.waitForTimeout(500)
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
mask: networkingMasks(page),
mask: lowerRightMasks(page),
})
await arcEndClk()
}
@ -564,7 +286,7 @@ test(
// Ensure the draft rectangle looks the same as it usually does
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
mask: networkingMasks(page),
mask: lowerRightMasks(page),
})
}
)
@ -606,7 +328,7 @@ test(
// Ensure the draft rectangle looks the same as it usually does
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
mask: networkingMasks(page),
mask: lowerRightMasks(page),
})
await expect(page.locator('.cm-content')).toHaveText(
`sketch001 = startSketchOn(XZ)profile001 = circle(sketch001, center = [366.89, -62.01], radius = 1)`
@ -673,7 +395,7 @@ test.describe(
// screen shot should show the sketch
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
mask: networkingMasks(page),
mask: lowerRightMasks(page),
})
await u.doAndWaitForImageDiff(
@ -686,7 +408,7 @@ test.describe(
// second screen shot should look almost identical, i.e. scale should be the same.
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
mask: networkingMasks(page),
mask: lowerRightMasks(page),
})
})
@ -768,7 +490,7 @@ test.describe(
// screen shot should show the sketch
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
mask: networkingMasks(page),
mask: lowerRightMasks(page),
})
// exit sketch
@ -782,7 +504,7 @@ test.describe(
// second screen shot should look almost identical, i.e. scale should be the same.
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
mask: networkingMasks(page),
mask: lowerRightMasks(page),
})
})
}
@ -841,7 +563,7 @@ part002 = startSketchOn(part001, face = seg01)
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
mask: networkingMasks(page),
mask: lowerRightMasks(page),
})
}
)
@ -877,7 +599,7 @@ test(
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
mask: networkingMasks(page),
mask: lowerRightMasks(page),
})
}
)
@ -914,7 +636,7 @@ test(
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
mask: networkingMasks(page),
mask: lowerRightMasks(page),
})
}
)
@ -979,7 +701,7 @@ test.describe('Grid visibility', { tag: '@snapshot' }, () => {
await expect(stream).toHaveScreenshot({
maxDiffPixels: 100,
mask: [...headerMasks(page), ...networkingMasks(page)],
mask: [...headerMasks(page), ...lowerRightMasks(page)],
})
})
@ -1000,7 +722,7 @@ test.describe('Grid visibility', { tag: '@snapshot' }, () => {
await expect(stream).toHaveScreenshot({
maxDiffPixels: 100,
mask: [...headerMasks(page), ...networkingMasks(page)],
mask: [...headerMasks(page), ...lowerRightMasks(page)],
})
})
@ -1039,7 +761,7 @@ test.describe('Grid visibility', { tag: '@snapshot' }, () => {
await expect(stream).toHaveScreenshot({
maxDiffPixels: 100,
mask: [...headerMasks(page), ...networkingMasks(page)],
mask: [...headerMasks(page), ...lowerRightMasks(page)],
})
})
})
@ -1107,7 +829,7 @@ test('theme persists', async ({ page, context }) => {
await expect(page, 'expect screenshot to have light theme').toHaveScreenshot({
maxDiffPixels: 100,
mask: networkingMasks(page),
mask: lowerRightMasks(page),
})
})
@ -1148,7 +870,7 @@ sweepSketch = startSketchOn(XY)
await expect(page, 'expect small color widget').toHaveScreenshot({
maxDiffPixels: 100,
mask: networkingMasks(page),
mask: lowerRightMasks(page),
})
})
@ -1201,7 +923,7 @@ sweepSketch = startSketchOn(XY)
'expect small color widget to have window open'
).toHaveScreenshot({
maxDiffPixels: 100,
mask: networkingMasks(page),
mask: lowerRightMasks(page),
})
})
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 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: 56 KiB

After

Width:  |  Height:  |  Size: 56 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: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 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: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 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: 132 KiB

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

View File

@ -1,7 +1,6 @@
import type { SaveSettingsPayload } from '@src/lib/settings/settingsTypes'
import { Themes } from '@src/lib/theme'
import type { DeepPartial } from '@src/lib/types'
import { ONBOARDING_SUBPATHS } from '@src/lib/onboardingPaths'
import type { Settings } from '@rust/kcl-lib/bindings/Settings'
@ -29,28 +28,6 @@ export const TEST_SETTINGS: DeepPartial<Settings> = {
},
}
export const TEST_SETTINGS_ONBOARDING_USER_MENU: DeepPartial<Settings> = {
...TEST_SETTINGS,
app: {
...TEST_SETTINGS.app,
onboarding_status: ONBOARDING_SUBPATHS.USER_MENU,
},
}
export const TEST_SETTINGS_ONBOARDING_EXPORT: DeepPartial<Settings> = {
...TEST_SETTINGS,
app: { ...TEST_SETTINGS.app, onboarding_status: ONBOARDING_SUBPATHS.EXPORT },
}
export const TEST_SETTINGS_ONBOARDING_PARAMETRIC_MODELING: DeepPartial<Settings> =
{
...TEST_SETTINGS,
app: {
...TEST_SETTINGS.app,
onboarding_status: ONBOARDING_SUBPATHS.PARAMETRIC_MODELING,
},
}
export const TEST_SETTINGS_ONBOARDING_START: DeepPartial<Settings> = {
...TEST_SETTINGS,
app: { ...TEST_SETTINGS.app, onboarding_status: '' },

View File

@ -13,11 +13,15 @@ import fsp from 'fs/promises'
import pixelMatch from 'pixelmatch'
import type { Protocol } from 'playwright-core/types/protocol'
import { PNG } from 'pngjs'
import dotenv from 'dotenv'
const NODE_ENV = process.env.NODE_ENV || 'development'
dotenv.config({ path: [`.env.${NODE_ENV}.local`, `.env.${NODE_ENV}`] })
export const token = process.env.token || ''
import type { ProjectConfiguration } from '@rust/kcl-lib/bindings/ProjectConfiguration'
import { isErrorWhitelisted } from '@e2e/playwright/lib/console-error-whitelist'
import { secrets } from '@e2e/playwright/secrets'
import { TEST_SETTINGS, TEST_SETTINGS_KEY } from '@e2e/playwright/storageStates'
import { test } from '@e2e/playwright/zoo-test'
@ -31,8 +35,9 @@ export const headerMasks = (page: Page) => [
page.locator('#sidebar-bottom-ribbon'),
]
export const networkingMasks = (page: Page) => [
export const lowerRightMasks = (page: Page) => [
page.getByTestId('network-toggle'),
page.getByTestId('billing-remaining-bar'),
]
export type TestColor = [number, number, number]
@ -890,7 +895,7 @@ export async function setup(
localStorage.setItem('PLAYWRIGHT_TEST_DIR', PLAYWRIGHT_TEST_DIR)
},
{
token: secrets.token,
token,
settingsKey: TEST_SETTINGS_KEY,
settings: settingsToToml({
settings: {
@ -918,7 +923,7 @@ export async function setup(
await context.addCookies([
{
name: COOKIE_NAME,
value: secrets.token,
value: token,
path: '/',
domain: 'localhost',
secure: true,

View File

@ -52,7 +52,9 @@ test.describe('Testing loading external models', () => {
name,
})
const warningText = page.getByText('Overwrite current file with sample?')
const confirmButton = page.getByRole('button', { name: 'Submit command' })
const confirmButton = page.getByRole('button', {
name: 'Submit command',
})
await test.step(`Precondition: check the initial code`, async () => {
await u.openKclCodePanel()

View File

@ -20,7 +20,7 @@ import {
createProject,
executorInputPath,
getUtils,
networkingMasks,
lowerRightMasks,
settingsToToml,
tomlToSettings,
} from '@e2e/playwright/test-utils'
@ -1061,7 +1061,7 @@ fn cube`
'toggle-settings-initial.png',
{
maxDiffPixels: 15,
mask: networkingMasks(page),
mask: lowerRightMasks(page),
}
)
@ -1078,7 +1078,7 @@ fn cube`
'toggle-settings-initial.png',
{
maxDiffPixels: 15,
mask: networkingMasks(page),
mask: lowerRightMasks(page),
}
)
})

1
package-lock.json generated
View File

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

View File

@ -137,8 +137,8 @@
"test:unit:components": "jest -c jest-component-unit-tests/jest.config.ts --rootDir jest-component-unit-tests/",
"test:unit:kcl-samples": "vitest run --mode development ./src/lang/kclSamples.test.ts",
"test:playwright:electron": "playwright test --config=playwright.electron.config.ts --grep-invert=@snapshot",
"test:playwright:electron:local": "npm run tronb:vite:dev && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert=@snapshot --grep-invert=\"$(curl --silent https://test-analysis-bot.hawk-dinosaur.ts.net/projects/KittyCAD/modeling-app/tests/disabled/regex)\"",
"test:playwright:electron:local-engine": "npm run tronb:vite:dev && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot|@skipLocalEngine' --grep-invert=\"$(curl --silent https://test-analysis-bot.hawk-dinosaur.ts.net/projects/KittyCAD/modeling-app/tests/disabled/regex)\"",
"test:playwright:electron:local": "npm run tronb:vite:dev && playwright test --config=playwright.electron.config.ts --grep-invert=@snapshot --grep-invert=\"$(curl --silent https://test-analysis-bot.hawk-dinosaur.ts.net/projects/KittyCAD/modeling-app/tests/disabled/regex)\"",
"test:playwright:electron:local-engine": "npm run tronb:vite:dev && playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot|@skipLocalEngine' --grep-invert=\"$(curl --silent https://test-analysis-bot.hawk-dinosaur.ts.net/projects/KittyCAD/modeling-app/tests/disabled/regex)\"",
"test:unit:local": "npm run simpleserver:bg && npm run test:unit; kill-port 3000",
"test:unit:kcl-samples:local": "npm run simpleserver:bg && npm run test:unit:kcl-samples; kill-port 3000"
},

View File

@ -51,7 +51,7 @@ faceRotations = [
// Create faces by mapping over the rotations array
dodecFaces = map(
faceRotations,
f = fn(rotation) {
f = fn(@rotation) {
return createFaceTemplate(rotation[3])
|> rotate(
pitch = rotation[0],
@ -66,15 +66,15 @@ fn calculateArrayLength(@arr) {
return reduce(
arr,
initial = 0,
f = fn(item, accumulator) {
return accumulator + 1
f = fn(@item, accum) {
return accum + 1
},
)
}
fn createIntersection(@solids) {
fn reduceIntersect(previous, current) {
return intersect([previous, current])
fn reduceIntersect(@previous, accum) {
return intersect([previous, accum])
}
lastIndex = calculateArrayLength(solids) - 1
lastSolid = solids[lastIndex]

View File

@ -19,7 +19,7 @@ gearHeight = 3
cmo = 101
rs = map(
[0..cmo],
f = fn(i) {
f = fn(@i) {
return baseDiameter / 2 + i / cmo * (tipDiameter - baseDiameter) / 2
},
)
@ -27,7 +27,7 @@ rs = map(
// Calculate operating pressure angle
angles = map(
rs,
f = fn(r) {
f = fn(@r) {
return units::toDegrees(acos(baseDiameter / 2 / r))
},
)
@ -35,7 +35,7 @@ angles = map(
// Calculate the involute function
invas = map(
angles,
f = fn(a) {
f = fn(@a) {
return tan(a) - units::toRadians(a)
},
)
@ -43,14 +43,14 @@ invas = map(
// Map the involute curve
xs = map(
[0..cmo],
f = fn(i) {
f = fn(@i) {
return rs[i] * cos(invas[i]: number(rad))
},
)
ys = map(
[0..cmo],
f = fn(i) {
f = fn(@i) {
return rs[i] * sin(invas[i]: number(rad))
},
)
@ -63,15 +63,15 @@ body = startSketchOn(XY)
toothAngle = 360 / nTeeth / 1.5
// Plot the involute curve
fn leftInvolute(i, sg) {
fn leftInvolute(@i, accum) {
j = 100 - i // iterate backwards
return line(sg, endAbsolute = [xs[j], ys[j]])
return line(accum, endAbsolute = [xs[j], ys[j]])
}
fn rightInvolute(i, sg) {
fn rightInvolute(@i, accum) {
x = rs[i] * cos(-toothAngle + units::toDegrees(atan(ys[i] / xs[i])))
y = -rs[i] * sin(-toothAngle + units::toDegrees(atan(ys[i] / xs[i])))
return line(sg, endAbsolute = [x, y])
return line(accum, endAbsolute = [x, y])
}
// Draw gear teeth

View File

@ -4,293 +4,450 @@
"pathFromProjectDirectoryToFirstFile": "80-20-rail/main.kcl",
"multipleFiles": false,
"title": "80/20 Rail",
"description": "An 80/20 extruded aluminum linear rail. T-slot profile adjustable by profile height, rail length, and origin position"
"description": "An 80/20 extruded aluminum linear rail. T-slot profile adjustable by profile height, rail length, and origin position",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "axial-fan/main.kcl",
"multipleFiles": true,
"title": "PC Fan",
"description": "A small axial fan, used to push or draw airflow over components to remove excess heat"
"description": "A small axial fan, used to push or draw airflow over components to remove excess heat",
"files": [
"fan-housing.kcl",
"fan.kcl",
"main.kcl",
"motor.kcl",
"parameters.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "ball-bearing/main.kcl",
"multipleFiles": false,
"title": "Ball Bearing",
"description": "A ball bearing is a type of rolling-element bearing that uses balls to maintain the separation between the bearing races. The primary purpose of a ball bearing is to reduce rotational friction and support radial and axial loads."
"description": "A ball bearing is a type of rolling-element bearing that uses balls to maintain the separation between the bearing races. The primary purpose of a ball bearing is to reduce rotational friction and support radial and axial loads.",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "bench/main.kcl",
"multipleFiles": true,
"title": "Bench",
"description": "This is a slight remix of Depep1's original 3D Boaty (https://www.printables.com/model/1141963-3d-boaty). This is a tool used for benchmarking 3D FDM printers for bed adhesion, overhangs, bridging and top surface quality. The name of this file is a bit of misnomer, the shape of the object is a typical park bench."
"description": "This is a slight remix of Depep1's original 3D Boaty (https://www.printables.com/model/1141963-3d-boaty). This is a tool used for benchmarking 3D FDM printers for bed adhesion, overhangs, bridging and top surface quality. The name of this file is a bit of misnomer, the shape of the object is a typical park bench.",
"files": [
"bench-parts.kcl",
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "bottle/main.kcl",
"multipleFiles": false,
"title": "Bottle",
"description": "A simple bottle with a hollow, watertight interior"
"description": "A simple bottle with a hollow, watertight interior",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "bracket/main.kcl",
"multipleFiles": false,
"title": "Shelf Bracket",
"description": "This is a bracket that holds a shelf. It is made of aluminum and is designed to hold a force of 300 lbs. The bracket is 6 inches wide and the force is applied at the end of the shelf, 12 inches from the wall. The bracket has a factor of safety of 1.2. The legs of the bracket are 5 inches and 2 inches long. The thickness of the bracket is calculated from the constraints provided."
"description": "This is a bracket that holds a shelf. It is made of aluminum and is designed to hold a force of 300 lbs. The bracket is 6 inches wide and the force is applied at the end of the shelf, 12 inches from the wall. The bracket has a factor of safety of 1.2. The legs of the bracket are 5 inches and 2 inches long. The thickness of the bracket is calculated from the constraints provided.",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "car-wheel-assembly/main.kcl",
"multipleFiles": true,
"title": "Car Wheel Assembly",
"description": "A car wheel assembly with a rotor, tire, and lug nuts."
"description": "A car wheel assembly with a rotor, tire, and lug nuts.",
"files": [
"brake-caliper.kcl",
"car-rotor.kcl",
"car-tire.kcl",
"car-wheel.kcl",
"lug-nut.kcl",
"main.kcl",
"parameters.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "color-cube/main.kcl",
"multipleFiles": false,
"title": "Color Cube",
"description": "This is a color cube centered about the origin. It is used to help determine orientation in the scene."
"description": "This is a color cube centered about the origin. It is used to help determine orientation in the scene.",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "cycloidal-gear/main.kcl",
"multipleFiles": false,
"title": "Cycloidal Gear",
"description": "A cycloidal gear is a gear with a continuous, curved tooth profile. They are used in watchmaking and high precision robotics actuation"
"description": "A cycloidal gear is a gear with a continuous, curved tooth profile. They are used in watchmaking and high precision robotics actuation",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "dodecahedron/main.kcl",
"multipleFiles": false,
"title": "Dodecahedron",
"description": "A regular dodecahedron or pentagonal dodecahedron is a dodecahedron composed of regular pentagonal faces, three meeting at each vertex. This example shows constructing the a dodecahedron with a series of intersects."
"description": "A regular dodecahedron or pentagonal dodecahedron is a dodecahedron composed of regular pentagonal faces, three meeting at each vertex. This example shows constructing the a dodecahedron with a series of intersects.",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "dual-basin-utility-sink/main.kcl",
"multipleFiles": false,
"title": "Dual-Basin Utility Sink",
"description": "A stainless steel sink unit with dual rectangular basins and six under-counter storage compartments."
"description": "A stainless steel sink unit with dual rectangular basins and six under-counter storage compartments.",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "enclosure/main.kcl",
"multipleFiles": false,
"title": "Enclosure",
"description": "An enclosure body and sealing lid for storing items"
"description": "An enclosure body and sealing lid for storing items",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "exhaust-manifold/main.kcl",
"multipleFiles": false,
"title": "Exhaust Manifold",
"description": "A welded exhaust header for an inline 4-cylinder engine"
"description": "A welded exhaust header for an inline 4-cylinder engine",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "flange/main.kcl",
"multipleFiles": false,
"title": "Flange",
"description": "A flange is a flat rim, collar, or rib, typically forged or cast, that is used to strengthen an object, guide it, or attach it to another object. Flanges are known for their use in various applications, including piping, plumbing, and mechanical engineering, among others."
"description": "A flange is a flat rim, collar, or rib, typically forged or cast, that is used to strengthen an object, guide it, or attach it to another object. Flanges are known for their use in various applications, including piping, plumbing, and mechanical engineering, among others.",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "focusrite-scarlett-mounting-bracket/main.kcl",
"multipleFiles": false,
"title": "A mounting bracket for the Focusrite Scarlett Solo audio interface",
"description": "This is a bracket that holds an audio device underneath a desk or shelf. The audio device has dimensions of 144mm wide, 80mm length and 45mm depth with fillets of 6mm. This mounting bracket is designed to be 3D printed with PLA material"
"description": "This is a bracket that holds an audio device underneath a desk or shelf. The audio device has dimensions of 144mm wide, 80mm length and 45mm depth with fillets of 6mm. This mounting bracket is designed to be 3D printed with PLA material",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "food-service-spatula/main.kcl",
"multipleFiles": false,
"title": "Food Service Spatula",
"description": "Use these spatulas for mixing, flipping, and scraping."
"description": "Use these spatulas for mixing, flipping, and scraping.",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "french-press/main.kcl",
"multipleFiles": false,
"title": "French Press",
"description": "A french press immersion coffee maker"
"description": "A french press immersion coffee maker",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "gear/main.kcl",
"multipleFiles": false,
"title": "Spur Gear",
"description": "A rotating machine part having cut teeth or, in the case of a cogwheel, inserted teeth (called cogs), which mesh with another toothed part to transmit torque. Geared devices can change the speed, torque, and direction of a power source. The two elements that define a gear are its circular shape and the teeth that are integrated into its outer edge, which are designed to fit into the teeth of another gear."
"description": "A rotating machine part having cut teeth or, in the case of a cogwheel, inserted teeth (called cogs), which mesh with another toothed part to transmit torque. Geared devices can change the speed, torque, and direction of a power source. The two elements that define a gear are its circular shape and the teeth that are integrated into its outer edge, which are designed to fit into the teeth of another gear.",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "gear-rack/main.kcl",
"multipleFiles": false,
"title": "100mm Gear Rack",
"description": "A flat bar or rail that is engraved with teeth along its length. These teeth are designed to mesh with the teeth of a gear, known as a pinion. When the pinion, a small cylindrical gear, rotates, its teeth engage with the teeth on the rack, causing the rack to move linearly. Conversely, linear motion applied to the rack will cause the pinion to rotate."
"description": "A flat bar or rail that is engraved with teeth along its length. These teeth are designed to mesh with the teeth of a gear, known as a pinion. When the pinion, a small cylindrical gear, rotates, its teeth engage with the teeth on the rack, causing the rack to move linearly. Conversely, linear motion applied to the rack will cause the pinion to rotate.",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "gridfinity-baseplate/main.kcl",
"multipleFiles": false,
"title": "Gridfinity Baseplate",
"description": "Gridfinity is a system to help you work more efficiently. This is a system invented by Zack Freedman. There are two main components the baseplate and the bins. The components are comprised of a matrix of squares. Allowing easy stacking and expansion"
"description": "Gridfinity is a system to help you work more efficiently. This is a system invented by Zack Freedman. There are two main components the baseplate and the bins. The components are comprised of a matrix of squares. Allowing easy stacking and expansion",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "gridfinity-baseplate-magnets/main.kcl",
"multipleFiles": false,
"title": "Gridfinity Baseplate With Magnets",
"description": "Gridfinity is a system to help you work more efficiently. This is a system invented by Zack Freedman. There are two main components the baseplate and the bins. The components are comprised of a matrix of squares. Allowing easy stacking and expansion. This baseplate version includes holes for magnet placement"
"description": "Gridfinity is a system to help you work more efficiently. This is a system invented by Zack Freedman. There are two main components the baseplate and the bins. The components are comprised of a matrix of squares. Allowing easy stacking and expansion. This baseplate version includes holes for magnet placement",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "gridfinity-bins/main.kcl",
"multipleFiles": false,
"title": "Gridfinity Bins",
"description": "Gridfinity is a system to help you work more efficiently. This is a system invented by Zack Freedman. There are two main components the baseplate and the bins. The components are comprised of a matrix of squares. Allowing easy stacking and expansion"
"description": "Gridfinity is a system to help you work more efficiently. This is a system invented by Zack Freedman. There are two main components the baseplate and the bins. The components are comprised of a matrix of squares. Allowing easy stacking and expansion",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "gridfinity-bins-stacking-lip/main.kcl",
"multipleFiles": false,
"title": "Gridfinity Bins With A Stacking Lip",
"description": "Gridfinity is a system to help you work more efficiently. This is a system invented by Zack Freedman. There are two main components the baseplate and the bins. The components are comprised of a matrix of squares. Allowing easy stacking and expansion. This Gridfinity bins version includes a lip to allowable stacking Gridfinity bins"
"description": "Gridfinity is a system to help you work more efficiently. This is a system invented by Zack Freedman. There are two main components the baseplate and the bins. The components are comprised of a matrix of squares. Allowing easy stacking and expansion. This Gridfinity bins version includes a lip to allowable stacking Gridfinity bins",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "hex-nut/main.kcl",
"multipleFiles": false,
"title": "Hex Nut",
"description": "A hex nut is a type of fastener with a threaded hole and a hexagonal outer shape, used in a wide variety of applications to secure parts together. The hexagonal shape allows for a greater torque to be applied with wrenches or tools, making it one of the most common nut types in hardware."
"description": "A hex nut is a type of fastener with a threaded hole and a hexagonal outer shape, used in a wide variety of applications to secure parts together. The hexagonal shape allows for a greater torque to be applied with wrenches or tools, making it one of the most common nut types in hardware.",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "i-beam/main.kcl",
"multipleFiles": false,
"title": "I-beam",
"description": "A structural metal beam with an I shaped cross section. Often used in construction and architecture"
"description": "A structural metal beam with an I shaped cross section. Often used in construction and architecture",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "keyboard/main.kcl",
"multipleFiles": false,
"title": "Zoo Keyboard",
"description": "A custom keyboard with Zoo brand lettering"
"description": "A custom keyboard with Zoo brand lettering",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "kitt/main.kcl",
"multipleFiles": false,
"title": "Kitt",
"description": "The beloved KittyCAD mascot in a voxelized style."
"description": "The beloved KittyCAD mascot in a voxelized style.",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "lego/main.kcl",
"multipleFiles": false,
"title": "Lego Brick",
"description": "A standard Lego brick. This is a small, plastic construction block toy that can be interlocked with other blocks to build various structures, models, and figures. There are a lot of hacks used in this code."
"description": "A standard Lego brick. This is a small, plastic construction block toy that can be interlocked with other blocks to build various structures, models, and figures. There are a lot of hacks used in this code.",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "makeup-mirror/main.kcl",
"multipleFiles": false,
"title": "Makeup Mirror",
"description": "A circular vanity mirror mounted on a swiveling arm with pivot joints, used for personal grooming."
"description": "A circular vanity mirror mounted on a swiveling arm with pivot joints, used for personal grooming.",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "mounting-plate/main.kcl",
"multipleFiles": false,
"title": "Mounting Plate",
"description": "A flat piece of material, often metal or plastic, that serves as a support or base for attaching, securing, or mounting various types of equipment, devices, or components."
"description": "A flat piece of material, often metal or plastic, that serves as a support or base for attaching, securing, or mounting various types of equipment, devices, or components.",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "multi-axis-robot/main.kcl",
"multipleFiles": true,
"title": "Robot Arm",
"description": "A 4 axis robotic arm for industrial use. These machines can be used for assembly, packaging, organization of goods, and quality inspection processes"
"description": "A 4 axis robotic arm for industrial use. These machines can be used for assembly, packaging, organization of goods, and quality inspection processes",
"files": [
"globals.kcl",
"main.kcl",
"robot-arm-base.kcl",
"robot-arm-j2.kcl",
"robot-arm-j3.kcl",
"robot-rotating-base.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "parametric-bearing-pillow-block/main.kcl",
"multipleFiles": false,
"title": "Parametric Bearing Pillow Block",
"description": "A bearing pillow block, also known as a plummer block or pillow block bearing, is a pedestal used to provide support for a rotating shaft with the help of compatible bearings and various accessories. Housing a bearing, the pillow block provides a secure and stable foundation that allows the shaft to rotate smoothly within its machinery setup. These components are essential in a wide range of mechanical systems and machinery, playing a key role in reducing friction and supporting radial and axial loads."
"description": "A bearing pillow block, also known as a plummer block or pillow block bearing, is a pedestal used to provide support for a rotating shaft with the help of compatible bearings and various accessories. Housing a bearing, the pillow block provides a secure and stable foundation that allows the shaft to rotate smoothly within its machinery setup. These components are essential in a wide range of mechanical systems and machinery, playing a key role in reducing friction and supporting radial and axial loads.",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "pipe/main.kcl",
"multipleFiles": false,
"title": "Pipe",
"description": "Piping for the pipe flange assembly"
"description": "Piping for the pipe flange assembly",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "pipe-flange-assembly/main.kcl",
"multipleFiles": true,
"title": "Pipe and Flange Assembly",
"description": "A crucial component in various piping systems, designed to facilitate the connection, disconnection, and access to piping for inspection, cleaning, and modifications. This assembly combines pipes (long cylindrical conduits) with flanges (plate-like fittings) to create a secure yet detachable joint."
"description": "A crucial component in various piping systems, designed to facilitate the connection, disconnection, and access to piping for inspection, cleaning, and modifications. This assembly combines pipes (long cylindrical conduits) with flanges (plate-like fittings) to create a secure yet detachable joint.",
"files": [
"1120t74-pipe.kcl",
"68095k348-flange.kcl",
"91251a404-bolt.kcl",
"9472k188-gasket.kcl",
"95479a127-hex-nut.kcl",
"98017a257-washer.kcl",
"main.kcl",
"parameters.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "pipe-with-bend/main.kcl",
"multipleFiles": false,
"title": "Pipe with bend",
"description": "A tubular section or hollow cylinder, usually but not necessarily of circular cross-section, used mainly to convey substances that can flow."
"description": "A tubular section or hollow cylinder, usually but not necessarily of circular cross-section, used mainly to convey substances that can flow.",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "poopy-shoe/main.kcl",
"multipleFiles": false,
"title": "Poopy Shoe",
"description": "poop shute for bambu labs printer - optimized for printing."
"description": "poop shute for bambu labs printer - optimized for printing.",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "router-template-cross-bar/main.kcl",
"multipleFiles": false,
"title": "Router template for a cross bar",
"description": "A guide for routing a notch into a cross bar."
"description": "A guide for routing a notch into a cross bar.",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "router-template-slate/main.kcl",
"multipleFiles": false,
"title": "Router Template for a Slate",
"description": "A guide for routing a slate for a cross bar."
"description": "A guide for routing a slate for a cross bar.",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "sheet-metal-bracket/main.kcl",
"multipleFiles": false,
"title": "Sheet Metal Bracket",
"description": "A component typically made from flat sheet metal through various manufacturing processes such as bending, punching, cutting, and forming. These brackets are used to support, attach, or mount other hardware components, often providing a structural or functional base for assembly."
"description": "A component typically made from flat sheet metal through various manufacturing processes such as bending, punching, cutting, and forming. These brackets are used to support, attach, or mount other hardware components, often providing a structural or functional base for assembly.",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "socket-head-cap-screw/main.kcl",
"multipleFiles": false,
"title": "Socket Head Cap Screw",
"description": "This is for a #10-24 screw that is 1.00 inches long. A socket head cap screw is a type of fastener that is widely used in a variety of applications requiring a high strength fastening solution. It is characterized by its cylindrical head and internal hexagonal drive, which allows for tightening with an Allen wrench or hex key."
"description": "This is for a #10-24 screw that is 1.00 inches long. A socket head cap screw is a type of fastener that is widely used in a variety of applications requiring a high strength fastening solution. It is characterized by its cylindrical head and internal hexagonal drive, which allows for tightening with an Allen wrench or hex key.",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "walkie-talkie/main.kcl",
"multipleFiles": true,
"title": "Walkie Talkie",
"description": "A portable, handheld two-way radio device that allows users to communicate wirelessly over short to medium distances. It operates on specific radio frequencies and features a push-to-talk button for transmitting messages, making it ideal for quick and reliable communication in outdoor, work, or emergency settings."
"description": "A portable, handheld two-way radio device that allows users to communicate wirelessly over short to medium distances. It operates on specific radio frequencies and features a push-to-talk button for transmitting messages, making it ideal for quick and reliable communication in outdoor, work, or emergency settings.",
"files": [
"antenna.kcl",
"body.kcl",
"button.kcl",
"case.kcl",
"knob.kcl",
"main.kcl",
"parameters.kcl",
"talk-button.kcl",
"zoo-logo.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "washer/main.kcl",
"multipleFiles": false,
"title": "Washer",
"description": "A small, typically disk-shaped component with a hole in the middle, used in a wide range of applications, primarily in conjunction with fasteners like bolts and screws. Washers distribute the load of a fastener across a broader area. This is especially important when the fastening surface is soft or uneven, as it helps to prevent damage to the surface and ensures the load is evenly distributed, reducing the risk of the fastener becoming loose over time."
"description": "A small, typically disk-shaped component with a hole in the middle, used in a wide range of applications, primarily in conjunction with fasteners like bolts and screws. Washers distribute the load of a fastener across a broader area. This is especially important when the fastening surface is soft or uneven, as it helps to prevent damage to the surface and ensures the load is evenly distributed, reducing the risk of the fastener becoming loose over time.",
"files": [
"main.kcl"
]
}
]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

View File

@ -18,10 +18,10 @@ screenSketch = startSketchOn(XZ)
|> close()
// Create transform functions for the speaker grid pattern
fn transformX(i) {
fn transformX(@i) {
return { translate = [.125 * i, 0] }
}
fn transformY(i) {
fn transformY(@i) {
return { translate = [0, -.125 * i] }
}

20
rust/Cargo.lock generated
View File

@ -1815,7 +1815,7 @@ dependencies = [
[[package]]
name = "kcl-bumper"
version = "0.1.67"
version = "0.1.68"
dependencies = [
"anyhow",
"clap",
@ -1826,7 +1826,7 @@ dependencies = [
[[package]]
name = "kcl-derive-docs"
version = "0.1.67"
version = "0.1.68"
dependencies = [
"Inflector",
"anyhow",
@ -1845,7 +1845,7 @@ dependencies = [
[[package]]
name = "kcl-directory-test-macro"
version = "0.1.67"
version = "0.1.68"
dependencies = [
"proc-macro2",
"quote",
@ -1854,7 +1854,7 @@ dependencies = [
[[package]]
name = "kcl-language-server"
version = "0.2.67"
version = "0.2.68"
dependencies = [
"anyhow",
"clap",
@ -1875,7 +1875,7 @@ dependencies = [
[[package]]
name = "kcl-language-server-release"
version = "0.1.67"
version = "0.1.68"
dependencies = [
"anyhow",
"clap",
@ -1895,7 +1895,7 @@ dependencies = [
[[package]]
name = "kcl-lib"
version = "0.2.67"
version = "0.2.68"
dependencies = [
"anyhow",
"approx 0.5.1",
@ -1970,7 +1970,7 @@ dependencies = [
[[package]]
name = "kcl-python-bindings"
version = "0.3.67"
version = "0.3.68"
dependencies = [
"anyhow",
"kcl-lib",
@ -1985,7 +1985,7 @@ dependencies = [
[[package]]
name = "kcl-test-server"
version = "0.1.67"
version = "0.1.68"
dependencies = [
"anyhow",
"hyper 0.14.32",
@ -1998,7 +1998,7 @@ dependencies = [
[[package]]
name = "kcl-to-core"
version = "0.1.67"
version = "0.1.68"
dependencies = [
"anyhow",
"async-trait",
@ -2012,7 +2012,7 @@ dependencies = [
[[package]]
name = "kcl-wasm-lib"
version = "0.1.67"
version = "0.1.68"
dependencies = [
"anyhow",
"bson",

View File

@ -4,9 +4,9 @@ kcl_lib_flags := "-p kcl-lib --features artifact-graph"
# Run the same lint checks we run in CI.
lint:
cargo clippy --workspace --all-targets --tests --all-features --examples --benches -- -D warnings
cargo clippy --workspace --all-targets --all-features -- -D warnings
# Ensure we can build without extra feature flags.
cargo clippy -p kcl-lib --tests --examples --benches -- -D warnings
cargo clippy -p kcl-lib --all-targets -- -D warnings
# Run the stdlib docs generation
redo-kcl-stdlib-docs-no-imgs:

View File

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

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-directory-test-macro"
description = "A tool for generating tests from a directory of kcl files"
version = "0.1.67"
version = "0.1.68"
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.67"
version = "0.1.68"
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.67"
version = "0.2.68"
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.67"
version = "0.2.68"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"

View File

@ -46,7 +46,7 @@ shellExtrude = startSketchOn(s, "start")
|> close()
|> extrude(length = -(height - t))
fn tr(i) {
fn tr(@i) {
j = i + 1
x = (j/wbumps) * pitch
y = (j % wbumps) * pitch

View File

@ -1295,13 +1295,20 @@ impl Node<CallExpressionKw> {
// Build a hashmap from argument labels to the final evaluated values.
let mut fn_args = IndexMap::with_capacity(self.arguments.len());
let mut errors = Vec::new();
for arg_expr in &self.arguments {
let source_range = SourceRange::from(arg_expr.arg.clone());
let metadata = Metadata { source_range };
let value = ctx
.execute_expr(&arg_expr.arg, exec_state, &metadata, &[], StatementKind::Expression)
.await?;
fn_args.insert(arg_expr.label.name.clone(), Arg::new(value, source_range));
let arg = Arg::new(value, source_range);
match &arg_expr.label {
Some(l) => {
fn_args.insert(l.name.clone(), arg);
}
None => errors.push(arg),
}
}
let fn_args = fn_args; // remove mutability
@ -1321,10 +1328,11 @@ impl Node<CallExpressionKw> {
KwArgs {
unlabeled,
labeled: fn_args,
errors,
},
self.into(),
ctx.clone(),
exec_state.mod_local.pipe_value.clone().map(|v| Arg::new(v, callsite)),
exec_state.pipe_value().map(|v| Arg::new(v.clone(), callsite)),
);
match ctx.stdlib.get_either(fn_name) {
FunctionKind::Core(func) => {
@ -1835,89 +1843,6 @@ impl Node<PipeExpression> {
}
}
/// For each argument given,
/// assign it to a parameter of the function, in the given block of function memory.
/// Returns Err if too few/too many arguments were given for the function.
fn assign_args_to_params(
function_expression: NodeRef<'_, FunctionExpression>,
args: Vec<Arg>,
exec_state: &mut ExecState,
) -> Result<(), KclError> {
let num_args = function_expression.number_of_args();
let (min_params, max_params) = num_args.into_inner();
let n = args.len();
// Check if the user supplied too many arguments
// (we'll check for too few arguments below).
let err_wrong_number_args = KclError::Semantic(KclErrorDetails {
message: if min_params == max_params {
format!("Expected {min_params} arguments, got {n}")
} else {
format!("Expected {min_params}-{max_params} arguments, got {n}")
},
source_ranges: vec![function_expression.into()],
});
if n > max_params {
return Err(err_wrong_number_args);
}
// Add the arguments to the memory. A new call frame should have already
// been created.
for (index, param) in function_expression.params.iter().enumerate() {
if let Some(arg) = args.get(index) {
// Argument was provided.
if let Some(ty) = &param.type_ {
let value = arg
.value
.coerce(
&RuntimeType::from_parsed(ty.inner.clone(), exec_state, arg.source_range).unwrap(),
exec_state,
)
.map_err(|e| {
let mut message = format!(
"Argument requires a value with type `{}`, but found {}",
ty.inner,
arg.value.human_friendly_type(),
);
if let Some(ty) = e.explicit_coercion {
// TODO if we have access to the AST for the argument we could choose which example to suggest.
message = format!("{message}\n\nYou may need to add information about the type of the argument, for example:\n using a numeric suffix: `42{ty}`\n or using type ascription: `foo(): number({ty})`");
}
KclError::Semantic(KclErrorDetails {
message,
source_ranges: vec![arg.source_range],
})
})?;
exec_state
.mut_stack()
.add(param.identifier.name.clone(), value, (&param.identifier).into())?;
} else {
exec_state.mut_stack().add(
param.identifier.name.clone(),
arg.value.clone(),
(&param.identifier).into(),
)?;
}
} else {
// Argument was not provided.
if let Some(ref default_val) = param.default_value {
// If the corresponding parameter is optional,
// then it's fine, the user doesn't need to supply it.
let value = KclValue::from_default_param(default_val.clone(), exec_state);
exec_state
.mut_stack()
.add(param.identifier.name.clone(), value, (&param.identifier).into())?;
} else {
// But if the corresponding parameter was required,
// then the user has called with too few arguments.
return Err(err_wrong_number_args);
}
}
}
Ok(())
}
fn type_check_params_kw(
fn_name: Option<&str>,
function_expression: NodeRef<'_, FunctionExpression>,
@ -1977,6 +1902,44 @@ fn type_check_params_kw(
}
}
if !args.errors.is_empty() {
let actuals = args.labeled.keys();
let formals: Vec<_> = function_expression
.params
.iter()
.filter_map(|p| {
if !p.labeled {
return None;
}
let name = &p.identifier.name;
if actuals.clone().any(|a| a == name) {
return None;
}
Some(format!("`{name}`"))
})
.collect();
let suggestion = if formals.is_empty() {
String::new()
} else {
format!("; suggested labels: {}", formals.join(", "))
};
let mut errors = args.errors.iter().map(|e| {
CompilationError::err(
e.source_range,
format!("This argument needs a label, but it doesn't have one{suggestion}"),
)
});
let first = errors.next().unwrap();
errors.for_each(|e| exec_state.err(e));
return Err(KclError::Semantic(first.into()));
}
if let Some(arg) = &mut args.unlabeled {
if let Some(p) = function_expression.params.iter().find(|p| !p.labeled) {
if let Some(ty) = &p.type_ {
@ -2102,42 +2065,6 @@ fn coerce_result_type(
}
}
async fn call_user_defined_function(
args: Vec<Arg>,
memory: EnvironmentRef,
function_expression: NodeRef<'_, FunctionExpression>,
exec_state: &mut ExecState,
ctx: &ExecutorContext,
) -> Result<Option<KclValue>, KclError> {
// Create a new environment to execute the function body in so that local
// variables shadow variables in the parent scope. The new environment's
// parent should be the environment of the closure.
exec_state.mut_stack().push_new_env_for_call(memory);
if let Err(e) = assign_args_to_params(function_expression, args, exec_state) {
exec_state.mut_stack().pop_env();
return Err(e);
}
// Execute the function body using the memory we just created.
let result = ctx
.exec_block(&function_expression.body, exec_state, BodyType::Block)
.await;
let mut result = result.map(|_| {
exec_state
.stack()
.get(memory::RETURN_NAME, function_expression.as_source_range())
.ok()
.cloned()
});
result = coerce_result_type(result, function_expression, exec_state);
// Restore the previous memory.
exec_state.mut_stack().pop_env();
result
}
async fn call_user_defined_function_kw(
fn_name: Option<&str>,
args: crate::std::args::KwArgs,
@ -2176,41 +2103,6 @@ async fn call_user_defined_function_kw(
}
impl FunctionSource {
pub async fn call(
&self,
fn_name: Option<String>,
exec_state: &mut ExecState,
ctx: &ExecutorContext,
mut args: Vec<Arg>,
callsite: SourceRange,
) -> Result<Option<KclValue>, KclError> {
match self {
FunctionSource::Std { props, .. } => {
if args.len() <= 1 {
let args = crate::std::Args::new_kw(
KwArgs {
unlabeled: args.pop(),
labeled: IndexMap::new(),
},
callsite,
ctx.clone(),
exec_state.mod_local.pipe_value.clone().map(|v| Arg::new(v, callsite)),
);
self.call_kw(fn_name, exec_state, ctx, args, callsite).await
} else {
Err(KclError::Semantic(KclErrorDetails {
message: format!("{} requires its arguments to be labelled", props.name),
source_ranges: vec![callsite],
}))
}
}
FunctionSource::User { ast, memory, .. } => {
call_user_defined_function(args, *memory, ast, exec_state, ctx).await
}
FunctionSource::None => unreachable!(),
}
}
pub async fn call_kw(
&self,
fn_name: Option<String>,
@ -2404,7 +2296,7 @@ mod test {
(
"all params required, and all given, should be OK",
vec![req_param("x")],
vec![mem(1)],
vec![("x", mem(1))],
Ok(additional_program_memory(&[("x".to_owned(), mem(1))])),
),
(
@ -2413,7 +2305,7 @@ mod test {
vec![],
Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![SourceRange::default()],
message: "Expected 1 arguments, got 0".to_owned(),
message: "This function requires a parameter x, but you haven't passed it one.".to_owned(),
})),
),
(
@ -2428,13 +2320,13 @@ mod test {
vec![],
Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![SourceRange::default()],
message: "Expected 1-2 arguments, got 0".to_owned(),
message: "This function requires a parameter x, but you haven't passed it one.".to_owned(),
})),
),
(
"mixed params, minimum given, should be OK",
vec![req_param("x"), opt_param("y")],
vec![mem(1)],
vec![("x", mem(1))],
Ok(additional_program_memory(&[
("x".to_owned(), mem(1)),
("y".to_owned(), KclValue::none()),
@ -2443,21 +2335,12 @@ mod test {
(
"mixed params, maximum given, should be OK",
vec![req_param("x"), opt_param("y")],
vec![mem(1), mem(2)],
vec![("x", mem(1)), ("y", mem(2))],
Ok(additional_program_memory(&[
("x".to_owned(), mem(1)),
("y".to_owned(), mem(2)),
])),
),
(
"mixed params, too many given",
vec![req_param("x"), opt_param("y")],
vec![mem(1), mem(2), mem(3)],
Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![SourceRange::default()],
message: "Expected 1-2 arguments, got 3".to_owned(),
})),
),
] {
// Run each test.
let func_expr = &Node::no_src(FunctionExpression {
@ -2466,7 +2349,18 @@ mod test {
return_type: None,
digest: None,
});
let args = args.into_iter().map(Arg::synthetic).collect();
let labeled = args
.iter()
.map(|(name, value)| {
let arg = Arg::new(value.clone(), SourceRange::default());
((*name).to_owned(), arg)
})
.collect::<IndexMap<_, _>>();
let args = KwArgs {
unlabeled: None,
labeled,
errors: Vec::new(),
};
let exec_ctxt = ExecutorContext {
engine: Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new().await.unwrap(),
@ -2478,7 +2372,8 @@ mod test {
};
let mut exec_state = ExecState::new(&exec_ctxt);
exec_state.mod_local.stack = Stack::new_for_tests();
let actual = assign_args_to_params(func_expr, args, &mut exec_state).map(|_| exec_state.mod_local.stack);
let actual =
assign_args_to_params_kw(None, func_expr, args, &mut exec_state).map(|_| exec_state.mod_local.stack);
assert_eq!(
actual, expected,
"failed test '{test_name}':\ngot {actual:?}\nbut expected\n{expected:?}"
@ -2704,4 +2599,30 @@ a = foo()
parse_execute(program).await.unwrap_err();
}
#[tokio::test(flavor = "multi_thread")]
async fn test_sensible_error_when_missing_equals_in_kwarg() {
for (i, call) in ["f(x=1,y)", "f(x=1,3,z)", "f(x=1,y,z=1)", "f(x=1, 3 + 4, z=1)"]
.into_iter()
.enumerate()
{
let program = format!(
"fn foo() {{ return 0 }}
y = 42
z = 0
fn f(x, y, z) {{ return 0 }}
{call}"
);
let err = parse_execute(&program).await.unwrap_err();
let msg = err.message();
assert!(
msg.contains("This argument needs a label, but it doesn't have one"),
"failed test {i}: {msg}"
);
assert!(msg.contains("`y`"), "failed test {i}, missing `y`: {msg}");
if i == 0 {
assert!(msg.contains("`z`"), "failed test {i}, missing `z`: {msg}");
}
}
}
}

View File

@ -307,7 +307,7 @@ impl KclValue {
} => "number(Angle)",
KclValue::Number { .. } => "number",
KclValue::String { .. } => "string (text)",
KclValue::MixedArray { .. } => "array (list)",
KclValue::MixedArray { .. } => "mixed array (list)",
KclValue::HomArray { .. } => "array (list)",
KclValue::Object { .. } => "object",
KclValue::Module { .. } => "module",

View File

@ -1741,7 +1741,7 @@ foo
#[tokio::test(flavor = "multi_thread")]
async fn test_pattern_transform_function_cannot_access_future_definitions() {
let ast = r#"
fn transform(replicaId) {
fn transform(@replicaId) {
// x shouldn't be defined yet.
scale = x
return {
@ -1932,7 +1932,7 @@ a = []
notArray = !a";
assert_eq!(
parse_execute(code5).await.unwrap_err().message(),
"Cannot apply unary operator ! to non-boolean value: array (list)",
"Cannot apply unary operator ! to non-boolean value: mixed array (list)",
);
let code6 = "

View File

@ -283,6 +283,10 @@ impl ExecState {
source_ranges: vec![source_range],
})
}
pub(crate) fn pipe_value(&self) -> Option<&KclValue> {
self.mod_local.pipe_value.as_ref()
}
}
impl GlobalState {

View File

@ -488,7 +488,9 @@ impl CallExpressionKw {
}
hasher.update(slf.arguments.len().to_ne_bytes());
for argument in slf.arguments.iter_mut() {
hasher.update(argument.label.compute_digest());
if let Some(l) = &mut argument.label {
hasher.update(l.compute_digest());
}
hasher.update(argument.arg.compute_digest());
}
});

View File

@ -460,10 +460,12 @@ impl Node<Program> {
crate::walk::Node::CallExpressionKw(call) => {
if call.inner.callee.inner.name.inner.name == "appearance" {
for arg in &call.arguments {
if arg.label.inner.name == "color" {
// Get the value of the argument.
if let Expr::Literal(literal) = &arg.arg {
add_color(literal);
if let Some(l) = &arg.label {
if l.inner.name == "color" {
// Get the value of the argument.
if let Expr::Literal(literal) = &arg.arg {
add_color(literal);
}
}
}
}
@ -1872,7 +1874,7 @@ pub struct CallExpressionKw {
#[ts(export)]
#[serde(tag = "type")]
pub struct LabeledArg {
pub label: Node<Identifier>,
pub label: Option<Node<Identifier>>,
pub arg: Expr,
}
@ -1917,7 +1919,7 @@ impl CallExpressionKw {
self.unlabeled
.iter()
.map(|e| (None, e))
.chain(self.arguments.iter().map(|arg| (Some(&arg.label), &arg.arg)))
.chain(self.arguments.iter().map(|arg| (arg.label.as_ref(), &arg.arg)))
}
pub fn replace_value(&mut self, source_range: SourceRange, new_value: Expr) {

View File

@ -2714,13 +2714,18 @@ fn pipe_sep(i: &mut TokenSlice) -> PResult<()> {
}
fn labeled_argument(i: &mut TokenSlice) -> PResult<LabeledArg> {
separated_pair(
terminated(nameable_identifier, opt(whitespace)),
terminated(one_of((TokenType::Operator, "=")), opt(whitespace)),
(
opt((
terminated(nameable_identifier, opt(whitespace)),
terminated(one_of((TokenType::Operator, "=")), opt(whitespace)),
)),
expression,
)
.map(|(label, arg)| LabeledArg { label, arg })
.parse_next(i)
.map(|(label, arg)| LabeledArg {
label: label.map(|(l, _)| l),
arg,
})
.parse_next(i)
}
/// A type of a function argument.
@ -3040,6 +3045,7 @@ fn fn_call_kw(i: &mut TokenSlice) -> PResult<Node<CallExpressionKw>> {
return Ok(result);
}
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
enum ArgPlace {
NonCode(Node<NonCodeNode>),
@ -3068,24 +3074,17 @@ fn fn_call_kw(i: &mut TokenSlice) -> PResult<Node<CallExpressionKw>> {
}
ArgPlace::UnlabeledArg(arg) => {
let followed_by_equals = peek((opt(whitespace), equals)).parse_next(i).is_ok();
let err = if followed_by_equals {
ErrMode::Cut(
if followed_by_equals {
return Err(ErrMode::Cut(
CompilationError::fatal(
SourceRange::from(arg),
"This argument has a label, but no value. Put some value after the equals sign",
)
.into(),
)
));
} else {
ErrMode::Cut(
CompilationError::fatal(
SourceRange::from(arg),
"This argument needs a label, but it doesn't have one",
)
.into(),
)
};
return Err(err);
args.push(LabeledArg { label: None, arg });
}
}
}
Ok((args, non_code_nodes))
@ -3098,7 +3097,9 @@ fn fn_call_kw(i: &mut TokenSlice) -> PResult<Node<CallExpressionKw>> {
// Validate there aren't any duplicate labels.
let mut counted_labels = IndexMap::with_capacity(args.len());
for arg in &args {
*counted_labels.entry(&arg.label.inner.name).or_insert(0) += 1;
if let Some(l) = &arg.label {
*counted_labels.entry(&l.inner.name).or_insert(0) += 1;
}
}
if let Some((duplicated, n)) = counted_labels.iter().find(|(_label, n)| n > &&1) {
let msg = format!(
@ -4923,27 +4924,6 @@ bar = 1
crate::parsing::top_level_parse(some_program_string).unwrap(); // Updated import path
}
#[test]
fn test_sensible_error_when_missing_equals_in_kwarg() {
for (i, program) in ["f(x=1,y)", "f(x=1,y,z)", "f(x=1,y,z=1)", "f(x=1, y, z=1)"]
.into_iter()
.enumerate()
{
let tokens = crate::parsing::token::lex(program, ModuleId::default()).unwrap();
let err = fn_call_kw.parse(tokens.as_slice()).unwrap_err();
let cause = err.inner().cause.as_ref().unwrap();
assert_eq!(
cause.message, "This argument needs a label, but it doesn't have one",
"failed test {i}: {program}"
);
assert_eq!(
cause.source_range.start(),
program.find("y").unwrap(),
"failed test {i}: {program}"
);
}
}
#[test]
fn test_sensible_error_when_missing_rhs_of_kw_arg() {
for (i, program) in ["f(x, y=)"].into_iter().enumerate() {

View File

@ -496,43 +496,84 @@ pub enum OnboardingStatus {
/// The user has dismissed onboarding.
Dismissed,
// Routes
#[serde(rename = "/")]
#[display("/")]
Index,
#[serde(rename = "/camera")]
#[display("/camera")]
Camera,
#[serde(rename = "/streaming")]
#[display("/streaming")]
Streaming,
#[serde(rename = "/editor")]
#[display("/editor")]
Editor,
#[serde(rename = "/parametric-modeling")]
#[display("/parametric-modeling")]
ParametricModeling,
#[serde(rename = "/interactive-numbers")]
#[display("/interactive-numbers")]
InteractiveNumbers,
#[serde(rename = "/command-k")]
#[display("/command-k")]
CommandK,
#[serde(rename = "/user-menu")]
#[display("/user-menu")]
UserMenu,
#[serde(rename = "/project-menu")]
#[display("/project-menu")]
ProjectMenu,
#[serde(rename = "/export")]
#[display("/export")]
Export,
#[serde(rename = "/sketching")]
#[display("/sketching")]
Sketching,
#[serde(rename = "/future-work")]
#[display("/future-work")]
FutureWork,
// Desktop Routes
#[serde(rename = "/desktop")]
#[display("/desktop")]
DesktopWelcome,
#[serde(rename = "/desktop/scene")]
#[display("/desktop/scene")]
DesktopScene,
#[serde(rename = "/desktop/toolbar")]
#[display("/desktop/toolbar")]
DesktopToolbar,
#[serde(rename = "/desktop/text-to-cad")]
#[display("/desktop/text-to-cad")]
DesktopTextToCadWelcome,
#[serde(rename = "/desktop/text-to-cad-prompt")]
#[display("/desktop/text-to-cad-prompt")]
DesktopTextToCadPrompt,
#[serde(rename = "/desktop/feature-tree-pane")]
#[display("/desktop/feature-tree-pane")]
DesktopFeatureTreePane,
#[serde(rename = "/desktop/code-pane")]
#[display("/desktop/code-pane")]
DesktopCodePane,
#[serde(rename = "/desktop/project-pane")]
#[display("/desktop/project-pane")]
DesktopProjectFilesPane,
#[serde(rename = "/desktop/other-panes")]
#[display("/desktop/other-panes")]
DesktopOtherPanes,
#[serde(rename = "/desktop/prompt-to-edit")]
#[display("/desktop/prompt-to-edit")]
DesktopPromptToEditWelcome,
#[serde(rename = "/desktop/prompt-to-edit-prompt")]
#[display("/desktop/prompt-to-edit-prompt")]
DesktopPromptToEditPrompt,
#[serde(rename = "/desktop/prompt-to-edit-result")]
#[display("/desktop/prompt-to-edit-result")]
DesktopPromptToEditResult,
#[serde(rename = "/desktop/imports")]
#[display("/desktop/imports")]
DesktopImports,
#[serde(rename = "/desktop/exports")]
#[display("/desktop/exports")]
DesktopExports,
#[serde(rename = "/desktop/conclusion")]
#[display("/desktop/conclusion")]
DesktopConclusion,
// Browser Routes
#[serde(rename = "/browser")]
#[display("/browser")]
BrowserWelcome,
#[serde(rename = "/browser/scene")]
#[display("/browser/scene")]
BrowserScene,
#[serde(rename = "/browser/toolbar")]
#[display("/browser/toolbar")]
BrowserToolbar,
#[serde(rename = "/browser/text-to-cad")]
#[display("/browser/text-to-cad")]
BrowserTextToCadWelcome,
#[serde(rename = "/browser/text-to-cad-prompt")]
#[display("/browser/text-to-cad-prompt")]
BrowserTextToCadPrompt,
#[serde(rename = "/browser/feature-tree-pane")]
#[display("/browser/feature-tree-pane")]
BrowserFeatureTreePane,
#[serde(rename = "/browser/prompt-to-edit")]
#[display("/browser/prompt-to-edit")]
BrowserPromptToEditWelcome,
#[serde(rename = "/browser/prompt-to-edit-prompt")]
#[display("/browser/prompt-to-edit-prompt")]
BrowserPromptToEditPrompt,
#[serde(rename = "/browser/prompt-to-edit-result")]
#[display("/browser/prompt-to-edit-result")]
BrowserPromptToEditResult,
#[serde(rename = "/browser/conclusion")]
#[display("/browser/conclusion")]
BrowserConclusion,
}
fn is_default<T: Default + PartialEq>(t: &T) -> bool {

View File

@ -220,6 +220,7 @@ struct KclMetadata {
multiple_files: bool,
title: String,
description: String,
files: Vec<String>,
}
// Function to read and parse .kcl files
@ -263,12 +264,16 @@ fn get_kcl_metadata(project_path: &Path, files: &[String]) -> Option<KclMetadata
primary_kcl_file.clone()
};
let mut files = files.to_vec();
files.sort();
Some(KclMetadata {
file: primary_kcl_file,
path_from_project_directory_to_first_file: path_from_project_dir,
multiple_files: files.len() > 1,
title,
description,
files,
})
}

View File

@ -62,6 +62,7 @@ pub struct KwArgs {
pub unlabeled: Option<Arg>,
/// Labeled args.
pub labeled: IndexMap<String, Arg>,
pub errors: Vec<Arg>,
}
impl KwArgs {

View File

@ -1,6 +1,10 @@
use indexmap::IndexMap;
use kcl_derive_docs::stdlib;
use super::{args::Arg, Args};
use super::{
args::{Arg, KwArgs},
Args,
};
use crate::{
errors::{KclError, KclErrorDetails},
execution::{
@ -44,7 +48,7 @@ pub async fn map(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
/// // Call `map`, using an anonymous function instead of a named one.
/// circles = map(
/// [1..3],
/// f = fn(id) {
/// f = fn(@id) {
/// return startSketchOn(XY)
/// |> circle( center= [id * 2 * r, 0], radius= r)
/// }
@ -81,9 +85,18 @@ async fn call_map_closure(
exec_state: &mut ExecState,
ctxt: &ExecutorContext,
) -> Result<KclValue, KclError> {
let output = map_fn
.call(None, exec_state, ctxt, vec![Arg::synthetic(input)], source_range)
.await?;
let kw_args = KwArgs {
unlabeled: Some(Arg::new(input, source_range)),
labeled: Default::default(),
errors: Vec::new(),
};
let args = Args::new_kw(
kw_args,
source_range,
ctxt.clone(),
exec_state.pipe_value().map(|v| Arg::new(v.clone(), source_range)),
);
let output = map_fn.call_kw(None, exec_state, ctxt, args, source_range).await?;
let source_ranges = vec![source_range];
let output = output.ok_or_else(|| {
KclError::Semantic(KclErrorDetails {
@ -106,7 +119,7 @@ pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
/// using the previous value and the element.
/// ```no_run
/// // This function adds two numbers.
/// fn add(a, b) { return a + b }
/// fn add(@a, accum) { return a + accum }
///
/// // This function adds an array of numbers.
/// // It uses the `reduce` function, to call the `add` function on every
@ -118,7 +131,7 @@ pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
/// fn sum(arr):
/// sumSoFar = 0
/// for i in arr:
/// sumSoFar = add(sumSoFar, i)
/// sumSoFar = add(i, sumSoFar)
/// return sumSoFar
/// */
///
@ -131,7 +144,7 @@ pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
/// // an anonymous `add` function as its parameter, instead of declaring a
/// // named function outside.
/// arr = [1, 2, 3]
/// sum = reduce(arr, initial = 0, f = fn (i, result_so_far) { return i + result_so_far })
/// sum = reduce(arr, initial = 0, f = fn (@i, accum) { return i + accum })
///
/// // We use `assert` to check that our `sum` function gives the
/// // expected result. It's good to check your work!
@ -150,11 +163,11 @@ pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
/// // Use a `reduce` to draw the remaining decagon sides.
/// // For each number in the array 1..10, run the given function,
/// // which takes a partially-sketched decagon and adds one more edge to it.
/// fullDecagon = reduce([1..10], initial = startOfDecagonSketch, f = fn(i, partialDecagon) {
/// fullDecagon = reduce([1..10], initial = startOfDecagonSketch, f = fn(@i, accum) {
/// // Draw one edge of the decagon.
/// x = cos(stepAngle * i) * radius
/// y = sin(stepAngle * i) * radius
/// return line(partialDecagon, end = [x, y])
/// return line(accum, end = [x, y])
/// })
///
/// return fullDecagon
@ -209,16 +222,28 @@ async fn inner_reduce<'a>(
async fn call_reduce_closure(
elem: KclValue,
start: KclValue,
accum: KclValue,
reduce_fn: &FunctionSource,
source_range: SourceRange,
exec_state: &mut ExecState,
ctxt: &ExecutorContext,
) -> Result<KclValue, KclError> {
// Call the reduce fn for this repetition.
let reduce_fn_args = vec![Arg::synthetic(elem), Arg::synthetic(start)];
let mut labeled = IndexMap::with_capacity(1);
labeled.insert("accum".to_string(), Arg::new(accum, source_range));
let kw_args = KwArgs {
unlabeled: Some(Arg::new(elem, source_range)),
labeled,
errors: Vec::new(),
};
let reduce_fn_args = Args::new_kw(
kw_args,
source_range,
ctxt.clone(),
exec_state.pipe_value().map(|v| Arg::new(v.clone(), source_range)),
);
let transform_fn_return = reduce_fn
.call(None, exec_state, ctxt, reduce_fn_args, source_range)
.call_kw(None, exec_state, ctxt, reduce_fn_args, source_range)
.await?;
// Unpack the returned transform object.

View File

@ -16,7 +16,7 @@ use serde::Serialize;
use uuid::Uuid;
use super::{
args::Arg,
args::{Arg, KwArgs},
utils::{point_3d_to_mm, point_to_mm},
};
use crate::{
@ -427,9 +427,19 @@ async fn make_transform<T: GeometryTrait>(
ty: NumericType::count(),
meta: vec![source_range.into()],
};
let transform_fn_args = vec![Arg::synthetic(repetition_num)];
let kw_args = KwArgs {
unlabeled: Some(Arg::new(repetition_num, source_range)),
labeled: Default::default(),
errors: Vec::new(),
};
let transform_fn_args = Args::new_kw(
kw_args,
source_range,
ctxt.clone(),
exec_state.pipe_value().map(|v| Arg::new(v.clone(), source_range)),
);
let transform_fn_return = transform
.call(None, exec_state, ctxt, transform_fn_args, source_range)
.call_kw(None, exec_state, ctxt, transform_fn_args, source_range)
.await?;
// Unpack the returned transform object.

View File

@ -405,9 +405,13 @@ impl CallExpressionKw {
impl LabeledArg {
fn recast(&self, options: &FormatOptions, indentation_level: usize, ctxt: ExprContext) -> String {
let label = &self.label.name;
let arg = self.arg.recast(options, indentation_level, ctxt);
format!("{label} = {arg}")
let mut result = String::new();
if let Some(l) = &self.label {
result.push_str(&l.name);
result.push_str(" = ");
}
result.push_str(&self.arg.recast(options, indentation_level, ctxt));
result
}
}
@ -2532,7 +2536,7 @@ sketch002 = startSketchOn({
let input = r#"squares_out = reduce(
arr,
n = 0: number,
f = fn(i, squares) {
f = fn(@i, accum) {
return 1
},
)

View File

@ -1,5 +1,150 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Operations executed double_map_fn.kcl
---
[]
[
{
"type": "GroupBegin",
"group": {
"type": "FunctionCall",
"name": null,
"functionSourceRange": [],
"unlabeledArg": {
"value": {
"type": "Number",
"value": 0.0,
"ty": {
"type": "Known",
"type": "Count"
}
},
"sourceRange": []
},
"labeledArgs": {}
},
"sourceRange": []
},
{
"type": "GroupBegin",
"group": {
"type": "FunctionCall",
"name": null,
"functionSourceRange": [],
"unlabeledArg": {
"value": {
"type": "Number",
"value": 1.0,
"ty": {
"type": "Known",
"type": "Count"
}
},
"sourceRange": []
},
"labeledArgs": {}
},
"sourceRange": []
},
{
"type": "GroupBegin",
"group": {
"type": "FunctionCall",
"name": null,
"functionSourceRange": [],
"unlabeledArg": {
"value": {
"type": "Number",
"value": 2.0,
"ty": {
"type": "Known",
"type": "Count"
}
},
"sourceRange": []
},
"labeledArgs": {}
},
"sourceRange": []
},
{
"type": "GroupBegin",
"group": {
"type": "FunctionCall",
"name": null,
"functionSourceRange": [],
"unlabeledArg": {
"value": {
"type": "Number",
"value": 1.0,
"ty": {
"type": "Known",
"type": "Count"
}
},
"sourceRange": []
},
"labeledArgs": {}
},
"sourceRange": []
},
{
"type": "GroupBegin",
"group": {
"type": "FunctionCall",
"name": null,
"functionSourceRange": [],
"unlabeledArg": {
"value": {
"type": "Number",
"value": 2.0,
"ty": {
"type": "Known",
"type": "Count"
}
},
"sourceRange": []
},
"labeledArgs": {}
},
"sourceRange": []
},
{
"type": "GroupBegin",
"group": {
"type": "FunctionCall",
"name": null,
"functionSourceRange": [],
"unlabeledArg": {
"value": {
"type": "Number",
"value": 3.0,
"ty": {
"type": "Known",
"type": "Count"
}
},
"sourceRange": []
},
"labeledArgs": {}
},
"sourceRange": []
},
{
"type": "GroupEnd"
},
{
"type": "GroupEnd"
},
{
"type": "GroupEnd"
},
{
"type": "GroupEnd"
},
{
"type": "GroupEnd"
},
{
"type": "GroupEnd"
}
]

View File

@ -1,235 +1,235 @@
```mermaid
flowchart LR
subgraph path5 [Path]
5["Path<br>[1091, 1141, 0]"]
8["Segment<br>[1091, 1141, 0]"]
5["Path<br>[1096, 1146, 0]"]
8["Segment<br>[1096, 1146, 0]"]
220[Solid2d]
end
subgraph path6 [Path]
6["Path<br>[1610, 1647, 0]"]
9["Segment<br>[1306, 1344, 0]"]
10["Segment<br>[1306, 1344, 0]"]
11["Segment<br>[1306, 1344, 0]"]
12["Segment<br>[1306, 1344, 0]"]
13["Segment<br>[1306, 1344, 0]"]
14["Segment<br>[1306, 1344, 0]"]
15["Segment<br>[1306, 1344, 0]"]
16["Segment<br>[1306, 1344, 0]"]
17["Segment<br>[1306, 1344, 0]"]
18["Segment<br>[1306, 1344, 0]"]
19["Segment<br>[1306, 1344, 0]"]
20["Segment<br>[1306, 1344, 0]"]
21["Segment<br>[1306, 1344, 0]"]
22["Segment<br>[1306, 1344, 0]"]
23["Segment<br>[1306, 1344, 0]"]
24["Segment<br>[1306, 1344, 0]"]
25["Segment<br>[1306, 1344, 0]"]
26["Segment<br>[1306, 1344, 0]"]
27["Segment<br>[1306, 1344, 0]"]
28["Segment<br>[1306, 1344, 0]"]
29["Segment<br>[1306, 1344, 0]"]
30["Segment<br>[1306, 1344, 0]"]
31["Segment<br>[1306, 1344, 0]"]
32["Segment<br>[1306, 1344, 0]"]
33["Segment<br>[1306, 1344, 0]"]
34["Segment<br>[1306, 1344, 0]"]
35["Segment<br>[1306, 1344, 0]"]
36["Segment<br>[1306, 1344, 0]"]
37["Segment<br>[1306, 1344, 0]"]
38["Segment<br>[1306, 1344, 0]"]
39["Segment<br>[1306, 1344, 0]"]
40["Segment<br>[1306, 1344, 0]"]
41["Segment<br>[1306, 1344, 0]"]
42["Segment<br>[1306, 1344, 0]"]
43["Segment<br>[1306, 1344, 0]"]
44["Segment<br>[1306, 1344, 0]"]
45["Segment<br>[1306, 1344, 0]"]
46["Segment<br>[1306, 1344, 0]"]
47["Segment<br>[1306, 1344, 0]"]
48["Segment<br>[1306, 1344, 0]"]
49["Segment<br>[1306, 1344, 0]"]
50["Segment<br>[1306, 1344, 0]"]
51["Segment<br>[1306, 1344, 0]"]
52["Segment<br>[1306, 1344, 0]"]
53["Segment<br>[1306, 1344, 0]"]
54["Segment<br>[1306, 1344, 0]"]
55["Segment<br>[1306, 1344, 0]"]
56["Segment<br>[1306, 1344, 0]"]
57["Segment<br>[1306, 1344, 0]"]
58["Segment<br>[1306, 1344, 0]"]
59["Segment<br>[1306, 1344, 0]"]
60["Segment<br>[1306, 1344, 0]"]
61["Segment<br>[1306, 1344, 0]"]
62["Segment<br>[1306, 1344, 0]"]
63["Segment<br>[1306, 1344, 0]"]
64["Segment<br>[1306, 1344, 0]"]
65["Segment<br>[1306, 1344, 0]"]
66["Segment<br>[1306, 1344, 0]"]
67["Segment<br>[1306, 1344, 0]"]
68["Segment<br>[1306, 1344, 0]"]
69["Segment<br>[1306, 1344, 0]"]
70["Segment<br>[1306, 1344, 0]"]
71["Segment<br>[1306, 1344, 0]"]
72["Segment<br>[1306, 1344, 0]"]
73["Segment<br>[1306, 1344, 0]"]
74["Segment<br>[1306, 1344, 0]"]
75["Segment<br>[1306, 1344, 0]"]
76["Segment<br>[1306, 1344, 0]"]
77["Segment<br>[1306, 1344, 0]"]
78["Segment<br>[1306, 1344, 0]"]
79["Segment<br>[1306, 1344, 0]"]
80["Segment<br>[1306, 1344, 0]"]
81["Segment<br>[1306, 1344, 0]"]
82["Segment<br>[1306, 1344, 0]"]
83["Segment<br>[1306, 1344, 0]"]
84["Segment<br>[1306, 1344, 0]"]
85["Segment<br>[1306, 1344, 0]"]
86["Segment<br>[1306, 1344, 0]"]
87["Segment<br>[1306, 1344, 0]"]
88["Segment<br>[1306, 1344, 0]"]
89["Segment<br>[1306, 1344, 0]"]
90["Segment<br>[1306, 1344, 0]"]
91["Segment<br>[1306, 1344, 0]"]
92["Segment<br>[1306, 1344, 0]"]
93["Segment<br>[1306, 1344, 0]"]
94["Segment<br>[1306, 1344, 0]"]
95["Segment<br>[1306, 1344, 0]"]
96["Segment<br>[1306, 1344, 0]"]
97["Segment<br>[1306, 1344, 0]"]
98["Segment<br>[1306, 1344, 0]"]
99["Segment<br>[1306, 1344, 0]"]
100["Segment<br>[1306, 1344, 0]"]
101["Segment<br>[1306, 1344, 0]"]
102["Segment<br>[1306, 1344, 0]"]
103["Segment<br>[1306, 1344, 0]"]
104["Segment<br>[1306, 1344, 0]"]
105["Segment<br>[1306, 1344, 0]"]
106["Segment<br>[1306, 1344, 0]"]
107["Segment<br>[1306, 1344, 0]"]
108["Segment<br>[1306, 1344, 0]"]
109["Segment<br>[1306, 1344, 0]"]
110["Segment<br>[1526, 1556, 0]"]
111["Segment<br>[1526, 1556, 0]"]
112["Segment<br>[1526, 1556, 0]"]
113["Segment<br>[1526, 1556, 0]"]
114["Segment<br>[1526, 1556, 0]"]
115["Segment<br>[1526, 1556, 0]"]
116["Segment<br>[1526, 1556, 0]"]
117["Segment<br>[1526, 1556, 0]"]
118["Segment<br>[1526, 1556, 0]"]
119["Segment<br>[1526, 1556, 0]"]
120["Segment<br>[1526, 1556, 0]"]
121["Segment<br>[1526, 1556, 0]"]
122["Segment<br>[1526, 1556, 0]"]
123["Segment<br>[1526, 1556, 0]"]
124["Segment<br>[1526, 1556, 0]"]
125["Segment<br>[1526, 1556, 0]"]
126["Segment<br>[1526, 1556, 0]"]
127["Segment<br>[1526, 1556, 0]"]
128["Segment<br>[1526, 1556, 0]"]
129["Segment<br>[1526, 1556, 0]"]
130["Segment<br>[1526, 1556, 0]"]
131["Segment<br>[1526, 1556, 0]"]
132["Segment<br>[1526, 1556, 0]"]
133["Segment<br>[1526, 1556, 0]"]
134["Segment<br>[1526, 1556, 0]"]
135["Segment<br>[1526, 1556, 0]"]
136["Segment<br>[1526, 1556, 0]"]
137["Segment<br>[1526, 1556, 0]"]
138["Segment<br>[1526, 1556, 0]"]
139["Segment<br>[1526, 1556, 0]"]
140["Segment<br>[1526, 1556, 0]"]
141["Segment<br>[1526, 1556, 0]"]
142["Segment<br>[1526, 1556, 0]"]
143["Segment<br>[1526, 1556, 0]"]
144["Segment<br>[1526, 1556, 0]"]
145["Segment<br>[1526, 1556, 0]"]
146["Segment<br>[1526, 1556, 0]"]
147["Segment<br>[1526, 1556, 0]"]
148["Segment<br>[1526, 1556, 0]"]
149["Segment<br>[1526, 1556, 0]"]
150["Segment<br>[1526, 1556, 0]"]
151["Segment<br>[1526, 1556, 0]"]
152["Segment<br>[1526, 1556, 0]"]
153["Segment<br>[1526, 1556, 0]"]
154["Segment<br>[1526, 1556, 0]"]
155["Segment<br>[1526, 1556, 0]"]
156["Segment<br>[1526, 1556, 0]"]
157["Segment<br>[1526, 1556, 0]"]
158["Segment<br>[1526, 1556, 0]"]
159["Segment<br>[1526, 1556, 0]"]
160["Segment<br>[1526, 1556, 0]"]
161["Segment<br>[1526, 1556, 0]"]
162["Segment<br>[1526, 1556, 0]"]
163["Segment<br>[1526, 1556, 0]"]
164["Segment<br>[1526, 1556, 0]"]
165["Segment<br>[1526, 1556, 0]"]
166["Segment<br>[1526, 1556, 0]"]
167["Segment<br>[1526, 1556, 0]"]
168["Segment<br>[1526, 1556, 0]"]
169["Segment<br>[1526, 1556, 0]"]
170["Segment<br>[1526, 1556, 0]"]
171["Segment<br>[1526, 1556, 0]"]
172["Segment<br>[1526, 1556, 0]"]
173["Segment<br>[1526, 1556, 0]"]
174["Segment<br>[1526, 1556, 0]"]
175["Segment<br>[1526, 1556, 0]"]
176["Segment<br>[1526, 1556, 0]"]
177["Segment<br>[1526, 1556, 0]"]
178["Segment<br>[1526, 1556, 0]"]
179["Segment<br>[1526, 1556, 0]"]
180["Segment<br>[1526, 1556, 0]"]
181["Segment<br>[1526, 1556, 0]"]
182["Segment<br>[1526, 1556, 0]"]
183["Segment<br>[1526, 1556, 0]"]
184["Segment<br>[1526, 1556, 0]"]
185["Segment<br>[1526, 1556, 0]"]
186["Segment<br>[1526, 1556, 0]"]
187["Segment<br>[1526, 1556, 0]"]
188["Segment<br>[1526, 1556, 0]"]
189["Segment<br>[1526, 1556, 0]"]
190["Segment<br>[1526, 1556, 0]"]
191["Segment<br>[1526, 1556, 0]"]
192["Segment<br>[1526, 1556, 0]"]
193["Segment<br>[1526, 1556, 0]"]
194["Segment<br>[1526, 1556, 0]"]
195["Segment<br>[1526, 1556, 0]"]
196["Segment<br>[1526, 1556, 0]"]
197["Segment<br>[1526, 1556, 0]"]
198["Segment<br>[1526, 1556, 0]"]
199["Segment<br>[1526, 1556, 0]"]
200["Segment<br>[1526, 1556, 0]"]
201["Segment<br>[1526, 1556, 0]"]
202["Segment<br>[1526, 1556, 0]"]
203["Segment<br>[1526, 1556, 0]"]
204["Segment<br>[1526, 1556, 0]"]
205["Segment<br>[1526, 1556, 0]"]
206["Segment<br>[1526, 1556, 0]"]
207["Segment<br>[1526, 1556, 0]"]
208["Segment<br>[1526, 1556, 0]"]
209["Segment<br>[1526, 1556, 0]"]
210["Segment<br>[1526, 1556, 0]"]
211["Segment<br>[1713, 1811, 0]"]
212["Segment<br>[1871, 1878, 0]"]
6["Path<br>[1629, 1666, 0]"]
9["Segment<br>[1315, 1356, 0]"]
10["Segment<br>[1315, 1356, 0]"]
11["Segment<br>[1315, 1356, 0]"]
12["Segment<br>[1315, 1356, 0]"]
13["Segment<br>[1315, 1356, 0]"]
14["Segment<br>[1315, 1356, 0]"]
15["Segment<br>[1315, 1356, 0]"]
16["Segment<br>[1315, 1356, 0]"]
17["Segment<br>[1315, 1356, 0]"]
18["Segment<br>[1315, 1356, 0]"]
19["Segment<br>[1315, 1356, 0]"]
20["Segment<br>[1315, 1356, 0]"]
21["Segment<br>[1315, 1356, 0]"]
22["Segment<br>[1315, 1356, 0]"]
23["Segment<br>[1315, 1356, 0]"]
24["Segment<br>[1315, 1356, 0]"]
25["Segment<br>[1315, 1356, 0]"]
26["Segment<br>[1315, 1356, 0]"]
27["Segment<br>[1315, 1356, 0]"]
28["Segment<br>[1315, 1356, 0]"]
29["Segment<br>[1315, 1356, 0]"]
30["Segment<br>[1315, 1356, 0]"]
31["Segment<br>[1315, 1356, 0]"]
32["Segment<br>[1315, 1356, 0]"]
33["Segment<br>[1315, 1356, 0]"]
34["Segment<br>[1315, 1356, 0]"]
35["Segment<br>[1315, 1356, 0]"]
36["Segment<br>[1315, 1356, 0]"]
37["Segment<br>[1315, 1356, 0]"]
38["Segment<br>[1315, 1356, 0]"]
39["Segment<br>[1315, 1356, 0]"]
40["Segment<br>[1315, 1356, 0]"]
41["Segment<br>[1315, 1356, 0]"]
42["Segment<br>[1315, 1356, 0]"]
43["Segment<br>[1315, 1356, 0]"]
44["Segment<br>[1315, 1356, 0]"]
45["Segment<br>[1315, 1356, 0]"]
46["Segment<br>[1315, 1356, 0]"]
47["Segment<br>[1315, 1356, 0]"]
48["Segment<br>[1315, 1356, 0]"]
49["Segment<br>[1315, 1356, 0]"]
50["Segment<br>[1315, 1356, 0]"]
51["Segment<br>[1315, 1356, 0]"]
52["Segment<br>[1315, 1356, 0]"]
53["Segment<br>[1315, 1356, 0]"]
54["Segment<br>[1315, 1356, 0]"]
55["Segment<br>[1315, 1356, 0]"]
56["Segment<br>[1315, 1356, 0]"]
57["Segment<br>[1315, 1356, 0]"]
58["Segment<br>[1315, 1356, 0]"]
59["Segment<br>[1315, 1356, 0]"]
60["Segment<br>[1315, 1356, 0]"]
61["Segment<br>[1315, 1356, 0]"]
62["Segment<br>[1315, 1356, 0]"]
63["Segment<br>[1315, 1356, 0]"]
64["Segment<br>[1315, 1356, 0]"]
65["Segment<br>[1315, 1356, 0]"]
66["Segment<br>[1315, 1356, 0]"]
67["Segment<br>[1315, 1356, 0]"]
68["Segment<br>[1315, 1356, 0]"]
69["Segment<br>[1315, 1356, 0]"]
70["Segment<br>[1315, 1356, 0]"]
71["Segment<br>[1315, 1356, 0]"]
72["Segment<br>[1315, 1356, 0]"]
73["Segment<br>[1315, 1356, 0]"]
74["Segment<br>[1315, 1356, 0]"]
75["Segment<br>[1315, 1356, 0]"]
76["Segment<br>[1315, 1356, 0]"]
77["Segment<br>[1315, 1356, 0]"]
78["Segment<br>[1315, 1356, 0]"]
79["Segment<br>[1315, 1356, 0]"]
80["Segment<br>[1315, 1356, 0]"]
81["Segment<br>[1315, 1356, 0]"]
82["Segment<br>[1315, 1356, 0]"]
83["Segment<br>[1315, 1356, 0]"]
84["Segment<br>[1315, 1356, 0]"]
85["Segment<br>[1315, 1356, 0]"]
86["Segment<br>[1315, 1356, 0]"]
87["Segment<br>[1315, 1356, 0]"]
88["Segment<br>[1315, 1356, 0]"]
89["Segment<br>[1315, 1356, 0]"]
90["Segment<br>[1315, 1356, 0]"]
91["Segment<br>[1315, 1356, 0]"]
92["Segment<br>[1315, 1356, 0]"]
93["Segment<br>[1315, 1356, 0]"]
94["Segment<br>[1315, 1356, 0]"]
95["Segment<br>[1315, 1356, 0]"]
96["Segment<br>[1315, 1356, 0]"]
97["Segment<br>[1315, 1356, 0]"]
98["Segment<br>[1315, 1356, 0]"]
99["Segment<br>[1315, 1356, 0]"]
100["Segment<br>[1315, 1356, 0]"]
101["Segment<br>[1315, 1356, 0]"]
102["Segment<br>[1315, 1356, 0]"]
103["Segment<br>[1315, 1356, 0]"]
104["Segment<br>[1315, 1356, 0]"]
105["Segment<br>[1315, 1356, 0]"]
106["Segment<br>[1315, 1356, 0]"]
107["Segment<br>[1315, 1356, 0]"]
108["Segment<br>[1315, 1356, 0]"]
109["Segment<br>[1315, 1356, 0]"]
110["Segment<br>[1542, 1575, 0]"]
111["Segment<br>[1542, 1575, 0]"]
112["Segment<br>[1542, 1575, 0]"]
113["Segment<br>[1542, 1575, 0]"]
114["Segment<br>[1542, 1575, 0]"]
115["Segment<br>[1542, 1575, 0]"]
116["Segment<br>[1542, 1575, 0]"]
117["Segment<br>[1542, 1575, 0]"]
118["Segment<br>[1542, 1575, 0]"]
119["Segment<br>[1542, 1575, 0]"]
120["Segment<br>[1542, 1575, 0]"]
121["Segment<br>[1542, 1575, 0]"]
122["Segment<br>[1542, 1575, 0]"]
123["Segment<br>[1542, 1575, 0]"]
124["Segment<br>[1542, 1575, 0]"]
125["Segment<br>[1542, 1575, 0]"]
126["Segment<br>[1542, 1575, 0]"]
127["Segment<br>[1542, 1575, 0]"]
128["Segment<br>[1542, 1575, 0]"]
129["Segment<br>[1542, 1575, 0]"]
130["Segment<br>[1542, 1575, 0]"]
131["Segment<br>[1542, 1575, 0]"]
132["Segment<br>[1542, 1575, 0]"]
133["Segment<br>[1542, 1575, 0]"]
134["Segment<br>[1542, 1575, 0]"]
135["Segment<br>[1542, 1575, 0]"]
136["Segment<br>[1542, 1575, 0]"]
137["Segment<br>[1542, 1575, 0]"]
138["Segment<br>[1542, 1575, 0]"]
139["Segment<br>[1542, 1575, 0]"]
140["Segment<br>[1542, 1575, 0]"]
141["Segment<br>[1542, 1575, 0]"]
142["Segment<br>[1542, 1575, 0]"]
143["Segment<br>[1542, 1575, 0]"]
144["Segment<br>[1542, 1575, 0]"]
145["Segment<br>[1542, 1575, 0]"]
146["Segment<br>[1542, 1575, 0]"]
147["Segment<br>[1542, 1575, 0]"]
148["Segment<br>[1542, 1575, 0]"]
149["Segment<br>[1542, 1575, 0]"]
150["Segment<br>[1542, 1575, 0]"]
151["Segment<br>[1542, 1575, 0]"]
152["Segment<br>[1542, 1575, 0]"]
153["Segment<br>[1542, 1575, 0]"]
154["Segment<br>[1542, 1575, 0]"]
155["Segment<br>[1542, 1575, 0]"]
156["Segment<br>[1542, 1575, 0]"]
157["Segment<br>[1542, 1575, 0]"]
158["Segment<br>[1542, 1575, 0]"]
159["Segment<br>[1542, 1575, 0]"]
160["Segment<br>[1542, 1575, 0]"]
161["Segment<br>[1542, 1575, 0]"]
162["Segment<br>[1542, 1575, 0]"]
163["Segment<br>[1542, 1575, 0]"]
164["Segment<br>[1542, 1575, 0]"]
165["Segment<br>[1542, 1575, 0]"]
166["Segment<br>[1542, 1575, 0]"]
167["Segment<br>[1542, 1575, 0]"]
168["Segment<br>[1542, 1575, 0]"]
169["Segment<br>[1542, 1575, 0]"]
170["Segment<br>[1542, 1575, 0]"]
171["Segment<br>[1542, 1575, 0]"]
172["Segment<br>[1542, 1575, 0]"]
173["Segment<br>[1542, 1575, 0]"]
174["Segment<br>[1542, 1575, 0]"]
175["Segment<br>[1542, 1575, 0]"]
176["Segment<br>[1542, 1575, 0]"]
177["Segment<br>[1542, 1575, 0]"]
178["Segment<br>[1542, 1575, 0]"]
179["Segment<br>[1542, 1575, 0]"]
180["Segment<br>[1542, 1575, 0]"]
181["Segment<br>[1542, 1575, 0]"]
182["Segment<br>[1542, 1575, 0]"]
183["Segment<br>[1542, 1575, 0]"]
184["Segment<br>[1542, 1575, 0]"]
185["Segment<br>[1542, 1575, 0]"]
186["Segment<br>[1542, 1575, 0]"]
187["Segment<br>[1542, 1575, 0]"]
188["Segment<br>[1542, 1575, 0]"]
189["Segment<br>[1542, 1575, 0]"]
190["Segment<br>[1542, 1575, 0]"]
191["Segment<br>[1542, 1575, 0]"]
192["Segment<br>[1542, 1575, 0]"]
193["Segment<br>[1542, 1575, 0]"]
194["Segment<br>[1542, 1575, 0]"]
195["Segment<br>[1542, 1575, 0]"]
196["Segment<br>[1542, 1575, 0]"]
197["Segment<br>[1542, 1575, 0]"]
198["Segment<br>[1542, 1575, 0]"]
199["Segment<br>[1542, 1575, 0]"]
200["Segment<br>[1542, 1575, 0]"]
201["Segment<br>[1542, 1575, 0]"]
202["Segment<br>[1542, 1575, 0]"]
203["Segment<br>[1542, 1575, 0]"]
204["Segment<br>[1542, 1575, 0]"]
205["Segment<br>[1542, 1575, 0]"]
206["Segment<br>[1542, 1575, 0]"]
207["Segment<br>[1542, 1575, 0]"]
208["Segment<br>[1542, 1575, 0]"]
209["Segment<br>[1542, 1575, 0]"]
210["Segment<br>[1542, 1575, 0]"]
211["Segment<br>[1732, 1830, 0]"]
212["Segment<br>[1890, 1897, 0]"]
219[Solid2d]
end
subgraph path7 [Path]
7["Path<br>[2359, 2438, 0]"]
213["Segment<br>[2444, 2471, 0]"]
214["Segment<br>[2477, 2505, 0]"]
215["Segment<br>[2511, 2539, 0]"]
216["Segment<br>[2545, 2668, 0]"]
217["Segment<br>[2674, 2786, 0]"]
218["Segment<br>[2792, 2799, 0]"]
7["Path<br>[2378, 2457, 0]"]
213["Segment<br>[2463, 2490, 0]"]
214["Segment<br>[2496, 2524, 0]"]
215["Segment<br>[2530, 2558, 0]"]
216["Segment<br>[2564, 2687, 0]"]
217["Segment<br>[2693, 2805, 0]"]
218["Segment<br>[2811, 2818, 0]"]
221[Solid2d]
end
1["Plane<br>[168, 185, 0]"]
2["Plane<br>[1068, 1085, 0]"]
3["Plane<br>[1587, 1604, 0]"]
4["StartSketchOnFace<br>[2322, 2353, 0]"]
222["Sweep Extrusion<br>[1147, 1175, 0]"]
223["Sweep Extrusion<br>[1884, 1912, 0]"]
224["Sweep Extrusion<br>[2805, 2834, 0]"]
2["Plane<br>[1073, 1090, 0]"]
3["Plane<br>[1606, 1623, 0]"]
4["StartSketchOnFace<br>[2341, 2372, 0]"]
222["Sweep Extrusion<br>[1152, 1180, 0]"]
223["Sweep Extrusion<br>[1903, 1931, 0]"]
224["Sweep Extrusion<br>[2824, 2853, 0]"]
225[Wall]
226[Wall]
227[Wall]

View File

@ -857,7 +857,8 @@ description: Result of parsing import_async.kcl
"name": "i",
"start": 0,
"type": "Identifier"
}
},
"labeled": false
}
],
"start": 0,
@ -1093,7 +1094,8 @@ description: Result of parsing import_async.kcl
"name": "r",
"start": 0,
"type": "Identifier"
}
},
"labeled": false
}
],
"start": 0,
@ -1324,7 +1326,8 @@ description: Result of parsing import_async.kcl
"name": "a",
"start": 0,
"type": "Identifier"
}
},
"labeled": false
}
],
"start": 0,
@ -1526,7 +1529,8 @@ description: Result of parsing import_async.kcl
"name": "i",
"start": 0,
"type": "Identifier"
}
},
"labeled": false
}
],
"start": 0,
@ -1748,7 +1752,8 @@ description: Result of parsing import_async.kcl
"name": "i",
"start": 0,
"type": "Identifier"
}
},
"labeled": false
}
],
"start": 0,
@ -2308,7 +2313,7 @@ description: Result of parsing import_async.kcl
"name": {
"commentStart": 0,
"end": 0,
"name": "sg",
"name": "accum",
"start": 0,
"type": "Identifier"
},
@ -2358,14 +2363,15 @@ description: Result of parsing import_async.kcl
"name": "i",
"start": 0,
"type": "Identifier"
}
},
"labeled": false
},
{
"type": "Parameter",
"identifier": {
"commentStart": 0,
"end": 0,
"name": "sg",
"name": "accum",
"start": 0,
"type": "Identifier"
}
@ -2924,7 +2930,7 @@ description: Result of parsing import_async.kcl
"name": {
"commentStart": 0,
"end": 0,
"name": "sg",
"name": "accum",
"start": 0,
"type": "Identifier"
},
@ -2956,14 +2962,15 @@ description: Result of parsing import_async.kcl
"name": "i",
"start": 0,
"type": "Identifier"
}
},
"labeled": false
},
{
"type": "Parameter",
"identifier": {
"commentStart": 0,
"end": 0,
"name": "sg",
"name": "accum",
"start": 0,
"type": "Identifier"
}

View File

@ -22,26 +22,26 @@ gearHeight = 3
// Interpolate points along the involute curve
cmo = 101
rs = map([0..cmo], f = fn(i) {
rs = map([0..cmo], f = fn(@i) {
return baseDiameter / 2 + i / cmo * (tipDiameter - baseDiameter) / 2
})
// Calculate operating pressure angle
angles = map(rs, f = fn(r) {
angles = map(rs, f = fn(@r) {
return units::toDegrees( acos(baseDiameter / 2 / r))
})
// Calculate the involute function
invas = map(angles, f = fn(a) {
invas = map(angles, f = fn(@a) {
return tan(units::toRadians(a)) - units::toRadians(a)
})
// Map the involute curve
xs = map([0..cmo], f = fn(i) {
xs = map([0..cmo], f = fn(@i) {
return rs[i] * cos(invas[i]: number(rad))
})
ys = map([0..cmo], f = fn(i) {
ys = map([0..cmo], f = fn(@i) {
return rs[i] * sin(invas[i]: number(rad))
})
@ -53,15 +53,15 @@ body = startSketchOn(XY)
toothAngle = 360 / nTeeth / 1.5
// Plot the involute curve
fn leftInvolute(i, sg) {
fn leftInvolute(@i, accum) {
j = 100 - i // iterate backwards
return line(sg, endAbsolute = [xs[j], ys[j]])
return line(accum, endAbsolute = [xs[j], ys[j]])
}
fn rightInvolute(i, sg) {
fn rightInvolute(@i, accum) {
x = rs[i] * cos(-toothAngle + units::toDegrees(atan(ys[i] / xs[i])))
y = -rs[i] * sin(-toothAngle + units::toDegrees(atan(ys[i] / xs[i])))
return line(sg, endAbsolute = [x, y])
return line(accum, endAbsolute = [x, y])
}
// Draw gear teeth

File diff suppressed because it is too large Load Diff

View File

@ -27,7 +27,7 @@ gearHeight = 3
cmo = 101
rs = map(
[0..cmo],
f = fn(i) {
f = fn(@i) {
return baseDiameter / 2 + i / cmo * (tipDiameter - baseDiameter) / 2
},
)
@ -35,7 +35,7 @@ rs = map(
// Calculate operating pressure angle
angles = map(
rs,
f = fn(r) {
f = fn(@r) {
return units::toDegrees(acos(baseDiameter / 2 / r))
},
)
@ -43,7 +43,7 @@ angles = map(
// Calculate the involute function
invas = map(
angles,
f = fn(a) {
f = fn(@a) {
return tan(units::toRadians(a)) - units::toRadians(a)
},
)
@ -51,14 +51,14 @@ invas = map(
// Map the involute curve
xs = map(
[0..cmo],
f = fn(i) {
f = fn(@i) {
return rs[i] * cos(invas[i]: number(rad))
},
)
ys = map(
[0..cmo],
f = fn(i) {
f = fn(@i) {
return rs[i] * sin(invas[i]: number(rad))
},
)
@ -71,15 +71,15 @@ body = startSketchOn(XY)
toothAngle = 360 / nTeeth / 1.5
// Plot the involute curve
fn leftInvolute(i, sg) {
fn leftInvolute(@i, accum) {
j = 100 - i // iterate backwards
return line(sg, endAbsolute = [xs[j], ys[j]])
return line(accum, endAbsolute = [xs[j], ys[j]])
}
fn rightInvolute(i, sg) {
fn rightInvolute(@i, accum) {
x = rs[i] * cos(-toothAngle + units::toDegrees(atan(ys[i] / xs[i])))
y = -rs[i] * sin(-toothAngle + units::toDegrees(atan(ys[i] / xs[i])))
return line(sg, endAbsolute = [x, y])
return line(accum, endAbsolute = [x, y])
}
// Draw gear teeth

View File

@ -120,17 +120,17 @@ flowchart LR
94["Sweep Extrusion<br>[771, 821, 0]"]
95["Sweep Extrusion<br>[771, 821, 0]"]
96["Sweep Extrusion<br>[771, 821, 0]"]
97["CompositeSolid Intersect<br>[2018, 2048, 0]"]
98["CompositeSolid Intersect<br>[2018, 2048, 0]"]
99["CompositeSolid Intersect<br>[2018, 2048, 0]"]
100["CompositeSolid Intersect<br>[2018, 2048, 0]"]
101["CompositeSolid Intersect<br>[2018, 2048, 0]"]
102["CompositeSolid Intersect<br>[2018, 2048, 0]"]
103["CompositeSolid Intersect<br>[2018, 2048, 0]"]
104["CompositeSolid Intersect<br>[2018, 2048, 0]"]
105["CompositeSolid Intersect<br>[2018, 2048, 0]"]
106["CompositeSolid Intersect<br>[2018, 2048, 0]"]
107["CompositeSolid Intersect<br>[2018, 2048, 0]"]
97["CompositeSolid Intersect<br>[2007, 2035, 0]"]
98["CompositeSolid Intersect<br>[2007, 2035, 0]"]
99["CompositeSolid Intersect<br>[2007, 2035, 0]"]
100["CompositeSolid Intersect<br>[2007, 2035, 0]"]
101["CompositeSolid Intersect<br>[2007, 2035, 0]"]
102["CompositeSolid Intersect<br>[2007, 2035, 0]"]
103["CompositeSolid Intersect<br>[2007, 2035, 0]"]
104["CompositeSolid Intersect<br>[2007, 2035, 0]"]
105["CompositeSolid Intersect<br>[2007, 2035, 0]"]
106["CompositeSolid Intersect<br>[2007, 2035, 0]"]
107["CompositeSolid Intersect<br>[2007, 2035, 0]"]
108[Wall]
109[Wall]
110[Wall]

View File

@ -2249,7 +2249,8 @@ description: Result of parsing dodecahedron.kcl
"name": "rotation",
"start": 0,
"type": "Identifier"
}
},
"labeled": false
}
],
"start": 0,
@ -2372,7 +2373,7 @@ description: Result of parsing dodecahedron.kcl
"name": {
"commentStart": 0,
"end": 0,
"name": "accumulator",
"name": "accum",
"start": 0,
"type": "Identifier"
},
@ -2420,14 +2421,15 @@ description: Result of parsing dodecahedron.kcl
"name": "item",
"start": 0,
"type": "Identifier"
}
},
"labeled": false
},
{
"type": "Parameter",
"identifier": {
"commentStart": 0,
"end": 0,
"name": "accumulator",
"name": "accum",
"start": 0,
"type": "Identifier"
}
@ -2593,7 +2595,7 @@ description: Result of parsing dodecahedron.kcl
"name": {
"commentStart": 0,
"end": 0,
"name": "current",
"name": "accum",
"start": 0,
"type": "Identifier"
},
@ -2631,14 +2633,15 @@ description: Result of parsing dodecahedron.kcl
"name": "previous",
"start": 0,
"type": "Identifier"
}
},
"labeled": false
},
{
"type": "Parameter",
"identifier": {
"commentStart": 0,
"end": 0,
"name": "current",
"name": "accum",
"start": 0,
"type": "Identifier"
}

File diff suppressed because it is too large Load Diff

View File

@ -1,234 +1,234 @@
```mermaid
flowchart LR
subgraph path4 [Path]
4["Path<br>[1431, 1481, 0]"]
7["Segment<br>[1431, 1481, 0]"]
4["Path<br>[1436, 1486, 0]"]
7["Segment<br>[1436, 1486, 0]"]
219[Solid2d]
end
subgraph path5 [Path]
5["Path<br>[1950, 1987, 0]"]
8["Segment<br>[1646, 1684, 0]"]
9["Segment<br>[1646, 1684, 0]"]
10["Segment<br>[1646, 1684, 0]"]
11["Segment<br>[1646, 1684, 0]"]
12["Segment<br>[1646, 1684, 0]"]
13["Segment<br>[1646, 1684, 0]"]
14["Segment<br>[1646, 1684, 0]"]
15["Segment<br>[1646, 1684, 0]"]
16["Segment<br>[1646, 1684, 0]"]
17["Segment<br>[1646, 1684, 0]"]
18["Segment<br>[1646, 1684, 0]"]
19["Segment<br>[1646, 1684, 0]"]
20["Segment<br>[1646, 1684, 0]"]
21["Segment<br>[1646, 1684, 0]"]
22["Segment<br>[1646, 1684, 0]"]
23["Segment<br>[1646, 1684, 0]"]
24["Segment<br>[1646, 1684, 0]"]
25["Segment<br>[1646, 1684, 0]"]
26["Segment<br>[1646, 1684, 0]"]
27["Segment<br>[1646, 1684, 0]"]
28["Segment<br>[1646, 1684, 0]"]
29["Segment<br>[1646, 1684, 0]"]
30["Segment<br>[1646, 1684, 0]"]
31["Segment<br>[1646, 1684, 0]"]
32["Segment<br>[1646, 1684, 0]"]
33["Segment<br>[1646, 1684, 0]"]
34["Segment<br>[1646, 1684, 0]"]
35["Segment<br>[1646, 1684, 0]"]
36["Segment<br>[1646, 1684, 0]"]
37["Segment<br>[1646, 1684, 0]"]
38["Segment<br>[1646, 1684, 0]"]
39["Segment<br>[1646, 1684, 0]"]
40["Segment<br>[1646, 1684, 0]"]
41["Segment<br>[1646, 1684, 0]"]
42["Segment<br>[1646, 1684, 0]"]
43["Segment<br>[1646, 1684, 0]"]
44["Segment<br>[1646, 1684, 0]"]
45["Segment<br>[1646, 1684, 0]"]
46["Segment<br>[1646, 1684, 0]"]
47["Segment<br>[1646, 1684, 0]"]
48["Segment<br>[1646, 1684, 0]"]
49["Segment<br>[1646, 1684, 0]"]
50["Segment<br>[1646, 1684, 0]"]
51["Segment<br>[1646, 1684, 0]"]
52["Segment<br>[1646, 1684, 0]"]
53["Segment<br>[1646, 1684, 0]"]
54["Segment<br>[1646, 1684, 0]"]
55["Segment<br>[1646, 1684, 0]"]
56["Segment<br>[1646, 1684, 0]"]
57["Segment<br>[1646, 1684, 0]"]
58["Segment<br>[1646, 1684, 0]"]
59["Segment<br>[1646, 1684, 0]"]
60["Segment<br>[1646, 1684, 0]"]
61["Segment<br>[1646, 1684, 0]"]
62["Segment<br>[1646, 1684, 0]"]
63["Segment<br>[1646, 1684, 0]"]
64["Segment<br>[1646, 1684, 0]"]
65["Segment<br>[1646, 1684, 0]"]
66["Segment<br>[1646, 1684, 0]"]
67["Segment<br>[1646, 1684, 0]"]
68["Segment<br>[1646, 1684, 0]"]
69["Segment<br>[1646, 1684, 0]"]
70["Segment<br>[1646, 1684, 0]"]
71["Segment<br>[1646, 1684, 0]"]
72["Segment<br>[1646, 1684, 0]"]
73["Segment<br>[1646, 1684, 0]"]
74["Segment<br>[1646, 1684, 0]"]
75["Segment<br>[1646, 1684, 0]"]
76["Segment<br>[1646, 1684, 0]"]
77["Segment<br>[1646, 1684, 0]"]
78["Segment<br>[1646, 1684, 0]"]
79["Segment<br>[1646, 1684, 0]"]
80["Segment<br>[1646, 1684, 0]"]
81["Segment<br>[1646, 1684, 0]"]
82["Segment<br>[1646, 1684, 0]"]
83["Segment<br>[1646, 1684, 0]"]
84["Segment<br>[1646, 1684, 0]"]
85["Segment<br>[1646, 1684, 0]"]
86["Segment<br>[1646, 1684, 0]"]
87["Segment<br>[1646, 1684, 0]"]
88["Segment<br>[1646, 1684, 0]"]
89["Segment<br>[1646, 1684, 0]"]
90["Segment<br>[1646, 1684, 0]"]
91["Segment<br>[1646, 1684, 0]"]
92["Segment<br>[1646, 1684, 0]"]
93["Segment<br>[1646, 1684, 0]"]
94["Segment<br>[1646, 1684, 0]"]
95["Segment<br>[1646, 1684, 0]"]
96["Segment<br>[1646, 1684, 0]"]
97["Segment<br>[1646, 1684, 0]"]
98["Segment<br>[1646, 1684, 0]"]
99["Segment<br>[1646, 1684, 0]"]
100["Segment<br>[1646, 1684, 0]"]
101["Segment<br>[1646, 1684, 0]"]
102["Segment<br>[1646, 1684, 0]"]
103["Segment<br>[1646, 1684, 0]"]
104["Segment<br>[1646, 1684, 0]"]
105["Segment<br>[1646, 1684, 0]"]
106["Segment<br>[1646, 1684, 0]"]
107["Segment<br>[1646, 1684, 0]"]
108["Segment<br>[1646, 1684, 0]"]
109["Segment<br>[1866, 1896, 0]"]
110["Segment<br>[1866, 1896, 0]"]
111["Segment<br>[1866, 1896, 0]"]
112["Segment<br>[1866, 1896, 0]"]
113["Segment<br>[1866, 1896, 0]"]
114["Segment<br>[1866, 1896, 0]"]
115["Segment<br>[1866, 1896, 0]"]
116["Segment<br>[1866, 1896, 0]"]
117["Segment<br>[1866, 1896, 0]"]
118["Segment<br>[1866, 1896, 0]"]
119["Segment<br>[1866, 1896, 0]"]
120["Segment<br>[1866, 1896, 0]"]
121["Segment<br>[1866, 1896, 0]"]
122["Segment<br>[1866, 1896, 0]"]
123["Segment<br>[1866, 1896, 0]"]
124["Segment<br>[1866, 1896, 0]"]
125["Segment<br>[1866, 1896, 0]"]
126["Segment<br>[1866, 1896, 0]"]
127["Segment<br>[1866, 1896, 0]"]
128["Segment<br>[1866, 1896, 0]"]
129["Segment<br>[1866, 1896, 0]"]
130["Segment<br>[1866, 1896, 0]"]
131["Segment<br>[1866, 1896, 0]"]
132["Segment<br>[1866, 1896, 0]"]
133["Segment<br>[1866, 1896, 0]"]
134["Segment<br>[1866, 1896, 0]"]
135["Segment<br>[1866, 1896, 0]"]
136["Segment<br>[1866, 1896, 0]"]
137["Segment<br>[1866, 1896, 0]"]
138["Segment<br>[1866, 1896, 0]"]
139["Segment<br>[1866, 1896, 0]"]
140["Segment<br>[1866, 1896, 0]"]
141["Segment<br>[1866, 1896, 0]"]
142["Segment<br>[1866, 1896, 0]"]
143["Segment<br>[1866, 1896, 0]"]
144["Segment<br>[1866, 1896, 0]"]
145["Segment<br>[1866, 1896, 0]"]
146["Segment<br>[1866, 1896, 0]"]
147["Segment<br>[1866, 1896, 0]"]
148["Segment<br>[1866, 1896, 0]"]
149["Segment<br>[1866, 1896, 0]"]
150["Segment<br>[1866, 1896, 0]"]
151["Segment<br>[1866, 1896, 0]"]
152["Segment<br>[1866, 1896, 0]"]
153["Segment<br>[1866, 1896, 0]"]
154["Segment<br>[1866, 1896, 0]"]
155["Segment<br>[1866, 1896, 0]"]
156["Segment<br>[1866, 1896, 0]"]
157["Segment<br>[1866, 1896, 0]"]
158["Segment<br>[1866, 1896, 0]"]
159["Segment<br>[1866, 1896, 0]"]
160["Segment<br>[1866, 1896, 0]"]
161["Segment<br>[1866, 1896, 0]"]
162["Segment<br>[1866, 1896, 0]"]
163["Segment<br>[1866, 1896, 0]"]
164["Segment<br>[1866, 1896, 0]"]
165["Segment<br>[1866, 1896, 0]"]
166["Segment<br>[1866, 1896, 0]"]
167["Segment<br>[1866, 1896, 0]"]
168["Segment<br>[1866, 1896, 0]"]
169["Segment<br>[1866, 1896, 0]"]
170["Segment<br>[1866, 1896, 0]"]
171["Segment<br>[1866, 1896, 0]"]
172["Segment<br>[1866, 1896, 0]"]
173["Segment<br>[1866, 1896, 0]"]
174["Segment<br>[1866, 1896, 0]"]
175["Segment<br>[1866, 1896, 0]"]
176["Segment<br>[1866, 1896, 0]"]
177["Segment<br>[1866, 1896, 0]"]
178["Segment<br>[1866, 1896, 0]"]
179["Segment<br>[1866, 1896, 0]"]
180["Segment<br>[1866, 1896, 0]"]
181["Segment<br>[1866, 1896, 0]"]
182["Segment<br>[1866, 1896, 0]"]
183["Segment<br>[1866, 1896, 0]"]
184["Segment<br>[1866, 1896, 0]"]
185["Segment<br>[1866, 1896, 0]"]
186["Segment<br>[1866, 1896, 0]"]
187["Segment<br>[1866, 1896, 0]"]
188["Segment<br>[1866, 1896, 0]"]
189["Segment<br>[1866, 1896, 0]"]
190["Segment<br>[1866, 1896, 0]"]
191["Segment<br>[1866, 1896, 0]"]
192["Segment<br>[1866, 1896, 0]"]
193["Segment<br>[1866, 1896, 0]"]
194["Segment<br>[1866, 1896, 0]"]
195["Segment<br>[1866, 1896, 0]"]
196["Segment<br>[1866, 1896, 0]"]
197["Segment<br>[1866, 1896, 0]"]
198["Segment<br>[1866, 1896, 0]"]
199["Segment<br>[1866, 1896, 0]"]
200["Segment<br>[1866, 1896, 0]"]
201["Segment<br>[1866, 1896, 0]"]
202["Segment<br>[1866, 1896, 0]"]
203["Segment<br>[1866, 1896, 0]"]
204["Segment<br>[1866, 1896, 0]"]
205["Segment<br>[1866, 1896, 0]"]
206["Segment<br>[1866, 1896, 0]"]
207["Segment<br>[1866, 1896, 0]"]
208["Segment<br>[1866, 1896, 0]"]
209["Segment<br>[1866, 1896, 0]"]
210["Segment<br>[2053, 2122, 0]"]
211["Segment<br>[2182, 2189, 0]"]
5["Path<br>[1969, 2006, 0]"]
8["Segment<br>[1655, 1696, 0]"]
9["Segment<br>[1655, 1696, 0]"]
10["Segment<br>[1655, 1696, 0]"]
11["Segment<br>[1655, 1696, 0]"]
12["Segment<br>[1655, 1696, 0]"]
13["Segment<br>[1655, 1696, 0]"]
14["Segment<br>[1655, 1696, 0]"]
15["Segment<br>[1655, 1696, 0]"]
16["Segment<br>[1655, 1696, 0]"]
17["Segment<br>[1655, 1696, 0]"]
18["Segment<br>[1655, 1696, 0]"]
19["Segment<br>[1655, 1696, 0]"]
20["Segment<br>[1655, 1696, 0]"]
21["Segment<br>[1655, 1696, 0]"]
22["Segment<br>[1655, 1696, 0]"]
23["Segment<br>[1655, 1696, 0]"]
24["Segment<br>[1655, 1696, 0]"]
25["Segment<br>[1655, 1696, 0]"]
26["Segment<br>[1655, 1696, 0]"]
27["Segment<br>[1655, 1696, 0]"]
28["Segment<br>[1655, 1696, 0]"]
29["Segment<br>[1655, 1696, 0]"]
30["Segment<br>[1655, 1696, 0]"]
31["Segment<br>[1655, 1696, 0]"]
32["Segment<br>[1655, 1696, 0]"]
33["Segment<br>[1655, 1696, 0]"]
34["Segment<br>[1655, 1696, 0]"]
35["Segment<br>[1655, 1696, 0]"]
36["Segment<br>[1655, 1696, 0]"]
37["Segment<br>[1655, 1696, 0]"]
38["Segment<br>[1655, 1696, 0]"]
39["Segment<br>[1655, 1696, 0]"]
40["Segment<br>[1655, 1696, 0]"]
41["Segment<br>[1655, 1696, 0]"]
42["Segment<br>[1655, 1696, 0]"]
43["Segment<br>[1655, 1696, 0]"]
44["Segment<br>[1655, 1696, 0]"]
45["Segment<br>[1655, 1696, 0]"]
46["Segment<br>[1655, 1696, 0]"]
47["Segment<br>[1655, 1696, 0]"]
48["Segment<br>[1655, 1696, 0]"]
49["Segment<br>[1655, 1696, 0]"]
50["Segment<br>[1655, 1696, 0]"]
51["Segment<br>[1655, 1696, 0]"]
52["Segment<br>[1655, 1696, 0]"]
53["Segment<br>[1655, 1696, 0]"]
54["Segment<br>[1655, 1696, 0]"]
55["Segment<br>[1655, 1696, 0]"]
56["Segment<br>[1655, 1696, 0]"]
57["Segment<br>[1655, 1696, 0]"]
58["Segment<br>[1655, 1696, 0]"]
59["Segment<br>[1655, 1696, 0]"]
60["Segment<br>[1655, 1696, 0]"]
61["Segment<br>[1655, 1696, 0]"]
62["Segment<br>[1655, 1696, 0]"]
63["Segment<br>[1655, 1696, 0]"]
64["Segment<br>[1655, 1696, 0]"]
65["Segment<br>[1655, 1696, 0]"]
66["Segment<br>[1655, 1696, 0]"]
67["Segment<br>[1655, 1696, 0]"]
68["Segment<br>[1655, 1696, 0]"]
69["Segment<br>[1655, 1696, 0]"]
70["Segment<br>[1655, 1696, 0]"]
71["Segment<br>[1655, 1696, 0]"]
72["Segment<br>[1655, 1696, 0]"]
73["Segment<br>[1655, 1696, 0]"]
74["Segment<br>[1655, 1696, 0]"]
75["Segment<br>[1655, 1696, 0]"]
76["Segment<br>[1655, 1696, 0]"]
77["Segment<br>[1655, 1696, 0]"]
78["Segment<br>[1655, 1696, 0]"]
79["Segment<br>[1655, 1696, 0]"]
80["Segment<br>[1655, 1696, 0]"]
81["Segment<br>[1655, 1696, 0]"]
82["Segment<br>[1655, 1696, 0]"]
83["Segment<br>[1655, 1696, 0]"]
84["Segment<br>[1655, 1696, 0]"]
85["Segment<br>[1655, 1696, 0]"]
86["Segment<br>[1655, 1696, 0]"]
87["Segment<br>[1655, 1696, 0]"]
88["Segment<br>[1655, 1696, 0]"]
89["Segment<br>[1655, 1696, 0]"]
90["Segment<br>[1655, 1696, 0]"]
91["Segment<br>[1655, 1696, 0]"]
92["Segment<br>[1655, 1696, 0]"]
93["Segment<br>[1655, 1696, 0]"]
94["Segment<br>[1655, 1696, 0]"]
95["Segment<br>[1655, 1696, 0]"]
96["Segment<br>[1655, 1696, 0]"]
97["Segment<br>[1655, 1696, 0]"]
98["Segment<br>[1655, 1696, 0]"]
99["Segment<br>[1655, 1696, 0]"]
100["Segment<br>[1655, 1696, 0]"]
101["Segment<br>[1655, 1696, 0]"]
102["Segment<br>[1655, 1696, 0]"]
103["Segment<br>[1655, 1696, 0]"]
104["Segment<br>[1655, 1696, 0]"]
105["Segment<br>[1655, 1696, 0]"]
106["Segment<br>[1655, 1696, 0]"]
107["Segment<br>[1655, 1696, 0]"]
108["Segment<br>[1655, 1696, 0]"]
109["Segment<br>[1882, 1915, 0]"]
110["Segment<br>[1882, 1915, 0]"]
111["Segment<br>[1882, 1915, 0]"]
112["Segment<br>[1882, 1915, 0]"]
113["Segment<br>[1882, 1915, 0]"]
114["Segment<br>[1882, 1915, 0]"]
115["Segment<br>[1882, 1915, 0]"]
116["Segment<br>[1882, 1915, 0]"]
117["Segment<br>[1882, 1915, 0]"]
118["Segment<br>[1882, 1915, 0]"]
119["Segment<br>[1882, 1915, 0]"]
120["Segment<br>[1882, 1915, 0]"]
121["Segment<br>[1882, 1915, 0]"]
122["Segment<br>[1882, 1915, 0]"]
123["Segment<br>[1882, 1915, 0]"]
124["Segment<br>[1882, 1915, 0]"]
125["Segment<br>[1882, 1915, 0]"]
126["Segment<br>[1882, 1915, 0]"]
127["Segment<br>[1882, 1915, 0]"]
128["Segment<br>[1882, 1915, 0]"]
129["Segment<br>[1882, 1915, 0]"]
130["Segment<br>[1882, 1915, 0]"]
131["Segment<br>[1882, 1915, 0]"]
132["Segment<br>[1882, 1915, 0]"]
133["Segment<br>[1882, 1915, 0]"]
134["Segment<br>[1882, 1915, 0]"]
135["Segment<br>[1882, 1915, 0]"]
136["Segment<br>[1882, 1915, 0]"]
137["Segment<br>[1882, 1915, 0]"]
138["Segment<br>[1882, 1915, 0]"]
139["Segment<br>[1882, 1915, 0]"]
140["Segment<br>[1882, 1915, 0]"]
141["Segment<br>[1882, 1915, 0]"]
142["Segment<br>[1882, 1915, 0]"]
143["Segment<br>[1882, 1915, 0]"]
144["Segment<br>[1882, 1915, 0]"]
145["Segment<br>[1882, 1915, 0]"]
146["Segment<br>[1882, 1915, 0]"]
147["Segment<br>[1882, 1915, 0]"]
148["Segment<br>[1882, 1915, 0]"]
149["Segment<br>[1882, 1915, 0]"]
150["Segment<br>[1882, 1915, 0]"]
151["Segment<br>[1882, 1915, 0]"]
152["Segment<br>[1882, 1915, 0]"]
153["Segment<br>[1882, 1915, 0]"]
154["Segment<br>[1882, 1915, 0]"]
155["Segment<br>[1882, 1915, 0]"]
156["Segment<br>[1882, 1915, 0]"]
157["Segment<br>[1882, 1915, 0]"]
158["Segment<br>[1882, 1915, 0]"]
159["Segment<br>[1882, 1915, 0]"]
160["Segment<br>[1882, 1915, 0]"]
161["Segment<br>[1882, 1915, 0]"]
162["Segment<br>[1882, 1915, 0]"]
163["Segment<br>[1882, 1915, 0]"]
164["Segment<br>[1882, 1915, 0]"]
165["Segment<br>[1882, 1915, 0]"]
166["Segment<br>[1882, 1915, 0]"]
167["Segment<br>[1882, 1915, 0]"]
168["Segment<br>[1882, 1915, 0]"]
169["Segment<br>[1882, 1915, 0]"]
170["Segment<br>[1882, 1915, 0]"]
171["Segment<br>[1882, 1915, 0]"]
172["Segment<br>[1882, 1915, 0]"]
173["Segment<br>[1882, 1915, 0]"]
174["Segment<br>[1882, 1915, 0]"]
175["Segment<br>[1882, 1915, 0]"]
176["Segment<br>[1882, 1915, 0]"]
177["Segment<br>[1882, 1915, 0]"]
178["Segment<br>[1882, 1915, 0]"]
179["Segment<br>[1882, 1915, 0]"]
180["Segment<br>[1882, 1915, 0]"]
181["Segment<br>[1882, 1915, 0]"]
182["Segment<br>[1882, 1915, 0]"]
183["Segment<br>[1882, 1915, 0]"]
184["Segment<br>[1882, 1915, 0]"]
185["Segment<br>[1882, 1915, 0]"]
186["Segment<br>[1882, 1915, 0]"]
187["Segment<br>[1882, 1915, 0]"]
188["Segment<br>[1882, 1915, 0]"]
189["Segment<br>[1882, 1915, 0]"]
190["Segment<br>[1882, 1915, 0]"]
191["Segment<br>[1882, 1915, 0]"]
192["Segment<br>[1882, 1915, 0]"]
193["Segment<br>[1882, 1915, 0]"]
194["Segment<br>[1882, 1915, 0]"]
195["Segment<br>[1882, 1915, 0]"]
196["Segment<br>[1882, 1915, 0]"]
197["Segment<br>[1882, 1915, 0]"]
198["Segment<br>[1882, 1915, 0]"]
199["Segment<br>[1882, 1915, 0]"]
200["Segment<br>[1882, 1915, 0]"]
201["Segment<br>[1882, 1915, 0]"]
202["Segment<br>[1882, 1915, 0]"]
203["Segment<br>[1882, 1915, 0]"]
204["Segment<br>[1882, 1915, 0]"]
205["Segment<br>[1882, 1915, 0]"]
206["Segment<br>[1882, 1915, 0]"]
207["Segment<br>[1882, 1915, 0]"]
208["Segment<br>[1882, 1915, 0]"]
209["Segment<br>[1882, 1915, 0]"]
210["Segment<br>[2072, 2141, 0]"]
211["Segment<br>[2201, 2208, 0]"]
218[Solid2d]
end
subgraph path6 [Path]
6["Path<br>[2670, 2770, 0]"]
212["Segment<br>[2776, 2803, 0]"]
213["Segment<br>[2809, 2837, 0]"]
214["Segment<br>[2843, 2871, 0]"]
215["Segment<br>[2877, 2971, 0]"]
216["Segment<br>[2977, 3060, 0]"]
217["Segment<br>[3066, 3073, 0]"]
6["Path<br>[2689, 2789, 0]"]
212["Segment<br>[2795, 2822, 0]"]
213["Segment<br>[2828, 2856, 0]"]
214["Segment<br>[2862, 2890, 0]"]
215["Segment<br>[2896, 2990, 0]"]
216["Segment<br>[2996, 3079, 0]"]
217["Segment<br>[3085, 3092, 0]"]
220[Solid2d]
end
1["Plane<br>[1408, 1425, 0]"]
2["Plane<br>[1927, 1944, 0]"]
3["StartSketchOnFace<br>[2633, 2664, 0]"]
221["Sweep Extrusion<br>[1487, 1515, 0]"]
222["Sweep Extrusion<br>[2195, 2223, 0]"]
223["Sweep Extrusion<br>[3079, 3108, 0]"]
1["Plane<br>[1413, 1430, 0]"]
2["Plane<br>[1946, 1963, 0]"]
3["StartSketchOnFace<br>[2652, 2683, 0]"]
221["Sweep Extrusion<br>[1492, 1520, 0]"]
222["Sweep Extrusion<br>[2214, 2242, 0]"]
223["Sweep Extrusion<br>[3098, 3127, 0]"]
224[Wall]
225[Wall]
226[Wall]

View File

@ -698,7 +698,8 @@ description: Result of parsing gear.kcl
"name": "i",
"start": 0,
"type": "Identifier"
}
},
"labeled": false
}
],
"start": 0,
@ -934,7 +935,8 @@ description: Result of parsing gear.kcl
"name": "r",
"start": 0,
"type": "Identifier"
}
},
"labeled": false
}
],
"start": 0,
@ -1135,7 +1137,8 @@ description: Result of parsing gear.kcl
"name": "a",
"start": 0,
"type": "Identifier"
}
},
"labeled": false
}
],
"start": 0,
@ -1337,7 +1340,8 @@ description: Result of parsing gear.kcl
"name": "i",
"start": 0,
"type": "Identifier"
}
},
"labeled": false
}
],
"start": 0,
@ -1559,7 +1563,8 @@ description: Result of parsing gear.kcl
"name": "i",
"start": 0,
"type": "Identifier"
}
},
"labeled": false
}
],
"start": 0,
@ -2119,7 +2124,7 @@ description: Result of parsing gear.kcl
"name": {
"commentStart": 0,
"end": 0,
"name": "sg",
"name": "accum",
"start": 0,
"type": "Identifier"
},
@ -2169,14 +2174,15 @@ description: Result of parsing gear.kcl
"name": "i",
"start": 0,
"type": "Identifier"
}
},
"labeled": false
},
{
"type": "Parameter",
"identifier": {
"commentStart": 0,
"end": 0,
"name": "sg",
"name": "accum",
"start": 0,
"type": "Identifier"
}
@ -2735,7 +2741,7 @@ description: Result of parsing gear.kcl
"name": {
"commentStart": 0,
"end": 0,
"name": "sg",
"name": "accum",
"start": 0,
"type": "Identifier"
},
@ -2767,14 +2773,15 @@ description: Result of parsing gear.kcl
"name": "i",
"start": 0,
"type": "Identifier"
}
},
"labeled": false
},
{
"type": "Parameter",
"identifier": {
"commentStart": 0,
"end": 0,
"name": "sg",
"name": "accum",
"start": 0,
"type": "Identifier"
}

File diff suppressed because it is too large Load Diff

View File

@ -47,23 +47,23 @@ flowchart LR
142[Solid2d]
end
subgraph path23 [Path]
23["Path<br>[998, 1045, 3]"]
64["Segment<br>[1051, 1092, 3]"]
65["Segment<br>[1098, 1140, 3]"]
66["Segment<br>[1146, 1188, 3]"]
67["Segment<br>[1194, 1201, 3]"]
23["Path<br>[1000, 1047, 3]"]
64["Segment<br>[1053, 1094, 3]"]
65["Segment<br>[1100, 1142, 3]"]
66["Segment<br>[1148, 1190, 3]"]
67["Segment<br>[1196, 1203, 3]"]
150[Solid2d]
end
subgraph path24 [Path]
24["Path<br>[1459, 1610, 3]"]
68["Segment<br>[1616, 1692, 3]"]
69["Segment<br>[1698, 1851, 3]"]
70["Segment<br>[1857, 1933, 3]"]
71["Segment<br>[1939, 2095, 3]"]
72["Segment<br>[2101, 2178, 3]"]
73["Segment<br>[2184, 2339, 3]"]
74["Segment<br>[2345, 2421, 3]"]
75["Segment<br>[2427, 2434, 3]"]
24["Path<br>[1461, 1612, 3]"]
68["Segment<br>[1618, 1694, 3]"]
69["Segment<br>[1700, 1853, 3]"]
70["Segment<br>[1859, 1935, 3]"]
71["Segment<br>[1941, 2097, 3]"]
72["Segment<br>[2103, 2180, 3]"]
73["Segment<br>[2186, 2341, 3]"]
74["Segment<br>[2347, 2423, 3]"]
75["Segment<br>[2429, 2436, 3]"]
139[Solid2d]
end
subgraph path25 [Path]
@ -181,13 +181,13 @@ flowchart LR
end
1["Plane<br>[386, 403, 2]"]
2["Plane<br>[473, 490, 3]"]
3["Plane<br>[975, 992, 3]"]
4["Plane<br>[1436, 1453, 3]"]
5["Plane<br>[2585, 2602, 3]"]
6["Plane<br>[2682, 2699, 3]"]
7["Plane<br>[2781, 2798, 3]"]
8["Plane<br>[2879, 2896, 3]"]
9["Plane<br>[2977, 2994, 3]"]
3["Plane<br>[977, 994, 3]"]
4["Plane<br>[1438, 1455, 3]"]
5["Plane<br>[2587, 2604, 3]"]
6["Plane<br>[2684, 2701, 3]"]
7["Plane<br>[2783, 2800, 3]"]
8["Plane<br>[2881, 2898, 3]"]
9["Plane<br>[2979, 2996, 3]"]
10["Plane<br>[325, 342, 5]"]
11["Plane<br>[553, 592, 5]"]
12["Plane<br>[256, 273, 6]"]
@ -200,7 +200,7 @@ flowchart LR
158["Sweep Extrusion<br>[1767, 1810, 2]"]
159["Sweep Extrusion<br>[2169, 2212, 2]"]
160["Sweep Extrusion<br>[2464, 2497, 2]"]
161["Sweep Extrusion<br>[3035, 3066, 3]"]
161["Sweep Extrusion<br>[3037, 3068, 3]"]
162["Sweep Loft<br>[932, 975, 5]"]
163["Sweep Extrusion<br>[609, 661, 6]"]
164["Sweep Revolve<br>[540, 557, 7]"]

View File

@ -2,60 +2,60 @@
flowchart LR
subgraph path2 [Path]
2["Path<br>[733, 769, 0]"]
3["Segment<br>[923, 987, 0]"]
4["Segment<br>[923, 987, 0]"]
5["Segment<br>[923, 987, 0]"]
6["Segment<br>[923, 987, 0]"]
7["Segment<br>[923, 987, 0]"]
8["Segment<br>[923, 987, 0]"]
9["Segment<br>[923, 987, 0]"]
10["Segment<br>[923, 987, 0]"]
11["Segment<br>[923, 987, 0]"]
12["Segment<br>[923, 987, 0]"]
13["Segment<br>[923, 987, 0]"]
14["Segment<br>[923, 987, 0]"]
15["Segment<br>[923, 987, 0]"]
16["Segment<br>[923, 987, 0]"]
17["Segment<br>[923, 987, 0]"]
18["Segment<br>[923, 987, 0]"]
19["Segment<br>[923, 987, 0]"]
20["Segment<br>[923, 987, 0]"]
21["Segment<br>[923, 987, 0]"]
22["Segment<br>[923, 987, 0]"]
23["Segment<br>[923, 987, 0]"]
24["Segment<br>[923, 987, 0]"]
25["Segment<br>[923, 987, 0]"]
26["Segment<br>[923, 987, 0]"]
27["Segment<br>[923, 987, 0]"]
28["Segment<br>[923, 987, 0]"]
29["Segment<br>[923, 987, 0]"]
30["Segment<br>[923, 987, 0]"]
31["Segment<br>[923, 987, 0]"]
32["Segment<br>[923, 987, 0]"]
33["Segment<br>[923, 987, 0]"]
34["Segment<br>[923, 987, 0]"]
35["Segment<br>[923, 987, 0]"]
36["Segment<br>[923, 987, 0]"]
37["Segment<br>[923, 987, 0]"]
38["Segment<br>[923, 987, 0]"]
39["Segment<br>[923, 987, 0]"]
40["Segment<br>[923, 987, 0]"]
41["Segment<br>[923, 987, 0]"]
42["Segment<br>[923, 987, 0]"]
43["Segment<br>[923, 987, 0]"]
44["Segment<br>[923, 987, 0]"]
45["Segment<br>[923, 987, 0]"]
46["Segment<br>[923, 987, 0]"]
47["Segment<br>[923, 987, 0]"]
48["Segment<br>[923, 987, 0]"]
49["Segment<br>[923, 987, 0]"]
50["Segment<br>[923, 987, 0]"]
51["Segment<br>[923, 987, 0]"]
52["Segment<br>[1051, 1069, 0]"]
3["Segment<br>[923, 986, 0]"]
4["Segment<br>[923, 986, 0]"]
5["Segment<br>[923, 986, 0]"]
6["Segment<br>[923, 986, 0]"]
7["Segment<br>[923, 986, 0]"]
8["Segment<br>[923, 986, 0]"]
9["Segment<br>[923, 986, 0]"]
10["Segment<br>[923, 986, 0]"]
11["Segment<br>[923, 986, 0]"]
12["Segment<br>[923, 986, 0]"]
13["Segment<br>[923, 986, 0]"]
14["Segment<br>[923, 986, 0]"]
15["Segment<br>[923, 986, 0]"]
16["Segment<br>[923, 986, 0]"]
17["Segment<br>[923, 986, 0]"]
18["Segment<br>[923, 986, 0]"]
19["Segment<br>[923, 986, 0]"]
20["Segment<br>[923, 986, 0]"]
21["Segment<br>[923, 986, 0]"]
22["Segment<br>[923, 986, 0]"]
23["Segment<br>[923, 986, 0]"]
24["Segment<br>[923, 986, 0]"]
25["Segment<br>[923, 986, 0]"]
26["Segment<br>[923, 986, 0]"]
27["Segment<br>[923, 986, 0]"]
28["Segment<br>[923, 986, 0]"]
29["Segment<br>[923, 986, 0]"]
30["Segment<br>[923, 986, 0]"]
31["Segment<br>[923, 986, 0]"]
32["Segment<br>[923, 986, 0]"]
33["Segment<br>[923, 986, 0]"]
34["Segment<br>[923, 986, 0]"]
35["Segment<br>[923, 986, 0]"]
36["Segment<br>[923, 986, 0]"]
37["Segment<br>[923, 986, 0]"]
38["Segment<br>[923, 986, 0]"]
39["Segment<br>[923, 986, 0]"]
40["Segment<br>[923, 986, 0]"]
41["Segment<br>[923, 986, 0]"]
42["Segment<br>[923, 986, 0]"]
43["Segment<br>[923, 986, 0]"]
44["Segment<br>[923, 986, 0]"]
45["Segment<br>[923, 986, 0]"]
46["Segment<br>[923, 986, 0]"]
47["Segment<br>[923, 986, 0]"]
48["Segment<br>[923, 986, 0]"]
49["Segment<br>[923, 986, 0]"]
50["Segment<br>[923, 986, 0]"]
51["Segment<br>[923, 986, 0]"]
52["Segment<br>[1050, 1068, 0]"]
53[Solid2d]
end
1["Plane<br>[710, 727, 0]"]
54["Sweep Extrusion<br>[1123, 1161, 0]"]
54["Sweep Extrusion<br>[1122, 1160, 0]"]
55[Wall]
56[Wall]
57[Wall]

View File

@ -801,7 +801,7 @@ description: Result of parsing loop_tag.kcl
"name": {
"commentStart": 0,
"end": 0,
"name": "sketch",
"name": "accum",
"start": 0,
"type": "Identifier"
},
@ -833,14 +833,15 @@ description: Result of parsing loop_tag.kcl
"name": "index",
"start": 0,
"type": "Identifier"
}
},
"labeled": false
},
{
"type": "Parameter",
"identifier": {
"commentStart": 0,
"end": 0,
"name": "sketch",
"name": "accum",
"start": 0,
"type": "Identifier"
}

View File

@ -27,8 +27,8 @@ initialSketch = startSketchOn(XY)
finalSketch = reduce(
[1..numSides-1],
initial = initialSketch,
f = fn(index, sketch) {
return line(sketch, end = calculatePoint(index), tag = $problematicTag)
f = fn(@index, accum) {
return line(accum, end = calculatePoint(index), tag = $problematicTag)
}
)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -34,8 +34,8 @@ initialSketch = startSketchOn(XY)
finalSketch = reduce(
[1 .. numSides - 1],
initial = initialSketch,
f = fn(index, sketch) {
return line(sketch, end = calculatePoint(index), tag = $problematicTag)
f = fn(@index, accum) {
return line(accum, end = calculatePoint(index), tag = $problematicTag)
},
)

View File

@ -48,6 +48,48 @@ description: Operations executed multi_transform.kcl
"sourceRange": []
}
},
{
"type": "GroupBegin",
"group": {
"type": "FunctionCall",
"name": null,
"functionSourceRange": [],
"unlabeledArg": {
"value": {
"type": "Number",
"value": 1.0,
"ty": {
"type": "Known",
"type": "Count"
}
},
"sourceRange": []
},
"labeledArgs": {}
},
"sourceRange": []
},
{
"type": "GroupBegin",
"group": {
"type": "FunctionCall",
"name": null,
"functionSourceRange": [],
"unlabeledArg": {
"value": {
"type": "Number",
"value": 2.0,
"ty": {
"type": "Known",
"type": "Count"
}
},
"sourceRange": []
},
"labeledArgs": {}
},
"sourceRange": []
},
{
"labeledArgs": {
"instances": {
@ -85,5 +127,11 @@ description: Operations executed multi_transform.kcl
},
"sourceRange": []
}
},
{
"type": "GroupEnd"
},
{
"type": "GroupEnd"
}
]

View File

@ -77,8 +77,8 @@ description: Artifact commands subtract_cylinder_from_cube.kcl
"type": "move_path_pen",
"path": "[uuid]",
"to": {
"x": -10.0,
"y": -10.0,
"x": -8.0,
"y": -8.0,
"z": 0.0
}
}
@ -106,8 +106,8 @@ description: Artifact commands subtract_cylinder_from_cube.kcl
"segment": {
"type": "line",
"end": {
"x": 10.0,
"y": -10.0,
"x": 12.0,
"y": -8.0,
"z": 0.0
},
"relative": false
@ -123,8 +123,8 @@ description: Artifact commands subtract_cylinder_from_cube.kcl
"segment": {
"type": "line",
"end": {
"x": 10.0,
"y": 10.0,
"x": 12.0,
"y": 12.0,
"z": 0.0
},
"relative": false
@ -140,8 +140,8 @@ description: Artifact commands subtract_cylinder_from_cube.kcl
"segment": {
"type": "line",
"end": {
"x": -10.0,
"y": 10.0,
"x": -8.0,
"y": 12.0,
"z": 0.0
},
"relative": false
@ -412,8 +412,8 @@ description: Artifact commands subtract_cylinder_from_cube.kcl
},
"y_axis": {
"x": 0.0,
"y": 1.0,
"z": 0.0
"y": 0.0,
"z": 1.0
},
"size": 60.0,
"clobber": false,
@ -439,8 +439,8 @@ description: Artifact commands subtract_cylinder_from_cube.kcl
"adjust_camera": false,
"planar_normal": {
"x": 0.0,
"y": 0.0,
"z": 1.0
"y": -1.0,
"z": 0.0
}
}
},
@ -453,8 +453,8 @@ description: Artifact commands subtract_cylinder_from_cube.kcl
"segment": {
"type": "arc",
"center": {
"x": 2.0,
"y": 2.0
"x": 5.0,
"y": 5.0
},
"radius": 2.0,
"start": {
@ -476,8 +476,8 @@ description: Artifact commands subtract_cylinder_from_cube.kcl
"type": "move_path_pen",
"path": "[uuid]",
"to": {
"x": 4.0,
"y": 2.0,
"x": 7.0,
"y": 5.0,
"z": 0.0
}
}
@ -507,8 +507,8 @@ description: Artifact commands subtract_cylinder_from_cube.kcl
"adjust_camera": false,
"planar_normal": {
"x": 0.0,
"y": 0.0,
"z": 1.0
"y": -1.0,
"z": 0.0
}
}
},
@ -518,7 +518,7 @@ description: Artifact commands subtract_cylinder_from_cube.kcl
"command": {
"type": "extrude",
"target": "[uuid]",
"distance": 5.0,
"distance": 34.0,
"faces": null,
"opposite": "None"
}
@ -594,6 +594,30 @@ description: Artifact commands subtract_cylinder_from_cube.kcl
"face_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "set_object_transform",
"object_id": "[uuid]",
"transforms": [
{
"translate": {
"property": {
"x": 0.0,
"y": 0.0,
"z": 3.14
},
"set": false,
"is_local": true
},
"rotate_rpy": null,
"rotate_angle_axis": null,
"scale": null
}
]
}
},
{
"cmdId": "[uuid]",
"range": [],

View File

@ -16,8 +16,8 @@ flowchart LR
1["Plane<br>[27, 44, 0]"]
2["Plane<br>[372, 389, 0]"]
12["Sweep Extrusion<br>[306, 326, 0]"]
13["Sweep Extrusion<br>[436, 455, 0]"]
14["CompositeSolid Subtract<br>[468, 504, 0]"]
13["Sweep Extrusion<br>[436, 456, 0]"]
14["CompositeSolid Subtract<br>[494, 530, 0]"]
15[Wall]
16[Wall]
17[Wall]

View File

@ -765,24 +765,24 @@ description: Result of parsing subtract_cylinder_from_cube.kcl
{
"commentStart": 0,
"end": 0,
"raw": "0",
"raw": "2",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 0.0,
"value": 2.0,
"suffix": "None"
}
},
{
"commentStart": 0,
"end": 0,
"raw": "0",
"raw": "2",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 0.0,
"value": 2.0,
"suffix": "None"
}
}
@ -867,7 +867,7 @@ description: Result of parsing subtract_cylinder_from_cube.kcl
"name": {
"commentStart": 0,
"end": 0,
"name": "XY",
"name": "XZ",
"start": 0,
"type": "Identifier"
},
@ -894,24 +894,24 @@ description: Result of parsing subtract_cylinder_from_cube.kcl
{
"commentStart": 0,
"end": 0,
"raw": "2",
"raw": "5",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 2.0,
"value": 5.0,
"suffix": "None"
}
},
{
"commentStart": 0,
"end": 0,
"raw": "2",
"raw": "5",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 2.0,
"value": 5.0,
"suffix": "None"
}
}
@ -981,12 +981,12 @@ description: Result of parsing subtract_cylinder_from_cube.kcl
"arg": {
"commentStart": 0,
"end": 0,
"raw": "5",
"raw": "34",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 5.0,
"value": 34.0,
"suffix": "None"
}
}
@ -1013,6 +1013,53 @@ description: Result of parsing subtract_cylinder_from_cube.kcl
"type": "CallExpressionKw",
"type": "CallExpressionKw",
"unlabeled": null
},
{
"arguments": [
{
"type": "LabeledArg",
"label": {
"commentStart": 0,
"end": 0,
"name": "z",
"start": 0,
"type": "Identifier"
},
"arg": {
"commentStart": 0,
"end": 0,
"raw": "3.14",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 3.14,
"suffix": "None"
}
}
}
],
"callee": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "translate",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name"
},
"commentStart": 0,
"end": 0,
"start": 0,
"type": "CallExpressionKw",
"type": "CallExpressionKw",
"unlabeled": null
}
],
"commentStart": 0,

View File

@ -8,9 +8,10 @@ fn cube(center) {
|> extrude(length = 10)
}
part001 = cube(center = [0, 0])
part002 = startSketchOn(XY)
|> circle(center = [2, 2], radius = 2)
|> extrude(length = 5)
part001 = cube(center = [2, 2])
part002 = startSketchOn(XZ)
|> circle(center = [5, 5], radius = 2)
|> extrude(length = 34)
|> translate(z = 3.14)
fullPart = subtract([part001], tools=[part002])

View File

@ -62,7 +62,7 @@ description: Operations executed subtract_cylinder_from_cube.kcl
"value": [
{
"type": "Number",
"value": 0.0,
"value": 2.0,
"ty": {
"type": "Default",
"len": {
@ -75,7 +75,7 @@ description: Operations executed subtract_cylinder_from_cube.kcl
},
{
"type": "Number",
"value": 0.0,
"value": 2.0,
"ty": {
"type": "Default",
"len": {
@ -112,7 +112,7 @@ description: Operations executed subtract_cylinder_from_cube.kcl
"length": {
"value": {
"type": "Number",
"value": 5.0,
"value": 34.0,
"ty": {
"type": "Default",
"len": {

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