Compare commits

..

3 Commits

296 changed files with 21856 additions and 39894 deletions

View File

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

View File

@ -88,7 +88,6 @@ jobs:
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}} KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}}
ZOO_HOST: https://api.dev.zoo.dev ZOO_HOST: https://api.dev.zoo.dev
RUST_BACKTRACE: full RUST_BACKTRACE: full
RUST_MIN_STACK: 10485760000
- name: Commit differences - name: Commit differences
if: steps.path-changes.outputs.outside-kcl-samples == 'false' && steps.cargo-test-kcl-samples.outcome == 'failure' if: steps.path-changes.outputs.outside-kcl-samples == 'false' && steps.cargo-test-kcl-samples.outcome == 'failure'
shell: bash shell: bash
@ -120,7 +119,6 @@ jobs:
# Configure nextest when it's run by insta (via just). # Configure nextest when it's run by insta (via just).
NEXTEST_PROFILE: ci NEXTEST_PROFILE: ci
RUST_BACKTRACE: full RUST_BACKTRACE: full
RUST_MIN_STACK: 10485760000
- name: Build and archive tests - name: Build and archive tests
run: | run: |
cd rust cd rust
@ -184,7 +182,6 @@ jobs:
env: env:
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}} KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}}
ZOO_HOST: https://api.dev.zoo.dev ZOO_HOST: https://api.dev.zoo.dev
RUST_MIN_STACK: 10485760000
- name: Upload results - name: Upload results
if: always() if: always()
run: .github/ci-cd-scripts/upload-results.sh run: .github/ci-cd-scripts/upload-results.sh
@ -193,7 +190,6 @@ jobs:
TAB_API_KEY: ${{ secrets.TAB_API_KEY }} TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }} CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
CI_PR_NUMBER: ${{ github.event.pull_request.number }} CI_PR_NUMBER: ${{ github.event.pull_request.number }}
CI_SUITE: unit:kcl
run-internal-kcl-samples: run-internal-kcl-samples:
name: cargo test (internal-kcl-samples) name: cargo test (internal-kcl-samples)
runs-on: runs-on:
@ -242,7 +238,6 @@ jobs:
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}} KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}}
ZOO_HOST: https://api.dev.zoo.dev ZOO_HOST: https://api.dev.zoo.dev
MODELING_APP_INTERNAL_SAMPLES_SECRET: ${{secrets.MODELING_APP_INTERNAL_SAMPLES_SECRET}} MODELING_APP_INTERNAL_SAMPLES_SECRET: ${{secrets.MODELING_APP_INTERNAL_SAMPLES_SECRET}}
RUST_MIN_STACK: 10485760000
run-wasm-tests: run-wasm-tests:
name: Run wasm tests name: Run wasm tests
strategy: strategy:

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -235,48 +235,6 @@ extrude001 = extrude(sketch001, length = 5)`
.first() .first()
).toBeVisible() ).toBeVisible()
}) })
test('KCL errors with functions show hints for the entire backtrace', async ({
page,
homePage,
scene,
cmdBar,
editor,
toolbar,
}) => {
await homePage.goToModelingScene()
await scene.settled(cmdBar)
const code = `fn check(@x) {
return assert(x, isGreaterThan = 0)
}
fn middle(@x) {
return check(x)
}
middle(1)
middle(0)
`
await test.step('Set the code with a KCL error', async () => {
await toolbar.openPane('code')
await editor.replaceCode('', code)
})
// This shows all the diagnostics in a way that doesn't require the mouse
// pointer hovering over a coordinate, which would be brittle.
await test.step('Open CodeMirror diagnostics list', async () => {
// Ensure keyboard focus is in the editor.
await page.getByText('fn check(').click()
await page.keyboard.press('ControlOrMeta+Shift+M')
})
await expect(
page.getByText(`assert failed: Expected 0 to be greater than 0 but it wasn't
check()
middle()`)
).toBeVisible()
// There should be one hint inside middle() and one at the top level.
await expect(page.getByText('Part of the error backtrace')).toHaveCount(2)
})
}) })
test( test(

View File

@ -684,33 +684,4 @@ c = 3 + a`
highlightedHeaderArg: 'value', highlightedHeaderArg: 'value',
}) })
}) })
test('Text-to-CAD command can be closed with escape while in prompt', async ({
page,
homePage,
cmdBar,
}) => {
await homePage.expectState({
projectCards: [],
sortBy: 'last-modified-desc',
})
await homePage.textToCadBtn.click()
await cmdBar.expectState({
stage: 'arguments',
commandName: 'Text-to-CAD Create',
currentArgKey: 'prompt',
currentArgValue: '',
headerArguments: {
Method: 'New project',
NewProjectName: 'untitled',
Prompt: '',
},
highlightedHeaderArg: 'prompt',
})
await page.keyboard.press('Escape')
await cmdBar.toBeClosed()
await cmdBar.expectState({
stage: 'commandBarClosed',
})
})
}) })

View File

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

View File

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

View File

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

View File

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

View File

@ -1855,11 +1855,7 @@ sketch002 = startSketchOn(XZ)
}, },
stage: 'review', stage: 'review',
}) })
// Confirm we can submit from the review step with just `Enter` await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar(true)
await cmdBar.expectState({
stage: 'commandBarClosed',
})
}) })
await test.step(`Confirm code is added to the editor, scene has changed`, async () => { await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
@ -1999,7 +1995,7 @@ profile001 = ${circleCode}`
}, },
stage: 'review', stage: 'review',
}) })
await cmdBar.progressCmdBar(true) await cmdBar.progressCmdBar()
await editor.expectEditor.toContain(sweepDeclaration) await editor.expectEditor.toContain(sweepDeclaration)
}) })

View File

@ -2064,59 +2064,3 @@ test(
}) })
} }
) )
test(
'nested dir import works on windows',
{ tag: ['@electron', '@windows'] },
async ({ scene, cmdBar, context, page }, testInfo) => {
// Skip if on non-windows
if (process.platform !== 'win32') {
test.skip()
}
await context.folderSetupFn(async (dir) => {
const bracketDir = path.join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
const nestedDir = path.join(bracketDir, 'nested')
await fsp.mkdir(nestedDir, { recursive: true })
await fsp.copyFile(
executorInputPath('cylinder-inches.kcl'),
path.join(nestedDir, 'main.kcl')
)
await fsp.writeFile(
path.join(bracketDir, 'main.kcl'),
`import 'nested\\main.kcl' as thing
thing`
)
})
await page.setBodyDimensions({ width: 1200, height: 500 })
const u = await getUtils(page)
const pointOnModel = { x: 630, y: 280 }
await test.step('Opening the bracket project should load the stream', async () => {
// expect to see the text bracket
await expect(page.getByText('bracket')).toBeVisible()
await page.getByText('bracket').click()
await scene.settled(cmdBar)
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).toBeEnabled({
timeout: 20_000,
})
// gray at this pixel means the stream has loaded in the most
// user way we can verify it (pixel color)
await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [125, 125, 125]), {
timeout: 10_000,
})
.toBeLessThan(15)
})
}
)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 65 KiB

View File

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

View File

@ -37,8 +37,6 @@ When you submit a PR to add or modify KCL samples, images will be generated and
[![bottle](screenshots/bottle.png)](bottle/main.kcl) [![bottle](screenshots/bottle.png)](bottle/main.kcl)
#### [bracket](bracket/main.kcl) ([screenshot](screenshots/bracket.png)) #### [bracket](bracket/main.kcl) ([screenshot](screenshots/bracket.png))
[![bracket](screenshots/bracket.png)](bracket/main.kcl) [![bracket](screenshots/bracket.png)](bracket/main.kcl)
#### [brake-rotor](brake-rotor/main.kcl) ([screenshot](screenshots/brake-rotor.png))
[![brake-rotor](screenshots/brake-rotor.png)](brake-rotor/main.kcl)
#### [car-wheel-assembly](car-wheel-assembly/main.kcl) ([screenshot](screenshots/car-wheel-assembly.png)) #### [car-wheel-assembly](car-wheel-assembly/main.kcl) ([screenshot](screenshots/car-wheel-assembly.png))
[![car-wheel-assembly](screenshots/car-wheel-assembly.png)](car-wheel-assembly/main.kcl) [![car-wheel-assembly](screenshots/car-wheel-assembly.png)](car-wheel-assembly/main.kcl)
#### [cold-plate](cold-plate/main.kcl) ([screenshot](screenshots/cold-plate.png)) #### [cold-plate](cold-plate/main.kcl) ([screenshot](screenshots/cold-plate.png))

View File

@ -29,4 +29,4 @@ bottleNeck = startSketchOn(bottleBody, face = END)
// Define a shell operation so that the entire body and neck are hollow, with only the top face opened // Define a shell operation so that the entire body and neck are hollow, with only the top face opened
bottleShell = shell(bottleNeck, faces = [END], thickness = wallThickness) bottleShell = shell(bottleNeck, faces = [END], thickness = wallThickness)
|> appearance(%, color = "#0078c2") |> appearance(color = "#0078c2")

View File

@ -40,7 +40,7 @@ bracketBody = startSketchOn(XZ)
|> xLine(length = thickness, tag = $seg05) |> xLine(length = thickness, tag = $seg05)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg06) |> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg06)
|> close() |> close()
|> extrude(%, length = width) |> extrude(length = width)
// Add mounting holes to mount to the shelf // Add mounting holes to mount to the shelf
shelfMountingHoles = startSketchOn(bracketBody, face = seg03) shelfMountingHoles = startSketchOn(bracketBody, face = seg03)
@ -53,7 +53,7 @@ shelfMountingHoles = startSketchOn(bracketBody, face = seg03)
) )
|> patternLinear2d(instances = 2, distance = -(extBendRadius + shelfMountingHolePlacementOffset) + shelfMountLength - shelfMountingHolePlacementOffset, axis = [-1, 0]) |> patternLinear2d(instances = 2, distance = -(extBendRadius + shelfMountingHolePlacementOffset) + shelfMountLength - shelfMountingHolePlacementOffset, axis = [-1, 0])
|> patternLinear2d(instances = 2, distance = width - (shelfMountingHolePlacementOffset * 2), axis = [0, 1]) |> patternLinear2d(instances = 2, distance = width - (shelfMountingHolePlacementOffset * 2), axis = [0, 1])
|> extrude(%, length = -thickness - .01) |> extrude(length = -thickness - .01)
// Add mounting holes to mount to the wall // Add mounting holes to mount to the wall
wallMountingHoles = startSketchOn(bracketBody, face = seg04) wallMountingHoles = startSketchOn(bracketBody, face = seg04)
@ -65,7 +65,7 @@ wallMountingHoles = startSketchOn(bracketBody, face = seg04)
radius = wallMountingHoleDiameter / 2, radius = wallMountingHoleDiameter / 2,
) )
|> patternLinear2d(instances = 2, distance = width - (wallMountingHolePlacementOffset * 2), axis = [0, 1]) |> patternLinear2d(instances = 2, distance = width - (wallMountingHolePlacementOffset * 2), axis = [0, 1])
|> extrude(%, length = -thickness - 0.1) |> extrude(length = -thickness - 0.1)
// Apply bends // Apply bends
fillet(bracketBody, radius = extBendRadius, tags = [getNextAdjacentEdge(seg03)]) fillet(bracketBody, radius = extBendRadius, tags = [getNextAdjacentEdge(seg03)])

View File

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

View File

@ -24,13 +24,13 @@ lugHoles = startSketchOn(rotorBump, face = END)
instances = lugCount, instances = lugCount,
rotateDuplicates = true, rotateDuplicates = true,
) )
|> extrude(%, length = -(rotorInnerDiameterThickness + rotorSinglePlateThickness)) |> extrude(length = -(rotorInnerDiameterThickness + rotorSinglePlateThickness))
|> appearance(color = "#dbcd70", roughness = 90, metalness = 90) |> appearance(color = "#dbcd70", roughness = 90, metalness = 90)
// (update when boolean is available) // (update when boolean is available)
centerSpacer = startSketchOn(rotor, face = START) centerSpacer = startSketchOn(rotor, face = START)
|> circle(%, center = [0, 0], radius = .25) |> circle(center = [0, 0], radius = .25)
|> extrude(%, length = spacerLength) |> extrude(length = spacerLength)
secondaryRotorSketch = startSketchOn(centerSpacer, face = END) secondaryRotorSketch = startSketchOn(centerSpacer, face = END)
|> circle(center = [0, 0], radius = rotorDiameter / 2) |> circle(center = [0, 0], radius = rotorDiameter / 2)

View File

@ -44,7 +44,7 @@ copperTubePath = startSketchOn(offsetPlane(XY, offset = tubeDiameter))
// Create the profile for the inner and outer diameter of the hollow copper tube // Create the profile for the inner and outer diameter of the hollow copper tube
tubeWall = startSketchOn(offsetPlane(YZ, offset = -7.35)) tubeWall = startSketchOn(offsetPlane(YZ, offset = -7.35))
|> circle(center = [-bendRadius * 3, tubeDiameter], radius = tubeDiameter / 2) |> circle(center = [-bendRadius * 3, tubeDiameter], radius = tubeDiameter / 2)
|> subtract2d(%, tool = circle(center = [-bendRadius * 3, tubeDiameter], radius = tubeDiameter / 2 - wallThickness)) |> subtract2d(tool = circle(center = [-bendRadius * 3, tubeDiameter], radius = tubeDiameter / 2 - wallThickness))
|> sweep(path = copperTubePath) |> sweep(path = copperTubePath)
|> appearance(color = "#b81b0a") |> appearance(color = "#b81b0a")

View File

@ -33,7 +33,7 @@ plateBody = startSketchOn(XY)
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)]) |> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close() |> close()
|> subtract2d(tool = circle(center = [0, 0], radius = centerHoleDiameter / 2 * 1.5)) |> subtract2d(tool = circle(center = [0, 0], radius = centerHoleDiameter / 2 * 1.5))
|> extrude(%, length = plateThickness) |> extrude(length = plateThickness)
// Function to create a countersunk hole // Function to create a countersunk hole
fn countersink(@holePosition) { fn countersink(@holePosition) {

View File

@ -10,7 +10,7 @@ import * from "parameters.kcl"
// Model the center of the fan // Model the center of the fan
fanCenter = startSketchOn(YZ) fanCenter = startSketchOn(YZ)
|> circle(center = [0, 0], radius = fanHeight / 2, tag = $centerBend) |> circle(center = [0, 0], radius = fanHeight / 2, tag = $centerBend)
|> extrude(%, length = fanHeight) |> extrude(length = fanHeight)
|> fillet(radius = 1.5, tags = [getOppositeEdge(centerBend)]) |> fillet(radius = 1.5, tags = [getOppositeEdge(centerBend)])
// Create a function for a lofted fan blade cross section that rotates about the center hub of the fan // Create a function for a lofted fan blade cross section that rotates about the center hub of the fan

View File

@ -38,10 +38,10 @@ lowerArm = startSketchOn(offsetPlane(XZ, offset = fanSize / 2 + 2))
// Create the profile of the mounting wire and sweep along the XZ path // Create the profile of the mounting wire and sweep along the XZ path
wireProfile = startSketchOn(offsetPlane(XY, offset = 40 + fanSize / 2)) wireProfile = startSketchOn(offsetPlane(XY, offset = 40 + fanSize / 2))
sweepUpperArm = circle(wireProfile, center = [-12, -fanSize / 2 - 2], radius = 1) sweepUpperArm = circle(wireProfile, center = [-12, -fanSize / 2 - 2], radius = 1)
|> sweep(%, path = upperArm) |> sweep(path = upperArm)
sweepLowerArm = circle(wireProfile, center = [-12, -fanSize / 2 - 2], radius = 1) sweepLowerArm = circle(wireProfile, center = [-12, -fanSize / 2 - 2], radius = 1)
|> sweep(%, path = lowerArm) |> sweep(path = lowerArm)
// Draw the XY components of the mounting wire path // Draw the XY components of the mounting wire path
upperHook = startSketchOn(offsetPlane(XY, offset = segEndY(seg07))) upperHook = startSketchOn(offsetPlane(XY, offset = segEndY(seg07)))
@ -64,10 +64,10 @@ lowerHook = startSketchOn(offsetPlane(XY, offset = segEndY(seg08)))
// Sweep the wire profile around the hook-shaped segments of the mounting wire // Sweep the wire profile around the hook-shaped segments of the mounting wire
hookProfile = startSketchOn(offsetPlane(YZ, offset = segEndX(seg07))) hookProfile = startSketchOn(offsetPlane(YZ, offset = segEndX(seg07)))
sweepUpperHook = circle(hookProfile, center = [-fanSize / 2 - 2, segEndY(seg07)], radius = 1) sweepUpperHook = circle(hookProfile, center = [-fanSize / 2 - 2, segEndY(seg07)], radius = 1)
|> sweep(%, path = upperHook) |> sweep(path = upperHook)
sweepLowerHook = circle(hookProfile, center = [-fanSize / 2 - 2, segEndY(seg08)], radius = 1) sweepLowerHook = circle(hookProfile, center = [-fanSize / 2 - 2, segEndY(seg08)], radius = 1)
|> sweep(%, path = lowerHook) |> sweep(path = lowerHook)
// Union each piece of the wire into a single continuous sweep // Union each piece of the wire into a single continuous sweep
[ [

View File

@ -46,23 +46,23 @@ valveStemSketch = startSketchOn(offsetPlane(XY, offset = valveHeadLength))
// Create the valve stem end // Create the valve stem end
stepLength = stemHeadLength / 10 stepLength = stemHeadLength / 10
step1 = startSketchOn(valveStemSketch, face = END) step1 = startSketchOn(valveStemSketch, face = END)
|> circle(%, center = [0, 0], radius = stemDiameter / 2 * 0.9) |> circle(center = [0, 0], radius = stemDiameter / 2 * 0.9)
|> extrude(%, length = stepLength * 2) |> extrude(length = stepLength * 2)
step2 = startSketchOn(step1, face = END) step2 = startSketchOn(step1, face = END)
|> circle(%, center = [0, 0], radius = stemDiameter / 2 * 0.8) |> circle(center = [0, 0], radius = stemDiameter / 2 * 0.8)
|> extrude(%, length = stepLength) |> extrude(length = stepLength)
step3 = startSketchOn(step2, face = END) step3 = startSketchOn(step2, face = END)
|> circle(%, center = [0, 0], radius = stemDiameter / 2 * 0.9) |> circle(center = [0, 0], radius = stemDiameter / 2 * 0.9)
|> extrude(%, length = stepLength) |> extrude(length = stepLength)
step4 = startSketchOn(step3, face = END) step4 = startSketchOn(step3, face = END)
|> circle(%, center = [0, 0], radius = stemDiameter / 2 * 0.8) |> circle(center = [0, 0], radius = stemDiameter / 2 * 0.8)
|> extrude(%, length = stepLength) |> extrude(length = stepLength)
step5 = startSketchOn(step4, face = END) step5 = startSketchOn(step4, face = END)
|> circle(%, center = [0, 0], radius = stemDiameter / 2 * 0.9) |> circle(center = [0, 0], radius = stemDiameter / 2 * 0.9)
|> extrude(%, length = stepLength) |> extrude(length = stepLength)
step6 = startSketchOn(step5, face = END) step6 = startSketchOn(step5, face = END)
|> circle(%, center = [0, 0], radius = stemDiameter / 2 * 0.8) |> circle(center = [0, 0], radius = stemDiameter / 2 * 0.8)
|> extrude(%, length = stepLength) |> extrude(length = stepLength)
step7 = startSketchOn(step6, face = END) step7 = startSketchOn(step6, face = END)
|> circle( |> circle(
%, %,
@ -70,7 +70,7 @@ step7 = startSketchOn(step6, face = END)
radius = stemDiameter / 2 * 0.9, radius = stemDiameter / 2 * 0.9,
tag = $seg02, tag = $seg02,
) )
|> extrude(%, length = stepLength * 3, tagEnd = $capEnd001) |> extrude(length = stepLength * 3, tagEnd = $capEnd001)
|> chamfer( |> chamfer(
length = 0.5, length = 0.5,
tags = [ tags = [

View File

@ -49,4 +49,4 @@ bottomExtrusion = startSketchOn(flangeBase, face = START)
// Cut a hole through the entire body // Cut a hole through the entire body
pipeHole = startSketchOn(topExtrusion, face = END) pipeHole = startSketchOn(topExtrusion, face = END)
|> circle(center = [0, 0], radius = pipeDia / 2) |> circle(center = [0, 0], radius = pipeDia / 2)
|> extrude(%, length = -(topTotalThickness + baseThickness + bottomThickness)) |> extrude(length = -(topTotalThickness + baseThickness + bottomThickness))

View File

@ -74,16 +74,6 @@
"main.kcl" "main.kcl"
] ]
}, },
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "brake-rotor/main.kcl",
"multipleFiles": false,
"title": "A 320mm vented brake disc (rotor), with straight vanes, 30mm thick. The disc bell should accommodate 5 M12 wheel studs on a 114.3mm pitch circle diameter.",
"description": "",
"files": [
"main.kcl"
]
},
{ {
"file": "main.kcl", "file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "car-wheel-assembly/main.kcl", "pathFromProjectDirectoryToFirstFile": "car-wheel-assembly/main.kcl",

View File

@ -17,7 +17,7 @@ supportThickness = 3
// Main body of the PDU faceplate with integrated rack mounting flanges // Main body of the PDU faceplate with integrated rack mounting flanges
faceplateShape = startSketchOn(offsetPlane(XY, offset = -faceplateHeight / 2)) faceplateShape = startSketchOn(offsetPlane(XY, offset = -faceplateHeight / 2))
|> startProfile(%, at = [-faceplateWidth / 2 - supportWidth, 0]) |> startProfile(at = [-faceplateWidth / 2 - supportWidth, 0])
|> yLine(length = supportThickness) |> yLine(length = supportThickness)
|> xLine(length = supportWidth) |> xLine(length = supportWidth)
|> yLine(length = faceplateDepth - supportThickness) |> yLine(length = faceplateDepth - supportThickness)
@ -53,7 +53,7 @@ leftSpacerPosition = leftSpacerWidth / 2 - (nestWidth / 2)
fn boxModuleFn(width) { fn boxModuleFn(width) {
shape = startSketchOn(XZ) shape = startSketchOn(XZ)
|> startProfile(%, at = [-width / 2, moduleHeight / 2]) |> startProfile(at = [-width / 2, moduleHeight / 2])
|> xLine(length = width) |> xLine(length = width)
|> yLine(length = -moduleHeight) |> yLine(length = -moduleHeight)
|> xLine(length = -width) |> xLine(length = -width)
@ -72,25 +72,25 @@ leftSpacerShape = boxModuleFn(width = leftSpacerWidth)
// Module for power switch including front plate and red rocker button // Module for power switch including front plate and red rocker button
switchPosition = leftSpacerPosition + leftSpacerWidth / 2 + moduleWidth / 2 switchPosition = leftSpacerPosition + leftSpacerWidth / 2 + moduleWidth / 2
switchWidth = moduleWidth swtichWidth = moduleWidth
// Switch Body // Switch Body
switchBody = boxModuleFn(width = moduleWidth) switchBody = boxModuleFn(width = moduleWidth)
// Switch Plate // Switch Plate
switchPlateWidth = 20 swtichPlateWidth = 20
switchPlateHeight = 30 switchPlateHeight = 30
switchPlateThickness = 3 switchPlateThickness = 3
switchPlateShape = startSketchOn(switchBody, face = END) switchPlateShape = startSketchOn(switchBody, face = END)
|> startProfile( |> startProfile(
%, %,
at = [ at = [
-switchPlateWidth / 2, -swtichPlateWidth / 2,
-switchPlateHeight / 2 -switchPlateHeight / 2
], ],
) )
|> yLine(length = switchPlateHeight) |> yLine(length = switchPlateHeight)
|> xLine(length = switchPlateWidth) |> xLine(length = swtichPlateWidth)
|> yLine(length = -switchPlateHeight) |> yLine(length = -switchPlateHeight)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close() |> close()
@ -104,8 +104,8 @@ switchPlateBody = extrude(switchPlateShape, length = switchPlateThickness)
// Switch Button // Switch Button
switchButtonHeight = 26 switchButtonHeight = 26
switchButtonWidth = 15 swtichButtonWidth = 15
switchButtonShape = startSketchOn(offsetPlane(-YZ, offset = -switchButtonWidth / 2)) switchButtonShape = startSketchOn(offsetPlane(-YZ, offset = -swtichButtonWidth / 2))
|> startProfile( |> startProfile(
%, %,
at = [ at = [
@ -121,18 +121,18 @@ switchButtonShape = startSketchOn(offsetPlane(-YZ, offset = -switchButtonWidth /
]) ])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close() |> close()
switchButtonBody = extrude(switchButtonShape, length = switchButtonWidth) switchButtonBody = extrude(switchButtonShape, length = swtichButtonWidth)
|> translate( |> translate(
%, %,
x = switchPosition, x = switchPosition,
y = 0, y = 0,
z = 0, z = 0,
) )
|> appearance(%, color = "#ff0000") |> appearance(color = "#ff0000")
// Spacer between switch and plug modules for layout alignment // Spacer between switch and plug modules for layout alignment
secondSpacerWidth = moduleWidth / 2 secondSpacerWidth = moduleWidth / 2
secondSpacerPosition = switchPosition + switchWidth / 2 + secondSpacerWidth / 2 secondSpacerPosition = switchPosition + swtichWidth / 2 + secondSpacerWidth / 2
secondSpacerBody = boxModuleFn(width = secondSpacerWidth) secondSpacerBody = boxModuleFn(width = secondSpacerWidth)
|> translate( |> translate(
%, %,
@ -156,7 +156,7 @@ powerPlugBody = boxModuleFn(width = powerPlugWidth)
z = 0, z = 0,
) )
plugShape = startSketchOn(powerPlugBody, face = END) plugShape = startSketchOn(powerPlugBody, face = END)
|> circle(%, center = [0, 0], radius = 17) |> circle(center = [0, 0], radius = 17)
|> translate( |> translate(
%, %,
x = firstPowerPlugPosition, x = firstPowerPlugPosition,
@ -166,7 +166,7 @@ plugShape = startSketchOn(powerPlugBody, face = END)
plugBody = extrude(plugShape, length = -20) plugBody = extrude(plugShape, length = -20)
plugHoleDistance = 20 plugHoleDistance = 20
plugHoleShape = startSketchOn(plugBody, face = START) plugHoleShape = startSketchOn(plugBody, face = START)
|> circle(%, center = [-plugHoleDistance / 2, 0], radius = 3) |> circle(center = [-plugHoleDistance / 2, 0], radius = 3)
|> translate( |> translate(
%, %,
x = firstPowerPlugPosition, x = firstPowerPlugPosition,

View File

@ -71,7 +71,7 @@ bearingUpper = startProfile(
|> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close() |> close()
|> revolve(angle = 360, axis = Y) |> revolve(angle = 360, axis = Y)
|> appearance(%, color = "#121212") |> appearance(color = "#121212")
bearingLower = startProfile(bearingBody, at = [bearingBoreDiameter / 2, 0.025]) bearingLower = startProfile(bearingBody, at = [bearingBoreDiameter / 2, 0.025])
|> xLine(length = 0.05) |> xLine(length = 0.05)
@ -88,7 +88,7 @@ bearingLower = startProfile(bearingBody, at = [bearingBoreDiameter / 2, 0.025])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close() |> close()
|> revolve(angle = 360, axis = Y) |> revolve(angle = 360, axis = Y)
|> appearance(%, color = "#f0f0f0") |> appearance(color = "#f0f0f0")
// Revolve the link sketch // Revolve the link sketch
revolve(linkSketch, axis = Y, angle = 360 / 16) revolve(linkSketch, axis = Y, angle = 360 / 16)

View File

@ -21,7 +21,7 @@ plateSketch = startSketchOn(XY)
|> close() |> close()
|> subtract2d(tool = circle(center = [0, 0], radius = bearingOuterDiameter / 2)) |> subtract2d(tool = circle(center = [0, 0], radius = bearingOuterDiameter / 2))
plateBody = extrude(plateSketch, length = stockThickness) plateBody = extrude(plateSketch, length = stockThickness)
|> appearance(%, color = "#1e62eb") |> appearance(color = "#1e62eb")
|> fillet( |> fillet(
radius = boltDiameter * 1 / 3, radius = boltDiameter * 1 / 3,
tags = [ tags = [

View File

@ -11,13 +11,13 @@ import pipeInnerDiameter, pipeOuterDiameter, pipeLength from "parameters.kcl"
export fn pipe() { export fn pipe() {
// Create the pipe base // Create the pipe base
pipeBase = startSketchOn(XZ) pipeBase = startSketchOn(XZ)
|> circle(%, center = [0, 0], radius = pipeOuterDiameter / 2) |> circle(center = [0, 0], radius = pipeOuterDiameter / 2)
|> extrude(%, length = pipeLength) |> extrude(length = pipeLength)
// Extrude a hole through the length of the pipe // Extrude a hole through the length of the pipe
pipe = startSketchOn(pipeBase, face = END) pipe = startSketchOn(pipeBase, face = END)
|> circle(center = [0, 0], radius = pipeInnerDiameter / 2) |> circle(center = [0, 0], radius = pipeInnerDiameter / 2)
|> extrude(%, length = -pipeLength) |> extrude(length = -pipeLength)
|> appearance(color = "#a24ed0") |> appearance(color = "#a24ed0")
return pipe return pipe
} }

View File

@ -11,7 +11,7 @@ import pipeDiameter, mountingHoleDiameter, mountingHolePlacementDiameter, flange
export fn flange() { export fn flange() {
// Sketch the mounting hole pattern // Sketch the mounting hole pattern
mountingHoles = startSketchOn(XY) mountingHoles = startSketchOn(XY)
|> circle(%, center = [0, mountingHolePlacementDiameter / 2], radius = mountingHoleDiameter / 2) |> circle(center = [0, mountingHolePlacementDiameter / 2], radius = mountingHoleDiameter / 2)
|> patternCircular2d( |> patternCircular2d(
%, %,
instances = 4, instances = 4,
@ -22,23 +22,23 @@ export fn flange() {
// Create the flange base // Create the flange base
flangeBase = startSketchOn(XY) flangeBase = startSketchOn(XY)
|> circle(%, center = [0, 0], radius = flangeDiameter / 2) |> circle(center = [0, 0], radius = flangeDiameter / 2)
|> subtract2d(tool = mountingHoles) |> subtract2d(tool = mountingHoles)
|> extrude(%, length = flangeBaseThickness) |> extrude(length = flangeBaseThickness)
// Create both the raised portions on the front and back of the flange base // Create both the raised portions on the front and back of the flange base
flangeBack = startSketchOn(flangeBase, face = START) flangeBack = startSketchOn(flangeBase, face = START)
|> circle(%, center = [0, 0], radius = flangeBackDiameter / 2) |> circle(center = [0, 0], radius = flangeBackDiameter / 2)
|> extrude(%, length = flangeBackHeight) |> extrude(length = flangeBackHeight)
flangeFront = startSketchOn(flangeBase, face = END) flangeFront = startSketchOn(flangeBase, face = END)
|> circle(%, center = [0, 0], radius = flangeFrontDiameter / 2) |> circle(center = [0, 0], radius = flangeFrontDiameter / 2)
|> extrude(%, length = flangeFrontHeight) |> extrude(length = flangeFrontHeight)
// Create the circular cut in the center for the pipe // Create the circular cut in the center for the pipe
pipeCut = startSketchOn(flangeFront, face = END) pipeCut = startSketchOn(flangeFront, face = END)
|> circle(%, center = [0, 0], radius = pipeDiameter / 2) |> circle(center = [0, 0], radius = pipeDiameter / 2)
|> extrude(%, length = -flangeTotalThickness) |> extrude(length = -flangeTotalThickness)
|> appearance(%, color = "#bab0b0") |> appearance(color = "#bab0b0")
return pipeCut return pipeCut
} }

View File

@ -9,11 +9,11 @@ import gasketOutsideDiameter, gasketInnerDiameter, gasketThickness from "paramet
// Create the base of the gasket // Create the base of the gasket
gasketBase = startSketchOn(XY) gasketBase = startSketchOn(XY)
|> circle(%, center = [0, 0], radius = gasketOutsideDiameter / 2) |> circle(center = [0, 0], radius = gasketOutsideDiameter / 2)
|> extrude(%, length = gasketThickness) |> extrude(length = gasketThickness)
// Extrude a circular hole through the gasket base // Extrude a circular hole through the gasket base
startSketchOn(gasketBase, face = END) startSketchOn(gasketBase, face = END)
|> circle(%, center = [0, 0], radius = gasketInnerDiameter / 2) |> circle(center = [0, 0], radius = gasketInnerDiameter / 2)
|> extrude(%, length = -gasketThickness) |> extrude(length = -gasketThickness)
|> appearance(%, color = "#d0cb3e") |> appearance(color = "#d0cb3e")

View File

@ -26,8 +26,8 @@ export fn hexNut() {
// Create the hole in the center of the hex nut // Create the hole in the center of the hex nut
hexNut = startSketchOn(hexNutBase, face = END) hexNut = startSketchOn(hexNutBase, face = END)
|> circle(center = [0, 0], radius = hexNutDiameter / 2) |> circle(center = [0, 0], radius = hexNutDiameter / 2)
|> extrude(%, length = -hexNutThickness) |> extrude(length = -hexNutThickness)
|> appearance(%, color = "#4edfd5") |> appearance(color = "#4edfd5")
return hexNut return hexNut
} }

View File

@ -17,8 +17,8 @@ export fn washer() {
// Extrude a hole through the washer // Extrude a hole through the washer
washer = startSketchOn(washerBase, face = END) washer = startSketchOn(washerBase, face = END)
|> circle(center = [0, 0], radius = washerInnerDia / 2) |> circle(center = [0, 0], radius = washerInnerDia / 2)
|> extrude(%, length = -washerThickness) |> extrude(length = -washerThickness)
|> appearance(%, color = "#ee4f4f") |> appearance(color = "#ee4f4f")
return washer return washer
} }

View File

@ -11,11 +11,11 @@ pipeLength = 6
// Create the pipe base // Create the pipe base
pipeBase = startSketchOn(XZ) pipeBase = startSketchOn(XZ)
|> circle(%, center = [0, 0], radius = pipeOuterDiameter / 2) |> circle(center = [0, 0], radius = pipeOuterDiameter / 2)
|> extrude(%, length = pipeLength) |> extrude(length = pipeLength)
// Extrude a hole through the length of the pipe // Extrude a hole through the length of the pipe
pipe = startSketchOn(pipeBase, face = END) pipe = startSketchOn(pipeBase, face = END)
|> circle(center = [0, 0], radius = pipeInnerDiameter / 2) |> circle(center = [0, 0], radius = pipeInnerDiameter / 2)
|> extrude(%, length = -pipeLength) |> extrude(length = -pipeLength)
|> appearance(color = "#a24ed0") |> appearance(color = "#a24ed0")

View File

@ -112,7 +112,7 @@ stemLoftProfile6 = startSketchOn(plane006)
// Draw the third profile for the femoral stem // Draw the third profile for the femoral stem
stemTab = clone(stemLoftProfile6) stemTab = clone(stemLoftProfile6)
|> extrude(%, length = 6) |> extrude(length = 6)
// Loft the femur using all profiles in sequence // Loft the femur using all profiles in sequence

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 KiB

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

View File

@ -29,10 +29,10 @@ windowWidth = facadeWidth / windowCount
// Helper function: Creates a box from a center plane with given width and height // Helper function: Creates a box from a center plane with given width and height
fn boxFn(plane, width, height) { fn boxFn(plane, width, height) {
shape = startSketchOn(plane) shape = startSketchOn(plane)
|> startProfile(%, at = [-width / 2, -width / 2]) |> startProfile(at = [-width / 2, -width / 2])
|> line(%, end = [0, width]) |> line(end = [0, width])
|> line(%, end = [width, 0]) |> line(end = [width, 0])
|> line(%, end = [0, -width]) |> line(end = [0, -width])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close(%) |> close(%)
body = extrude(shape, length = height) body = extrude(shape, length = height)
@ -50,44 +50,44 @@ fn transformFn(@i) {
// Create building base // Create building base
baseThickness = 0.2 baseThickness = 0.2
baseSlab = boxFn(plane = XY, width = slabWidth, height = -baseThickness) baseSlab = boxFn(plane = XY, width = slabWidth, height = -baseThickness)
|> appearance(%, color = "#dbd7d2") |> appearance(color = "#dbd7d2")
// Create ground platform beneath the base // Create ground platform beneath the base
groundSize = 50 goundSize = 50
groundBody = boxFn(plane = offsetPlane(XY, offset = -baseThickness), width = groundSize, height = -5) groundBody = boxFn(plane = offsetPlane(XY, offset = -baseThickness), width = goundSize, height = -5)
|> appearance(%, color = "#3a3631") |> appearance(color = "#3a3631")
// Create a single slab with handrail height to be reused with pattern // Create a single slab with handrail height to be reused with pattern
slabAndHandrailGeometry = boxFn(plane = offsetPlane(XY, offset = floorHeight - slabThickness), width = slabWidth, height = slabThickness + handrailHeight) slabAndHandrailGeometry = boxFn(plane = offsetPlane(XY, offset = floorHeight - slabThickness), width = slabWidth, height = slabThickness + handrailHeight)
slabVoidStart = -slabWidth / 2 + handrailThickness slabVoidStart = -slabWidth / 2 + handrailThickness
slabVoidWidth = slabWidth - (handrailThickness * 2) slabVoidWidth = slabWidth - (handrailThickness * 2)
slabVoidShape = startSketchOn(slabAndHandrailGeometry, face = END) slabVoidShape = startSketchOn(slabAndHandrailGeometry, face = END)
|> startProfile(%, at = [slabVoidStart, slabVoidStart]) |> startProfile(at = [slabVoidStart, slabVoidStart])
|> line(%, end = [0, slabVoidWidth]) |> line(end = [0, slabVoidWidth])
|> line(%, end = [slabVoidWidth, 0]) |> line(end = [slabVoidWidth, 0])
|> line(%, end = [0, -slabVoidWidth]) |> line(end = [0, -slabVoidWidth])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close(%) |> close(%)
// Generate and pattern slabs with voids across all floors // Generate and pattern slabs with voids across all floors
slabBody = extrude(slabVoidShape, length = -handrailHeight) slabBody = extrude(slabVoidShape, length = -handrailHeight)
|> patternTransform(instances = floorCount, transform = transformFn) |> patternTransform(instances = floorCount, transform = transformFn)
|> appearance(%, color = "#dbd7d2") |> appearance(color = "#dbd7d2")
// Create structural core of the tower // Create structural core of the tower
coreLength = 10 coreLength = 10
coreWidth = 8 coreWidth = 8
core = startSketchOn(XY) core = startSketchOn(XY)
|> startProfile(%, at = [-coreLength / 2, -coreWidth / 2]) |> startProfile(at = [-coreLength / 2, -coreWidth / 2])
|> line(%, end = [0, coreWidth]) |> line(end = [0, coreWidth])
|> line(%, end = [coreLength, 0]) |> line(end = [coreLength, 0])
|> line(%, end = [-0.22, -coreWidth]) |> line(end = [-0.22, -coreWidth])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close(%) |> close(%)
|> extrude(%, length = coreHeight) |> extrude(length = coreHeight)
// Create facade panels for each floor // Create facade panels for each floor
facadeStart = facadeWidth / 2 facadeStart = facadeWidth / 2
facadeGeometry = boxFn(plane = XY, width = facadeWidth, height = facadeHeight) facadeGeometry = boxFn(plane = XY, width = facadeWidth, height = facadeHeight)
|> patternTransform(instances = floorCount, transform = transformFn) |> patternTransform(instances = floorCount, transform = transformFn)
|> appearance(%, color = "#151819") |> appearance(color = "#151819")

View File

@ -37,7 +37,7 @@ fn tongueBlockFn() {
// Create top-side profile with tongues // Create top-side profile with tongues
tongueShape = startSketchOn(XY) tongueShape = startSketchOn(XY)
|> startProfile(%, at = [-insertLength / 2, insertThickness / 2]) |> startProfile(at = [-insertLength / 2, insertThickness / 2])
|> tongueBlockFn() |> tongueBlockFn()
|> yLine(length = -insertThickness / 2) |> yLine(length = -insertThickness / 2)
|> xLine(length = -insertLength) |> xLine(length = -insertLength)

View File

@ -145,19 +145,19 @@ sinkBodyInside = startProfile(
// tap // tap
tapPlate = startSketchOn(offsetPlane(XY, offset = tableHeight)) tapPlate = startSketchOn(offsetPlane(XY, offset = tableHeight))
|> circle(%, center = [blockWidth / 2, tableDepth - 55], radius = 40) |> circle(center = [blockWidth / 2, tableDepth - 55], radius = 40)
|> patternLinear2d(axis = [1, 0], instances = sinkCount, distance = sinkSpacing) |> patternLinear2d(axis = [1, 0], instances = sinkCount, distance = sinkSpacing)
|> extrude(length = 5) |> extrude(length = 5)
tapPillar = startSketchOn(offsetPlane(XY, offset = tableHeight)) tapPillar = startSketchOn(offsetPlane(XY, offset = tableHeight))
|> circle(%, center = [blockWidth / 2, tableDepth - 55], radius = 15) |> circle(center = [blockWidth / 2, tableDepth - 55], radius = 15)
|> patternLinear2d(axis = [1, 0], instances = sinkCount, distance = sinkSpacing) |> patternLinear2d(axis = [1, 0], instances = sinkCount, distance = sinkSpacing)
|> extrude(length = 170) |> extrude(length = 170)
tapNose = startSketchOn(offsetPlane(XZ, offset = 55 - tableDepth)) tapNose = startSketchOn(offsetPlane(XZ, offset = 55 - tableDepth))
|> circle(%, center = [blockWidth / 2, tableHeight + 100], radius = 10) |> circle(center = [blockWidth / 2, tableHeight + 100], radius = 10)
|> patternLinear2d(axis = [1, 0], instances = sinkCount, distance = sinkSpacing) |> patternLinear2d(axis = [1, 0], instances = sinkCount, distance = sinkSpacing)
|> extrude(length = sinkWidth / 2.5) |> extrude(length = sinkWidth / 2.5)
tapHandle = startSketchOn(offsetPlane(XZ, offset = 55 - tableDepth)) tapHandle = startSketchOn(offsetPlane(XZ, offset = 55 - tableDepth))
|> circle(%, center = [blockWidth / 2, tableHeight + 150], radius = 4) |> circle(center = [blockWidth / 2, tableHeight + 150], radius = 4)
|> patternLinear2d(axis = [1, 0], instances = sinkCount, distance = sinkSpacing) |> patternLinear2d(axis = [1, 0], instances = sinkCount, distance = sinkSpacing)
|> extrude(length = 70) |> extrude(length = 70)

View File

@ -14,7 +14,7 @@ body = startSketchOn(XZ)
|> yLine(length = -height, tag = $chamfer2) |> yLine(length = -height, tag = $chamfer2)
|> xLine(length = -width, tag = $chamfer3) |> xLine(length = -width, tag = $chamfer3)
|> close(tag = $chamfer4) |> close(tag = $chamfer4)
|> extrude(%, length = thickness) |> extrude(length = thickness)
|> chamfer( |> chamfer(
length = chamferLength, length = chamferLength,
tags = [ tags = [

View File

@ -10,6 +10,6 @@ import width, thickness, height, knobDiameter, knobHeight, knobFilletRadius from
// Create the knob sketch and revolve // Create the knob sketch and revolve
startSketchOn(XY) startSketchOn(XY)
|> circle(center = [0, 0], radius = knobDiameter / 2, tag = $knobBend) |> circle(center = [0, 0], radius = knobDiameter / 2, tag = $knobBend)
|> extrude(%, length = knobHeight) |> extrude(length = knobHeight)
|> fillet(radius = knobFilletRadius, tags = [getOppositeEdge(knobBend)]) |> fillet(radius = knobFilletRadius, tags = [getOppositeEdge(knobBend)])
|> appearance(%, color = "#afbf36") |> appearance(color = "#afbf36")

20
rust/Cargo.lock generated
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

View File

@ -439,7 +439,12 @@ impl EngineManager for EngineConnection {
request_sent: tx, request_sent: tx,
}) })
.await .await
.map_err(|e| KclError::Engine(KclErrorDetails::new(format!("Failed to send debug: {}", e), vec![])))?; .map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to send debug: {}", e),
source_ranges: vec![],
})
})?;
let _ = rx.await; let _ = rx.await;
Ok(()) Ok(())
@ -474,25 +479,25 @@ impl EngineManager for EngineConnection {
}) })
.await .await
.map_err(|e| { .map_err(|e| {
KclError::Engine(KclErrorDetails::new( KclError::Engine(KclErrorDetails {
format!("Failed to send modeling command: {}", e), message: format!("Failed to send modeling command: {}", e),
vec![source_range], source_ranges: vec![source_range],
)) })
})?; })?;
// Wait for the request to be sent. // Wait for the request to be sent.
rx.await rx.await
.map_err(|e| { .map_err(|e| {
KclError::Engine(KclErrorDetails::new( KclError::Engine(KclErrorDetails {
format!("could not send request to the engine actor: {e}"), message: format!("could not send request to the engine actor: {e}"),
vec![source_range], source_ranges: vec![source_range],
)) })
})? })?
.map_err(|e| { .map_err(|e| {
KclError::Engine(KclErrorDetails::new( KclError::Engine(KclErrorDetails {
format!("could not send request to the engine: {e}"), message: format!("could not send request to the engine: {e}"),
vec![source_range], source_ranges: vec![source_range],
)) })
})?; })?;
Ok(()) Ok(())
@ -516,15 +521,15 @@ impl EngineManager for EngineConnection {
// Check if we have any pending errors. // Check if we have any pending errors.
let pe = self.pending_errors.read().await; let pe = self.pending_errors.read().await;
if !pe.is_empty() { if !pe.is_empty() {
return Err(KclError::Engine(KclErrorDetails::new( return Err(KclError::Engine(KclErrorDetails {
pe.join(", ").to_string(), message: pe.join(", ").to_string(),
vec![source_range], source_ranges: vec![source_range],
))); }));
} else { } else {
return Err(KclError::Engine(KclErrorDetails::new( return Err(KclError::Engine(KclErrorDetails {
"Modeling command failed: websocket closed early".to_string(), message: "Modeling command failed: websocket closed early".to_string(),
vec![source_range], source_ranges: vec![source_range],
))); }));
} }
} }
@ -543,10 +548,10 @@ impl EngineManager for EngineConnection {
} }
} }
Err(KclError::Engine(KclErrorDetails::new( Err(KclError::Engine(KclErrorDetails {
format!("Modeling command timed out `{}`", id), message: format!("Modeling command timed out `{}`", id),
vec![source_range], source_ranges: vec![source_range],
))) }))
} }
async fn get_session_data(&self) -> Option<ModelingSessionData> { async fn get_session_data(&self) -> Option<ModelingSessionData> {

View File

@ -147,27 +147,32 @@ impl EngineConnection {
id_to_source_range: HashMap<uuid::Uuid, SourceRange>, id_to_source_range: HashMap<uuid::Uuid, SourceRange>,
) -> Result<(), KclError> { ) -> Result<(), KclError> {
let source_range_str = serde_json::to_string(&source_range).map_err(|e| { let source_range_str = serde_json::to_string(&source_range).map_err(|e| {
KclError::Engine(KclErrorDetails::new( KclError::Engine(KclErrorDetails {
format!("Failed to serialize source range: {:?}", e), message: format!("Failed to serialize source range: {:?}", e),
vec![source_range], source_ranges: vec![source_range],
)) })
})?; })?;
let cmd_str = serde_json::to_string(&cmd).map_err(|e| { let cmd_str = serde_json::to_string(&cmd).map_err(|e| {
KclError::Engine(KclErrorDetails::new( KclError::Engine(KclErrorDetails {
format!("Failed to serialize modeling command: {:?}", e), message: format!("Failed to serialize modeling command: {:?}", e),
vec![source_range], source_ranges: vec![source_range],
)) })
})?; })?;
let id_to_source_range_str = serde_json::to_string(&id_to_source_range).map_err(|e| { let id_to_source_range_str = serde_json::to_string(&id_to_source_range).map_err(|e| {
KclError::Engine(KclErrorDetails::new( KclError::Engine(KclErrorDetails {
format!("Failed to serialize id to source range: {:?}", e), message: format!("Failed to serialize id to source range: {:?}", e),
vec![source_range], source_ranges: vec![source_range],
)) })
})?; })?;
self.manager self.manager
.fire_modeling_cmd_from_wasm(id.to_string(), source_range_str, cmd_str, id_to_source_range_str) .fire_modeling_cmd_from_wasm(id.to_string(), source_range_str, cmd_str, id_to_source_range_str)
.map_err(|e| KclError::Engine(KclErrorDetails::new(e.to_string().into(), vec![source_range])))?; .map_err(|e| {
KclError::Engine(KclErrorDetails {
message: e.to_string().into(),
source_ranges: vec![source_range],
})
})?;
Ok(()) Ok(())
} }
@ -180,28 +185,33 @@ impl EngineConnection {
id_to_source_range: HashMap<uuid::Uuid, SourceRange>, id_to_source_range: HashMap<uuid::Uuid, SourceRange>,
) -> Result<WebSocketResponse, KclError> { ) -> Result<WebSocketResponse, KclError> {
let source_range_str = serde_json::to_string(&source_range).map_err(|e| { let source_range_str = serde_json::to_string(&source_range).map_err(|e| {
KclError::Engine(KclErrorDetails::new( KclError::Engine(KclErrorDetails {
format!("Failed to serialize source range: {:?}", e), message: format!("Failed to serialize source range: {:?}", e),
vec![source_range], source_ranges: vec![source_range],
)) })
})?; })?;
let cmd_str = serde_json::to_string(&cmd).map_err(|e| { let cmd_str = serde_json::to_string(&cmd).map_err(|e| {
KclError::Engine(KclErrorDetails::new( KclError::Engine(KclErrorDetails {
format!("Failed to serialize modeling command: {:?}", e), message: format!("Failed to serialize modeling command: {:?}", e),
vec![source_range], source_ranges: vec![source_range],
)) })
})?; })?;
let id_to_source_range_str = serde_json::to_string(&id_to_source_range).map_err(|e| { let id_to_source_range_str = serde_json::to_string(&id_to_source_range).map_err(|e| {
KclError::Engine(KclErrorDetails::new( KclError::Engine(KclErrorDetails {
format!("Failed to serialize id to source range: {:?}", e), message: format!("Failed to serialize id to source range: {:?}", e),
vec![source_range], source_ranges: vec![source_range],
)) })
})?; })?;
let promise = self let promise = self
.manager .manager
.send_modeling_cmd_from_wasm(id.to_string(), source_range_str, cmd_str, id_to_source_range_str) .send_modeling_cmd_from_wasm(id.to_string(), source_range_str, cmd_str, id_to_source_range_str)
.map_err(|e| KclError::Engine(KclErrorDetails::new(e.to_string().into(), vec![source_range])))?; .map_err(|e| {
KclError::Engine(KclErrorDetails {
message: e.to_string().into(),
source_ranges: vec![source_range],
})
})?;
let value = crate::wasm::JsFuture::from(promise).await.map_err(|e| { let value = crate::wasm::JsFuture::from(promise).await.map_err(|e| {
// Try to parse the error as an engine error. // Try to parse the error as an engine error.
@ -209,52 +219,53 @@ impl EngineConnection {
if let Ok(kittycad_modeling_cmds::websocket::FailureWebSocketResponse { errors, .. }) = if let Ok(kittycad_modeling_cmds::websocket::FailureWebSocketResponse { errors, .. }) =
serde_json::from_str(&err_str) serde_json::from_str(&err_str)
{ {
KclError::Engine(KclErrorDetails::new( KclError::Engine(KclErrorDetails {
errors.iter().map(|e| e.message.clone()).collect::<Vec<_>>().join("\n"), message: errors.iter().map(|e| e.message.clone()).collect::<Vec<_>>().join("\n"),
vec![source_range], source_ranges: vec![source_range],
)) })
} else if let Ok(data) = } else if let Ok(data) =
serde_json::from_str::<Vec<kittycad_modeling_cmds::websocket::FailureWebSocketResponse>>(&err_str) serde_json::from_str::<Vec<kittycad_modeling_cmds::websocket::FailureWebSocketResponse>>(&err_str)
{ {
if let Some(data) = data.first() { if let Some(data) = data.first() {
// It could also be an array of responses. // It could also be an array of responses.
KclError::Engine(KclErrorDetails::new( KclError::Engine(KclErrorDetails {
data.errors message: data
.errors
.iter() .iter()
.map(|e| e.message.clone()) .map(|e| e.message.clone())
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join("\n"), .join("\n"),
vec![source_range], source_ranges: vec![source_range],
)) })
} else { } else {
KclError::Engine(KclErrorDetails::new( KclError::Engine(KclErrorDetails {
"Received empty response from engine".into(), message: "Received empty response from engine".into(),
vec![source_range], source_ranges: vec![source_range],
)) })
} }
} else { } else {
KclError::Engine(KclErrorDetails::new( KclError::Engine(KclErrorDetails {
format!("Failed to wait for promise from send modeling command: {:?}", e), message: format!("Failed to wait for promise from send modeling command: {:?}", e),
vec![source_range], source_ranges: vec![source_range],
)) })
} }
})?; })?;
if value.is_null() || value.is_undefined() { if value.is_null() || value.is_undefined() {
return Err(KclError::Engine(KclErrorDetails::new( return Err(KclError::Engine(KclErrorDetails {
"Received null or undefined response from engine".into(), message: "Received null or undefined response from engine".into(),
vec![source_range], source_ranges: vec![source_range],
))); }));
} }
// Convert JsValue to a Uint8Array // Convert JsValue to a Uint8Array
let data = js_sys::Uint8Array::from(value); let data = js_sys::Uint8Array::from(value);
let ws_result: WebSocketResponse = bson::from_slice(&data.to_vec()).map_err(|e| { let ws_result: WebSocketResponse = bson::from_slice(&data.to_vec()).map_err(|e| {
KclError::Engine(KclErrorDetails::new( KclError::Engine(KclErrorDetails {
format!("Failed to deserialize bson response from engine: {:?}", e), message: format!("Failed to deserialize bson response from engine: {:?}", e),
vec![source_range], source_ranges: vec![source_range],
)) })
})?; })?;
Ok(ws_result) Ok(ws_result)
@ -305,16 +316,18 @@ impl crate::engine::EngineManager for EngineConnection {
*self.default_planes.write().await = Some(new_planes); *self.default_planes.write().await = Some(new_planes);
// Start a new session. // Start a new session.
let promise = self let promise = self.manager.start_new_session().map_err(|e| {
.manager KclError::Engine(KclErrorDetails {
.start_new_session() message: e.to_string().into(),
.map_err(|e| KclError::Engine(KclErrorDetails::new(e.to_string().into(), vec![source_range])))?; source_ranges: vec![source_range],
})
})?;
crate::wasm::JsFuture::from(promise).await.map_err(|e| { crate::wasm::JsFuture::from(promise).await.map_err(|e| {
KclError::Engine(KclErrorDetails::new( KclError::Engine(KclErrorDetails {
format!("Failed to wait for promise from start new session: {:?}", e), message: format!("Failed to wait for promise from start new session: {:?}", e),
vec![source_range], source_ranges: vec![source_range],
)) })
})?; })?;
Ok(()) Ok(())

View File

@ -276,10 +276,10 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
{ {
let duration = instant::Duration::from_millis(1); let duration = instant::Duration::from_millis(1);
wasm_timer::Delay::new(duration).await.map_err(|err| { wasm_timer::Delay::new(duration).await.map_err(|err| {
KclError::Internal(KclErrorDetails::new( KclError::Internal(KclErrorDetails {
format!("Failed to sleep: {:?}", err), message: format!("Failed to sleep: {:?}", err),
vec![source_range], source_ranges: vec![source_range],
)) })
})?; })?;
} }
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
@ -293,10 +293,10 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
return Ok(response); return Ok(response);
} }
Err(KclError::Engine(KclErrorDetails::new( Err(KclError::Engine(KclErrorDetails {
"async command timed out".to_string(), message: "async command timed out".to_string(),
vec![source_range], source_ranges: vec![source_range],
))) }))
} }
/// Ensure ALL async commands have been completed. /// Ensure ALL async commands have been completed.
@ -547,10 +547,10 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
id_to_source_range.insert(Uuid::from(*cmd_id), *range); id_to_source_range.insert(Uuid::from(*cmd_id), *range);
} }
_ => { _ => {
return Err(KclError::Engine(KclErrorDetails::new( return Err(KclError::Engine(KclErrorDetails {
format!("The request is not a modeling command: {:?}", req), message: format!("The request is not a modeling command: {:?}", req),
vec![*range], source_ranges: vec![*range],
))); }));
} }
} }
} }
@ -595,10 +595,10 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
self.parse_batch_responses(last_id.into(), id_to_source_range, responses) self.parse_batch_responses(last_id.into(), id_to_source_range, responses)
} else { } else {
// We should never get here. // We should never get here.
Err(KclError::Engine(KclErrorDetails::new( Err(KclError::Engine(KclErrorDetails {
format!("Failed to get batch response: {:?}", response), message: format!("Failed to get batch response: {:?}", response),
vec![source_range], source_ranges: vec![source_range],
))) }))
} }
} }
WebSocketRequest::ModelingCmdReq(ModelingCmdReq { cmd: _, cmd_id }) => { WebSocketRequest::ModelingCmdReq(ModelingCmdReq { cmd: _, cmd_id }) => {
@ -610,20 +610,20 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
// request so we need the original request source range in case the engine returns // request so we need the original request source range in case the engine returns
// an error. // an error.
let source_range = id_to_source_range.get(cmd_id.as_ref()).cloned().ok_or_else(|| { let source_range = id_to_source_range.get(cmd_id.as_ref()).cloned().ok_or_else(|| {
KclError::Engine(KclErrorDetails::new( KclError::Engine(KclErrorDetails {
format!("Failed to get source range for command ID: {:?}", cmd_id), message: format!("Failed to get source range for command ID: {:?}", cmd_id),
vec![], source_ranges: vec![],
)) })
})?; })?;
let ws_resp = self let ws_resp = self
.inner_send_modeling_cmd(cmd_id.into(), source_range, final_req, id_to_source_range) .inner_send_modeling_cmd(cmd_id.into(), source_range, final_req, id_to_source_range)
.await?; .await?;
self.parse_websocket_response(ws_resp, source_range) self.parse_websocket_response(ws_resp, source_range)
} }
_ => Err(KclError::Engine(KclErrorDetails::new( _ => Err(KclError::Engine(KclErrorDetails {
format!("The final request is not a modeling command: {:?}", final_req), message: format!("The final request is not a modeling command: {:?}", final_req),
vec![source_range], source_ranges: vec![source_range],
))), })),
} }
} }
@ -729,10 +729,10 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
for (name, plane_id, color) in plane_settings { for (name, plane_id, color) in plane_settings {
let info = DEFAULT_PLANE_INFO.get(&name).ok_or_else(|| { let info = DEFAULT_PLANE_INFO.get(&name).ok_or_else(|| {
// We should never get here. // We should never get here.
KclError::Engine(KclErrorDetails::new( KclError::Engine(KclErrorDetails {
format!("Failed to get default plane info for: {:?}", name), message: format!("Failed to get default plane info for: {:?}", name),
vec![source_range], source_ranges: vec![source_range],
)) })
})?; })?;
planes.insert( planes.insert(
name, name,
@ -763,14 +763,15 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
WebSocketResponse::Success(success) => Ok(success.resp), WebSocketResponse::Success(success) => Ok(success.resp),
WebSocketResponse::Failure(fail) => { WebSocketResponse::Failure(fail) => {
let _request_id = fail.request_id; let _request_id = fail.request_id;
Err(KclError::Engine(KclErrorDetails::new( Err(KclError::Engine(KclErrorDetails {
fail.errors message: fail
.errors
.iter() .iter()
.map(|e| e.message.clone()) .map(|e| e.message.clone())
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join("\n"), .join("\n"),
vec![source_range], source_ranges: vec![source_range],
))) }))
} }
} }
} }
@ -805,25 +806,25 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
BatchResponse::Failure { errors } => { BatchResponse::Failure { errors } => {
// Get the source range for the command. // Get the source range for the command.
let source_range = id_to_source_range.get(cmd_id).cloned().ok_or_else(|| { let source_range = id_to_source_range.get(cmd_id).cloned().ok_or_else(|| {
KclError::Engine(KclErrorDetails::new( KclError::Engine(KclErrorDetails {
format!("Failed to get source range for command ID: {:?}", cmd_id), message: format!("Failed to get source range for command ID: {:?}", cmd_id),
vec![], source_ranges: vec![],
)) })
})?; })?;
return Err(KclError::Engine(KclErrorDetails::new( return Err(KclError::Engine(KclErrorDetails {
errors.iter().map(|e| e.message.clone()).collect::<Vec<_>>().join("\n"), message: errors.iter().map(|e| e.message.clone()).collect::<Vec<_>>().join("\n"),
vec![source_range], source_ranges: vec![source_range],
))); }));
} }
} }
} }
// Return an error that we did not get an error or the response we wanted. // Return an error that we did not get an error or the response we wanted.
// This should never happen but who knows. // This should never happen but who knows.
Err(KclError::Engine(KclErrorDetails::new( Err(KclError::Engine(KclErrorDetails {
format!("Failed to find response for command ID: {:?}", id), message: format!("Failed to find response for command ID: {:?}", id),
vec![], source_ranges: vec![],
))) }))
} }
async fn modify_grid( async fn modify_grid(

View File

@ -380,39 +380,20 @@ impl miette::Diagnostic for Report {
} }
#[derive(Debug, Serialize, Deserialize, ts_rs::TS, Clone, PartialEq, Eq, thiserror::Error, miette::Diagnostic)] #[derive(Debug, Serialize, Deserialize, ts_rs::TS, Clone, PartialEq, Eq, thiserror::Error, miette::Diagnostic)]
#[serde(rename_all = "camelCase")]
#[error("{message}")] #[error("{message}")]
#[ts(export)] #[ts(export)]
pub struct KclErrorDetails { pub struct KclErrorDetails {
#[serde(rename = "sourceRanges")]
#[label(collection, "Errors")] #[label(collection, "Errors")]
pub source_ranges: Vec<SourceRange>, pub source_ranges: Vec<SourceRange>,
pub backtrace: Vec<BacktraceItem>,
#[serde(rename = "msg")] #[serde(rename = "msg")]
pub message: String, pub message: String,
} }
impl KclErrorDetails {
pub fn new(message: String, source_ranges: Vec<SourceRange>) -> KclErrorDetails {
let backtrace = source_ranges
.iter()
.map(|s| BacktraceItem {
source_range: *s,
fn_name: None,
})
.collect();
KclErrorDetails {
source_ranges,
backtrace,
message,
}
}
}
impl KclError { impl KclError {
pub fn internal(message: String) -> KclError { pub fn internal(message: String) -> KclError {
KclError::Internal(KclErrorDetails { KclError::Internal(KclErrorDetails {
source_ranges: Default::default(), source_ranges: Default::default(),
backtrace: Default::default(),
message, message,
}) })
} }
@ -474,122 +455,45 @@ impl KclError {
} }
} }
pub fn backtrace(&self) -> Vec<BacktraceItem> {
match self {
KclError::Lexical(e)
| KclError::Syntax(e)
| KclError::Semantic(e)
| KclError::ImportCycle(e)
| KclError::Type(e)
| KclError::Io(e)
| KclError::Unexpected(e)
| KclError::ValueAlreadyDefined(e)
| KclError::UndefinedValue(e)
| KclError::InvalidExpression(e)
| KclError::Engine(e)
| KclError::Internal(e) => e.backtrace.clone(),
}
}
pub(crate) fn override_source_ranges(&self, source_ranges: Vec<SourceRange>) -> Self { pub(crate) fn override_source_ranges(&self, source_ranges: Vec<SourceRange>) -> Self {
let mut new = self.clone(); let mut new = self.clone();
match &mut new { match &mut new {
KclError::Lexical(e) KclError::Lexical(e) => e.source_ranges = source_ranges,
| KclError::Syntax(e) KclError::Syntax(e) => e.source_ranges = source_ranges,
| KclError::Semantic(e) KclError::Semantic(e) => e.source_ranges = source_ranges,
| KclError::ImportCycle(e) KclError::ImportCycle(e) => e.source_ranges = source_ranges,
| KclError::Type(e) KclError::Type(e) => e.source_ranges = source_ranges,
| KclError::Io(e) KclError::Io(e) => e.source_ranges = source_ranges,
| KclError::Unexpected(e) KclError::Unexpected(e) => e.source_ranges = source_ranges,
| KclError::ValueAlreadyDefined(e) KclError::ValueAlreadyDefined(e) => e.source_ranges = source_ranges,
| KclError::UndefinedValue(e) KclError::UndefinedValue(e) => e.source_ranges = source_ranges,
| KclError::InvalidExpression(e) KclError::InvalidExpression(e) => e.source_ranges = source_ranges,
| KclError::Engine(e) KclError::Engine(e) => e.source_ranges = source_ranges,
| KclError::Internal(e) => { KclError::Internal(e) => e.source_ranges = source_ranges,
e.backtrace = source_ranges
.iter()
.map(|s| BacktraceItem {
source_range: *s,
fn_name: None,
})
.collect();
e.source_ranges = source_ranges;
}
} }
new new
} }
pub(crate) fn set_last_backtrace_fn_name(&self, last_fn_name: Option<String>) -> Self { pub(crate) fn add_source_ranges(&self, source_ranges: Vec<SourceRange>) -> Self {
let mut new = self.clone(); let mut new = self.clone();
match &mut new { match &mut new {
KclError::Lexical(e) KclError::Lexical(e) => e.source_ranges.extend(source_ranges),
| KclError::Syntax(e) KclError::Syntax(e) => e.source_ranges.extend(source_ranges),
| KclError::Semantic(e) KclError::Semantic(e) => e.source_ranges.extend(source_ranges),
| KclError::ImportCycle(e) KclError::ImportCycle(e) => e.source_ranges.extend(source_ranges),
| KclError::Type(e) KclError::Type(e) => e.source_ranges.extend(source_ranges),
| KclError::Io(e) KclError::Io(e) => e.source_ranges.extend(source_ranges),
| KclError::Unexpected(e) KclError::Unexpected(e) => e.source_ranges.extend(source_ranges),
| KclError::ValueAlreadyDefined(e) KclError::ValueAlreadyDefined(e) => e.source_ranges.extend(source_ranges),
| KclError::UndefinedValue(e) KclError::UndefinedValue(e) => e.source_ranges.extend(source_ranges),
| KclError::InvalidExpression(e) KclError::InvalidExpression(e) => e.source_ranges.extend(source_ranges),
| KclError::Engine(e) KclError::Engine(e) => e.source_ranges.extend(source_ranges),
| KclError::Internal(e) => { KclError::Internal(e) => e.source_ranges.extend(source_ranges),
if let Some(item) = e.backtrace.last_mut() {
item.fn_name = last_fn_name;
}
}
} }
new new
} }
pub(crate) fn add_unwind_location(&self, last_fn_name: Option<String>, source_range: SourceRange) -> Self {
let mut new = self.clone();
match &mut new {
KclError::Lexical(e)
| KclError::Syntax(e)
| KclError::Semantic(e)
| KclError::ImportCycle(e)
| KclError::Type(e)
| KclError::Io(e)
| KclError::Unexpected(e)
| KclError::ValueAlreadyDefined(e)
| KclError::UndefinedValue(e)
| KclError::InvalidExpression(e)
| KclError::Engine(e)
| KclError::Internal(e) => {
if let Some(item) = e.backtrace.last_mut() {
item.fn_name = last_fn_name;
}
e.backtrace.push(BacktraceItem {
source_range,
fn_name: None,
});
e.source_ranges.push(source_range);
}
}
new
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS, thiserror::Error, miette::Diagnostic)]
#[serde(rename_all = "camelCase")]
#[ts(export)]
pub struct BacktraceItem {
pub source_range: SourceRange,
pub fn_name: Option<String>,
}
impl std::fmt::Display for BacktraceItem {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(fn_name) = &self.fn_name {
write!(f, "{fn_name}: {:?}", self.source_range)
} else {
write!(f, "(fn): {:?}", self.source_range)
}
}
} }
impl IntoDiagnostic for KclError { impl IntoDiagnostic for KclError {
@ -647,7 +551,6 @@ impl From<pyo3::PyErr> for KclError {
fn from(error: pyo3::PyErr) -> Self { fn from(error: pyo3::PyErr) -> Self {
KclError::Internal(KclErrorDetails { KclError::Internal(KclErrorDetails {
source_ranges: vec![], source_ranges: vec![],
backtrace: Default::default(),
message: error.to_string(), message: error.to_string(),
}) })
} }
@ -726,13 +629,8 @@ impl CompilationError {
impl From<CompilationError> for KclErrorDetails { impl From<CompilationError> for KclErrorDetails {
fn from(err: CompilationError) -> Self { fn from(err: CompilationError) -> Self {
let backtrace = vec![BacktraceItem {
source_range: err.source_range,
fn_name: None,
}];
KclErrorDetails { KclErrorDetails {
source_ranges: vec![err.source_range], source_ranges: vec![err.source_range],
backtrace,
message: err.message, message: err.message,
} }
} }

View File

@ -70,10 +70,10 @@ pub(super) fn expect_properties<'a>(
) -> Result<&'a [Node<ObjectProperty>], KclError> { ) -> Result<&'a [Node<ObjectProperty>], KclError> {
assert_eq!(annotation.name().unwrap(), for_key); assert_eq!(annotation.name().unwrap(), for_key);
Ok(&**annotation.properties.as_ref().ok_or_else(|| { Ok(&**annotation.properties.as_ref().ok_or_else(|| {
KclError::Semantic(KclErrorDetails::new( KclError::Semantic(KclErrorDetails {
format!("Empty `{for_key}` annotation"), message: format!("Empty `{for_key}` annotation"),
vec![annotation.as_source_range()], source_ranges: vec![annotation.as_source_range()],
)) })
})?) })?)
} }
@ -84,10 +84,10 @@ pub(super) fn expect_ident(expr: &Expr) -> Result<&str, KclError> {
} }
} }
Err(KclError::Semantic(KclErrorDetails::new( Err(KclError::Semantic(KclErrorDetails {
"Unexpected settings value, expected a simple name, e.g., `mm`".to_owned(), message: "Unexpected settings value, expected a simple name, e.g., `mm`".to_owned(),
vec![expr.into()], source_ranges: vec![expr.into()],
))) }))
} }
// Returns the unparsed number literal. // Returns the unparsed number literal.
@ -98,10 +98,10 @@ pub(super) fn expect_number(expr: &Expr) -> Result<String, KclError> {
} }
} }
Err(KclError::Semantic(KclErrorDetails::new( Err(KclError::Semantic(KclErrorDetails {
"Unexpected settings value, expected a number, e.g., `1.0`".to_owned(), message: "Unexpected settings value, expected a number, e.g., `1.0`".to_owned(),
vec![expr.into()], source_ranges: vec![expr.into()],
))) }))
} }
pub(super) fn get_impl(annotations: &[Node<Annotation>], source_range: SourceRange) -> Result<Option<Impl>, KclError> { pub(super) fn get_impl(annotations: &[Node<Annotation>], source_range: SourceRange) -> Result<Option<Impl>, KclError> {
@ -113,14 +113,14 @@ pub(super) fn get_impl(annotations: &[Node<Annotation>], source_range: SourceRan
if &*p.key.name == IMPL { if &*p.key.name == IMPL {
if let Some(s) = p.value.ident_name() { if let Some(s) = p.value.ident_name() {
return Impl::from_str(s).map(Some).map_err(|_| { return Impl::from_str(s).map(Some).map_err(|_| {
KclError::Semantic(KclErrorDetails::new( KclError::Semantic(KclErrorDetails {
format!( message: format!(
"Invalid value for {} attribute, expected one of: {}", "Invalid value for {} attribute, expected one of: {}",
IMPL, IMPL,
IMPL_VALUES.join(", ") IMPL_VALUES.join(", ")
), ),
vec![source_range], source_ranges: vec![source_range],
)) })
}); });
} }
} }
@ -139,12 +139,12 @@ impl UnitLen {
"inch" | "in" => Ok(UnitLen::Inches), "inch" | "in" => Ok(UnitLen::Inches),
"ft" => Ok(UnitLen::Feet), "ft" => Ok(UnitLen::Feet),
"yd" => Ok(UnitLen::Yards), "yd" => Ok(UnitLen::Yards),
value => Err(KclError::Semantic(KclErrorDetails::new( value => Err(KclError::Semantic(KclErrorDetails {
format!( message: format!(
"Unexpected value for length units: `{value}`; expected one of `mm`, `cm`, `m`, `in`, `ft`, `yd`" "Unexpected value for length units: `{value}`; expected one of `mm`, `cm`, `m`, `in`, `ft`, `yd`"
), ),
vec![source_range], source_ranges: vec![source_range],
))), })),
} }
} }
} }
@ -154,10 +154,10 @@ impl UnitAngle {
match s { match s {
"deg" => Ok(UnitAngle::Degrees), "deg" => Ok(UnitAngle::Degrees),
"rad" => Ok(UnitAngle::Radians), "rad" => Ok(UnitAngle::Radians),
value => Err(KclError::Semantic(KclErrorDetails::new( value => Err(KclError::Semantic(KclErrorDetails {
format!("Unexpected value for angle units: `{value}`; expected one of `deg`, `rad`"), message: format!("Unexpected value for angle units: `{value}`; expected one of `deg`, `rad`"),
vec![source_range], source_ranges: vec![source_range],
))), })),
} }
} }
} }

View File

@ -941,10 +941,12 @@ fn artifacts_to_update(
ModelingCmd::StartPath(_) => { ModelingCmd::StartPath(_) => {
let mut return_arr = Vec::new(); let mut return_arr = Vec::new();
let current_plane_id = path_to_plane_id_map.get(&artifact_command.cmd_id).ok_or_else(|| { let current_plane_id = path_to_plane_id_map.get(&artifact_command.cmd_id).ok_or_else(|| {
KclError::Internal(KclErrorDetails::new( KclError::Internal(KclErrorDetails {
format!("Expected a current plane ID when processing StartPath command, but we have none: {id:?}"), message: format!(
vec![range], "Expected a current plane ID when processing StartPath command, but we have none: {id:?}"
)) ),
source_ranges: vec![range],
})
})?; })?;
return_arr.push(Artifact::Path(Path { return_arr.push(Artifact::Path(Path {
id, id,
@ -1063,10 +1065,10 @@ fn artifacts_to_update(
// TODO: Using the first one. Make sure to revisit this // TODO: Using the first one. Make sure to revisit this
// choice, don't think it matters for now. // choice, don't think it matters for now.
path_id: ArtifactId::new(*loft_cmd.section_ids.first().ok_or_else(|| { path_id: ArtifactId::new(*loft_cmd.section_ids.first().ok_or_else(|| {
KclError::Internal(KclErrorDetails::new( KclError::Internal(KclErrorDetails {
format!("Expected at least one section ID in Loft command: {id:?}; cmd={cmd:?}"), message: format!("Expected at least one section ID in Loft command: {id:?}; cmd={cmd:?}"),
vec![range], source_ranges: vec![range],
)) })
})?), })?),
surface_ids: Vec::new(), surface_ids: Vec::new(),
edge_ids: Vec::new(), edge_ids: Vec::new(),
@ -1106,12 +1108,12 @@ fn artifacts_to_update(
}; };
last_path = Some(path); last_path = Some(path);
let path_sweep_id = path.sweep_id.ok_or_else(|| { let path_sweep_id = path.sweep_id.ok_or_else(|| {
KclError::Internal(KclErrorDetails::new( KclError::Internal(KclErrorDetails {
format!( message:format!(
"Expected a sweep ID on the path when processing Solid3dGetExtrusionFaceInfo command, but we have none: {id:?}, {path:?}" "Expected a sweep ID on the path when processing Solid3dGetExtrusionFaceInfo command, but we have none: {id:?}, {path:?}"
), ),
vec![range], source_ranges: vec![range],
)) })
})?; })?;
let extra_artifact = exec_artifacts.values().find(|a| { let extra_artifact = exec_artifacts.values().find(|a| {
if let Artifact::StartSketchOnFace(s) = a { if let Artifact::StartSketchOnFace(s) = a {
@ -1160,12 +1162,12 @@ fn artifacts_to_update(
continue; continue;
}; };
let path_sweep_id = path.sweep_id.ok_or_else(|| { let path_sweep_id = path.sweep_id.ok_or_else(|| {
KclError::Internal(KclErrorDetails::new( KclError::Internal(KclErrorDetails {
format!( message:format!(
"Expected a sweep ID on the path when processing last path's Solid3dGetExtrusionFaceInfo command, but we have none: {id:?}, {path:?}" "Expected a sweep ID on the path when processing last path's Solid3dGetExtrusionFaceInfo command, but we have none: {id:?}, {path:?}"
), ),
vec![range], source_ranges: vec![range],
)) })
})?; })?;
let extra_artifact = exec_artifacts.values().find(|a| { let extra_artifact = exec_artifacts.values().find(|a| {
if let Artifact::StartSketchOnFace(s) = a { if let Artifact::StartSketchOnFace(s) = a {

View File

@ -131,10 +131,10 @@ impl ExecutorContext {
match statement { match statement {
BodyItem::ImportStatement(import_stmt) => { BodyItem::ImportStatement(import_stmt) => {
if !matches!(body_type, BodyType::Root) { if !matches!(body_type, BodyType::Root) {
return Err(KclError::Semantic(KclErrorDetails::new( return Err(KclError::Semantic(KclErrorDetails {
"Imports are only supported at the top-level of a file.".to_owned(), message: "Imports are only supported at the top-level of a file.".to_owned(),
vec![import_stmt.into()], source_ranges: vec![import_stmt.into()],
))); }));
} }
let source_range = SourceRange::from(import_stmt); let source_range = SourceRange::from(import_stmt);
@ -157,25 +157,28 @@ impl ExecutorContext {
let mut ty = mem.get_from(&ty_name, env_ref, import_item.into(), 0).cloned(); let mut ty = mem.get_from(&ty_name, env_ref, import_item.into(), 0).cloned();
if value.is_err() && ty.is_err() { if value.is_err() && ty.is_err() {
return Err(KclError::UndefinedValue(KclErrorDetails::new( return Err(KclError::UndefinedValue(KclErrorDetails {
format!("{} is not defined in module", import_item.name.name), message: format!("{} is not defined in module", import_item.name.name),
vec![SourceRange::from(&import_item.name)], source_ranges: vec![SourceRange::from(&import_item.name)],
))); }));
} }
// Check that the item is allowed to be imported (in at least one namespace). // Check that the item is allowed to be imported (in at least one namespace).
if value.is_ok() && !module_exports.contains(&import_item.name.name) { if value.is_ok() && !module_exports.contains(&import_item.name.name) {
value = Err(KclError::Semantic(KclErrorDetails::new( value = Err(KclError::Semantic(KclErrorDetails {
format!( message: format!(
"Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.", "Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
import_item.name.name import_item.name.name
), ),
vec![SourceRange::from(&import_item.name)], source_ranges: vec![SourceRange::from(&import_item.name)],
))); }));
} }
if ty.is_ok() && !module_exports.contains(&ty_name) { if ty.is_ok() && !module_exports.contains(&ty_name) {
ty = Err(KclError::Semantic(KclErrorDetails::new(String::new(), vec![]))); ty = Err(KclError::Semantic(KclErrorDetails {
message: String::new(),
source_ranges: vec![],
}));
} }
if value.is_err() && ty.is_err() { if value.is_err() && ty.is_err() {
@ -222,10 +225,10 @@ impl ExecutorContext {
.memory .memory
.get_from(name, env_ref, source_range, 0) .get_from(name, env_ref, source_range, 0)
.map_err(|_err| { .map_err(|_err| {
KclError::Internal(KclErrorDetails::new( KclError::Internal(KclErrorDetails {
format!("{} is not defined in module (but was exported?)", name), message: format!("{} is not defined in module (but was exported?)", name),
vec![source_range], source_ranges: vec![source_range],
)) })
})? })?
.clone(); .clone();
exec_state.mut_stack().add(name.to_owned(), item, source_range)?; exec_state.mut_stack().add(name.to_owned(), item, source_range)?;
@ -281,14 +284,7 @@ impl ExecutorContext {
// Track exports. // Track exports.
if let ItemVisibility::Export = variable_declaration.visibility { if let ItemVisibility::Export = variable_declaration.visibility {
if matches!(body_type, BodyType::Root) { exec_state.mod_local.module_exports.push(var_name);
exec_state.mod_local.module_exports.push(var_name);
} else {
exec_state.err(CompilationError::err(
variable_declaration.as_source_range(),
"Exports are only supported at the top-level of a file. Remove `export` or move it to the top-level.",
));
}
} }
// Variable declaration can be the return value of a module. // Variable declaration can be the return value of a module.
last_expr = matches!(body_type, BodyType::Root).then_some(value); last_expr = matches!(body_type, BodyType::Root).then_some(value);
@ -301,10 +297,10 @@ impl ExecutorContext {
let std_path = match &exec_state.mod_local.std_path { let std_path = match &exec_state.mod_local.std_path {
Some(p) => p, Some(p) => p,
None => { None => {
return Err(KclError::Semantic(KclErrorDetails::new( return Err(KclError::Semantic(KclErrorDetails {
"User-defined types are not yet supported.".to_owned(), message: "User-defined types are not yet supported.".to_owned(),
vec![metadata.source_range], source_ranges: vec![metadata.source_range],
))); }));
} }
}; };
let (t, props) = crate::std::std_ty(std_path, &ty.name.name); let (t, props) = crate::std::std_ty(std_path, &ty.name.name);
@ -317,10 +313,10 @@ impl ExecutorContext {
.mut_stack() .mut_stack()
.add(name_in_mem.clone(), value, metadata.source_range) .add(name_in_mem.clone(), value, metadata.source_range)
.map_err(|_| { .map_err(|_| {
KclError::Semantic(KclErrorDetails::new( KclError::Semantic(KclErrorDetails {
format!("Redefinition of type {}.", ty.name.name), message: format!("Redefinition of type {}.", ty.name.name),
vec![metadata.source_range], source_ranges: vec![metadata.source_range],
)) })
})?; })?;
if let ItemVisibility::Export = ty.visibility { if let ItemVisibility::Export = ty.visibility {
@ -347,10 +343,10 @@ impl ExecutorContext {
.mut_stack() .mut_stack()
.add(name_in_mem.clone(), value, metadata.source_range) .add(name_in_mem.clone(), value, metadata.source_range)
.map_err(|_| { .map_err(|_| {
KclError::Semantic(KclErrorDetails::new( KclError::Semantic(KclErrorDetails {
format!("Redefinition of type {}.", ty.name.name), message: format!("Redefinition of type {}.", ty.name.name),
vec![metadata.source_range], source_ranges: vec![metadata.source_range],
)) })
})?; })?;
if let ItemVisibility::Export = ty.visibility { if let ItemVisibility::Export = ty.visibility {
@ -358,10 +354,10 @@ impl ExecutorContext {
} }
} }
None => { None => {
return Err(KclError::Semantic(KclErrorDetails::new( return Err(KclError::Semantic(KclErrorDetails {
"User-defined types are not yet supported.".to_owned(), message: "User-defined types are not yet supported.".to_owned(),
vec![metadata.source_range], source_ranges: vec![metadata.source_range],
))) }))
} }
}, },
} }
@ -372,10 +368,10 @@ impl ExecutorContext {
let metadata = Metadata::from(return_statement); let metadata = Metadata::from(return_statement);
if matches!(body_type, BodyType::Root) { if matches!(body_type, BodyType::Root) {
return Err(KclError::Semantic(KclErrorDetails::new( return Err(KclError::Semantic(KclErrorDetails {
"Cannot return from outside a function.".to_owned(), message: "Cannot return from outside a function.".to_owned(),
vec![metadata.source_range], source_ranges: vec![metadata.source_range],
))); }));
} }
let value = self let value = self
@ -391,10 +387,10 @@ impl ExecutorContext {
.mut_stack() .mut_stack()
.add(memory::RETURN_NAME.to_owned(), value, metadata.source_range) .add(memory::RETURN_NAME.to_owned(), value, metadata.source_range)
.map_err(|_| { .map_err(|_| {
KclError::Semantic(KclErrorDetails::new( KclError::Semantic(KclErrorDetails {
"Multiple returns from a single function.".to_owned(), message: "Multiple returns from a single function.".to_owned(),
vec![metadata.source_range], source_ranges: vec![metadata.source_range],
)) })
})?; })?;
last_expr = None; last_expr = None;
} }
@ -497,10 +493,10 @@ impl ExecutorContext {
*cache = Some((val, er, items.clone())); *cache = Some((val, er, items.clone()));
(er, items) (er, items)
}), }),
ModuleRepr::Foreign(geom, _) => Err(KclError::Semantic(KclErrorDetails::new( ModuleRepr::Foreign(geom, _) => Err(KclError::Semantic(KclErrorDetails {
"Cannot import items from foreign modules".to_owned(), message: "Cannot import items from foreign modules".to_owned(),
vec![geom.source_range], source_ranges: vec![geom.source_range],
))), })),
ModuleRepr::Dummy => unreachable!("Looking up {}, but it is still being interpreted", path), ModuleRepr::Dummy => unreachable!("Looking up {}, but it is still being interpreted", path),
}; };
@ -576,13 +572,13 @@ impl ExecutorContext {
err.override_source_ranges(vec![source_range]) err.override_source_ranges(vec![source_range])
} else { } else {
// TODO would be great to have line/column for the underlying error here // TODO would be great to have line/column for the underlying error here
KclError::Semantic(KclErrorDetails::new( KclError::Semantic(KclErrorDetails {
format!( message: format!(
"Error loading imported file ({path}). Open it to view more details.\n {}", "Error loading imported file ({path}). Open it to view more details.\n {}",
err.message() err.message()
), ),
vec![source_range], source_ranges: vec![source_range],
)) })
} }
}) })
} }
@ -643,10 +639,11 @@ impl ExecutorContext {
meta: vec![metadata.to_owned()], meta: vec![metadata.to_owned()],
} }
} else { } else {
return Err(KclError::Semantic(KclErrorDetails::new( return Err(KclError::Semantic(KclErrorDetails {
"Rust implementation of functions is restricted to the standard library".to_owned(), message: "Rust implementation of functions is restricted to the standard library"
vec![metadata.source_range], .to_owned(),
))); source_ranges: vec![metadata.source_range],
}));
} }
} else { } else {
// Snapshotting memory here is crucial for semantics so that we close // Snapshotting memory here is crucial for semantics so that we close
@ -670,18 +667,18 @@ impl ExecutorContext {
"you cannot declare variable {name} as %, because % can only be used in function calls" "you cannot declare variable {name} as %, because % can only be used in function calls"
); );
return Err(KclError::Semantic(KclErrorDetails::new( return Err(KclError::Semantic(KclErrorDetails {
message, message,
vec![pipe_substitution.into()], source_ranges: vec![pipe_substitution.into()],
))); }));
} }
StatementKind::Expression => match exec_state.mod_local.pipe_value.clone() { StatementKind::Expression => match exec_state.mod_local.pipe_value.clone() {
Some(x) => x, Some(x) => x,
None => { None => {
return Err(KclError::Semantic(KclErrorDetails::new( return Err(KclError::Semantic(KclErrorDetails {
"cannot use % outside a pipe expression".to_owned(), message: "cannot use % outside a pipe expression".to_owned(),
vec![pipe_substitution.into()], source_ranges: vec![pipe_substitution.into()],
))); }));
} }
}, },
}, },
@ -753,13 +750,13 @@ fn apply_ascription(
} else { } else {
"" ""
}; };
KclError::Semantic(KclErrorDetails::new( KclError::Semantic(KclErrorDetails {
format!( message: format!(
"could not coerce {} value to type {ty}{suggestion}", "could not coerce {} value to type {ty}{suggestion}",
value.human_friendly_type() value.human_friendly_type()
), ),
vec![source_range], source_ranges: vec![source_range],
)) })
}) })
} }
@ -786,10 +783,10 @@ impl Node<Name> {
ctx: &ExecutorContext, ctx: &ExecutorContext,
) -> Result<&'a KclValue, KclError> { ) -> Result<&'a KclValue, KclError> {
if self.abs_path { if self.abs_path {
return Err(KclError::Semantic(KclErrorDetails::new( return Err(KclError::Semantic(KclErrorDetails {
"Absolute paths (names beginning with `::` are not yet supported)".to_owned(), message: "Absolute paths (names beginning with `::` are not yet supported)".to_owned(),
self.as_source_ranges(), source_ranges: self.as_source_ranges(),
))); }));
} }
if self.path.is_empty() { if self.path.is_empty() {
@ -801,10 +798,10 @@ impl Node<Name> {
let value = match mem_spec { let value = match mem_spec {
Some((env, exports)) => { Some((env, exports)) => {
if !exports.contains(&p.name) { if !exports.contains(&p.name) {
return Err(KclError::Semantic(KclErrorDetails::new( return Err(KclError::Semantic(KclErrorDetails {
format!("Item {} not found in module's exported items", p.name), message: format!("Item {} not found in module's exported items", p.name),
p.as_source_ranges(), source_ranges: p.as_source_ranges(),
))); }));
} }
exec_state exec_state
@ -816,13 +813,13 @@ impl Node<Name> {
}; };
let KclValue::Module { value: module_id, .. } = value else { let KclValue::Module { value: module_id, .. } = value else {
return Err(KclError::Semantic(KclErrorDetails::new( return Err(KclError::Semantic(KclErrorDetails {
format!( message: format!(
"Identifier in path must refer to a module, found {}", "Identifier in path must refer to a module, found {}",
value.human_friendly_type() value.human_friendly_type()
), ),
p.as_source_ranges(), source_ranges: p.as_source_ranges(),
))); }));
}; };
mem_spec = Some( mem_spec = Some(
@ -833,10 +830,10 @@ impl Node<Name> {
let (env, exports) = mem_spec.unwrap(); let (env, exports) = mem_spec.unwrap();
if !exports.contains(&self.name.name) { if !exports.contains(&self.name.name) {
return Err(KclError::Semantic(KclErrorDetails::new( return Err(KclError::Semantic(KclErrorDetails {
format!("Item {} not found in module's exported items", self.name.name), message: format!("Item {} not found in module's exported items", self.name.name),
self.name.as_source_ranges(), source_ranges: self.name.as_source_ranges(),
))); }));
} }
exec_state exec_state
@ -864,44 +861,46 @@ impl Node<MemberExpression> {
if let Some(value) = map.get(&property) { if let Some(value) = map.get(&property) {
Ok(value.to_owned()) Ok(value.to_owned())
} else { } else {
Err(KclError::UndefinedValue(KclErrorDetails::new( Err(KclError::UndefinedValue(KclErrorDetails {
format!("Property '{property}' not found in object"), message: format!("Property '{property}' not found in object"),
vec![self.clone().into()], source_ranges: vec![self.clone().into()],
))) }))
} }
} }
(KclValue::Object { .. }, Property::String(property), true) => { (KclValue::Object { .. }, Property::String(property), true) => Err(KclError::Semantic(KclErrorDetails {
Err(KclError::Semantic(KclErrorDetails::new( message: format!("Cannot index object with string; use dot notation instead, e.g. `obj.{property}`"),
format!("Cannot index object with string; use dot notation instead, e.g. `obj.{property}`"), source_ranges: vec![self.clone().into()],
vec![self.clone().into()], })),
)))
}
(KclValue::Object { .. }, p, _) => { (KclValue::Object { .. }, p, _) => {
let t = p.type_name(); let t = p.type_name();
let article = article_for(t); let article = article_for(t);
Err(KclError::Semantic(KclErrorDetails::new( Err(KclError::Semantic(KclErrorDetails {
format!("Only strings can be used as the property of an object, but you're using {article} {t}",), message: format!(
vec![self.clone().into()], "Only strings can be used as the property of an object, but you're using {article} {t}",
))) ),
source_ranges: vec![self.clone().into()],
}))
} }
(KclValue::HomArray { value: arr, .. }, Property::UInt(index), _) => { (KclValue::HomArray { value: arr, .. }, Property::UInt(index), _) => {
let value_of_arr = arr.get(index); let value_of_arr = arr.get(index);
if let Some(value) = value_of_arr { if let Some(value) = value_of_arr {
Ok(value.to_owned()) Ok(value.to_owned())
} else { } else {
Err(KclError::UndefinedValue(KclErrorDetails::new( Err(KclError::UndefinedValue(KclErrorDetails {
format!("The array doesn't have any item at index {index}"), message: format!("The array doesn't have any item at index {index}"),
vec![self.clone().into()], source_ranges: vec![self.clone().into()],
))) }))
} }
} }
(KclValue::HomArray { .. }, p, _) => { (KclValue::HomArray { .. }, p, _) => {
let t = p.type_name(); let t = p.type_name();
let article = article_for(t); let article = article_for(t);
Err(KclError::Semantic(KclErrorDetails::new( Err(KclError::Semantic(KclErrorDetails {
format!("Only integers >= 0 can be used as the index of an array, but you're using {article} {t}",), message: format!(
vec![self.clone().into()], "Only integers >= 0 can be used as the index of an array, but you're using {article} {t}",
))) ),
source_ranges: vec![self.clone().into()],
}))
} }
(KclValue::Solid { value }, Property::String(prop), false) if prop == "sketch" => Ok(KclValue::Sketch { (KclValue::Solid { value }, Property::String(prop), false) if prop == "sketch" => Ok(KclValue::Sketch {
value: Box::new(value.sketch), value: Box::new(value.sketch),
@ -919,10 +918,10 @@ impl Node<MemberExpression> {
(being_indexed, _, _) => { (being_indexed, _, _) => {
let t = being_indexed.human_friendly_type(); let t = being_indexed.human_friendly_type();
let article = article_for(&t); let article = article_for(&t);
Err(KclError::Semantic(KclErrorDetails::new( Err(KclError::Semantic(KclErrorDetails {
format!("Only arrays can be indexed, but you're trying to index {article} {t}"), message: format!("Only arrays can be indexed, but you're trying to index {article} {t}"),
vec![self.clone().into()], source_ranges: vec![self.clone().into()],
))) }))
} }
} }
} }
@ -997,26 +996,26 @@ impl Node<BinaryExpression> {
meta: _, meta: _,
} = left_value } = left_value
else { else {
return Err(KclError::Semantic(KclErrorDetails::new( return Err(KclError::Semantic(KclErrorDetails {
format!( message: format!(
"Cannot apply logical operator to non-boolean value: {}", "Cannot apply logical operator to non-boolean value: {}",
left_value.human_friendly_type() left_value.human_friendly_type()
), ),
vec![self.left.clone().into()], source_ranges: vec![self.left.clone().into()],
))); }));
}; };
let KclValue::Bool { let KclValue::Bool {
value: right_value, value: right_value,
meta: _, meta: _,
} = right_value } = right_value
else { else {
return Err(KclError::Semantic(KclErrorDetails::new( return Err(KclError::Semantic(KclErrorDetails {
format!( message: format!(
"Cannot apply logical operator to non-boolean value: {}", "Cannot apply logical operator to non-boolean value: {}",
right_value.human_friendly_type() right_value.human_friendly_type()
), ),
vec![self.right.clone().into()], source_ranges: vec![self.right.clone().into()],
))); }));
}; };
let raw_value = match self.operator { let raw_value = match self.operator {
BinaryOperator::Or => left_value || right_value, BinaryOperator::Or => left_value || right_value,
@ -1116,13 +1115,13 @@ impl Node<UnaryExpression> {
meta: _, meta: _,
} = value } = value
else { else {
return Err(KclError::Semantic(KclErrorDetails::new( return Err(KclError::Semantic(KclErrorDetails {
format!( message: format!(
"Cannot apply unary operator ! to non-boolean value: {}", "Cannot apply unary operator ! to non-boolean value: {}",
value.human_friendly_type() value.human_friendly_type()
), ),
vec![self.into()], source_ranges: vec![self.into()],
))); }));
}; };
let meta = vec![Metadata { let meta = vec![Metadata {
source_range: self.into(), source_range: self.into(),
@ -1137,13 +1136,13 @@ impl Node<UnaryExpression> {
let value = &self.argument.get_result(exec_state, ctx).await?; let value = &self.argument.get_result(exec_state, ctx).await?;
let err = || { let err = || {
KclError::Semantic(KclErrorDetails::new( KclError::Semantic(KclErrorDetails {
format!( message: format!(
"You can only negate numbers, planes, or lines, but this is a {}", "You can only negate numbers, planes, or lines, but this is a {}",
value.human_friendly_type() value.human_friendly_type()
), ),
vec![self.into()], source_ranges: vec![self.into()],
)) })
}; };
match value { match value {
KclValue::Number { value, ty, .. } => { KclValue::Number { value, ty, .. } => {
@ -1240,10 +1239,10 @@ pub(crate) async fn execute_pipe_body(
ctx: &ExecutorContext, ctx: &ExecutorContext,
) -> Result<KclValue, KclError> { ) -> Result<KclValue, KclError> {
let Some((first, body)) = body.split_first() else { let Some((first, body)) = body.split_first() else {
return Err(KclError::Semantic(KclErrorDetails::new( return Err(KclError::Semantic(KclErrorDetails {
"Pipe expressions cannot be empty".to_owned(), message: "Pipe expressions cannot be empty".to_owned(),
vec![source_range], source_ranges: vec![source_range],
))); }));
}; };
// Evaluate the first element in the pipeline. // Evaluate the first element in the pipeline.
// They use the pipe_value from some AST node above this, so that if pipe expression is nested in a larger pipe expression, // They use the pipe_value from some AST node above this, so that if pipe expression is nested in a larger pipe expression,
@ -1278,10 +1277,10 @@ async fn inner_execute_pipe_body(
) -> Result<KclValue, KclError> { ) -> Result<KclValue, KclError> {
for expression in body { for expression in body {
if let Expr::TagDeclarator(_) = expression { if let Expr::TagDeclarator(_) = expression {
return Err(KclError::Semantic(KclErrorDetails::new( return Err(KclError::Semantic(KclErrorDetails {
format!("This cannot be in a PipeExpression: {:?}", expression), message: format!("This cannot be in a PipeExpression: {:?}", expression),
vec![expression.into()], source_ranges: vec![expression.into()],
))); }));
} }
let metadata = Metadata { let metadata = Metadata {
source_range: SourceRange::from(expression), source_range: SourceRange::from(expression),
@ -1350,37 +1349,35 @@ impl Node<ArrayRangeExpression> {
StatementKind::Expression, StatementKind::Expression,
) )
.await?; .await?;
let (start, start_ty) = start_val let (start, start_ty) = start_val.as_int_with_ty().ok_or(KclError::Semantic(KclErrorDetails {
.as_int_with_ty() source_ranges: vec![self.into()],
.ok_or(KclError::Semantic(KclErrorDetails::new( message: format!("Expected int but found {}", start_val.human_friendly_type()),
format!("Expected int but found {}", start_val.human_friendly_type()), }))?;
vec![self.into()],
)))?;
let metadata = Metadata::from(&self.end_element); let metadata = Metadata::from(&self.end_element);
let end_val = ctx let end_val = ctx
.execute_expr(&self.end_element, exec_state, &metadata, &[], StatementKind::Expression) .execute_expr(&self.end_element, exec_state, &metadata, &[], StatementKind::Expression)
.await?; .await?;
let (end, end_ty) = end_val.as_int_with_ty().ok_or(KclError::Semantic(KclErrorDetails::new( let (end, end_ty) = end_val.as_int_with_ty().ok_or(KclError::Semantic(KclErrorDetails {
format!("Expected int but found {}", end_val.human_friendly_type()), source_ranges: vec![self.into()],
vec![self.into()], message: format!("Expected int but found {}", end_val.human_friendly_type()),
)))?; }))?;
if start_ty != end_ty { if start_ty != end_ty {
let start = start_val.as_ty_f64().unwrap_or(TyF64 { n: 0.0, ty: start_ty }); let start = start_val.as_ty_f64().unwrap_or(TyF64 { n: 0.0, ty: start_ty });
let start = fmt::human_display_number(start.n, start.ty); let start = fmt::human_display_number(start.n, start.ty);
let end = end_val.as_ty_f64().unwrap_or(TyF64 { n: 0.0, ty: end_ty }); let end = end_val.as_ty_f64().unwrap_or(TyF64 { n: 0.0, ty: end_ty });
let end = fmt::human_display_number(end.n, end.ty); let end = fmt::human_display_number(end.n, end.ty);
return Err(KclError::Semantic(KclErrorDetails::new( return Err(KclError::Semantic(KclErrorDetails {
format!("Range start and end must be of the same type, but found {start} and {end}"), source_ranges: vec![self.into()],
vec![self.into()], message: format!("Range start and end must be of the same type, but found {start} and {end}"),
))); }));
} }
if end < start { if end < start {
return Err(KclError::Semantic(KclErrorDetails::new( return Err(KclError::Semantic(KclErrorDetails {
format!("Range start is greater than range end: {start} .. {end}"), source_ranges: vec![self.into()],
vec![self.into()], message: format!("Range start is greater than range end: {start} .. {end}"),
))); }));
} }
let range: Vec<_> = if self.end_inclusive { let range: Vec<_> = if self.end_inclusive {
@ -1441,10 +1438,10 @@ fn article_for<S: AsRef<str>>(s: S) -> &'static str {
fn number_as_f64(v: &KclValue, source_range: SourceRange) -> Result<TyF64, KclError> { fn number_as_f64(v: &KclValue, source_range: SourceRange) -> Result<TyF64, KclError> {
v.as_ty_f64().ok_or_else(|| { v.as_ty_f64().ok_or_else(|| {
let actual_type = v.human_friendly_type(); let actual_type = v.human_friendly_type();
KclError::Semantic(KclErrorDetails::new( KclError::Semantic(KclErrorDetails {
format!("Expected a number, but found {actual_type}",), source_ranges: vec![source_range],
vec![source_range], message: format!("Expected a number, but found {actual_type}",),
)) })
}) })
} }
@ -1533,16 +1530,16 @@ impl Property {
if let Some(x) = crate::try_f64_to_usize(value) { if let Some(x) = crate::try_f64_to_usize(value) {
Ok(Property::UInt(x)) Ok(Property::UInt(x))
} else { } else {
Err(KclError::Semantic(KclErrorDetails::new( Err(KclError::Semantic(KclErrorDetails {
format!("{value} is not a valid index, indices must be whole numbers >= 0"), source_ranges: property_sr,
property_sr, message: format!("{value} is not a valid index, indices must be whole numbers >= 0"),
))) }))
} }
} }
_ => Err(KclError::Semantic(KclErrorDetails::new( _ => Err(KclError::Semantic(KclErrorDetails {
"Only numbers (>= 0) can be indexes".to_owned(), source_ranges: vec![sr],
vec![sr], message: "Only numbers (>= 0) can be indexes".to_owned(),
))), })),
} }
} }
} }
@ -1550,7 +1547,12 @@ impl Property {
} }
fn jvalue_to_prop(value: &KclValue, property_sr: Vec<SourceRange>, name: &str) -> Result<Property, KclError> { fn jvalue_to_prop(value: &KclValue, property_sr: Vec<SourceRange>, name: &str) -> Result<Property, KclError> {
let make_err = |message: String| Err::<Property, _>(KclError::Semantic(KclErrorDetails::new(message, property_sr))); let make_err = |message: String| {
Err::<Property, _>(KclError::Semantic(KclErrorDetails {
source_ranges: property_sr,
message,
}))
};
match value { match value {
KclValue::Number{value: num, .. } => { KclValue::Number{value: num, .. } => {
let num = *num; let num = *num;
@ -1793,10 +1795,10 @@ d = b + c
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
.await .await
.map_err(|err| { .map_err(|err| {
KclError::Internal(KclErrorDetails::new( KclError::Internal(crate::errors::KclErrorDetails {
format!("Failed to create mock engine connection: {}", err), message: format!("Failed to create mock engine connection: {}", err),
vec![SourceRange::default()], source_ranges: vec![SourceRange::default()],
)) })
}) })
.unwrap(), .unwrap(),
)), )),

View File

@ -1,16 +1,13 @@
use async_recursion::async_recursion; use async_recursion::async_recursion;
use indexmap::IndexMap; use indexmap::IndexMap;
use super::{types::ArrayLen, EnvironmentRef}; use crate::execution::cad_op::{Group, OpArg, OpKclValue, Operation};
use crate::{ use crate::{
docs::StdLibFn, docs::StdLibFn,
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
execution::{ execution::{
cad_op::{Group, OpArg, OpKclValue, Operation}, kcl_value::FunctionSource, memory, types::RuntimeType, BodyType, ExecState, ExecutorContext, KclValue,
kcl_value::FunctionSource, Metadata, StatementKind, TagEngineInfo, TagIdentifier,
memory,
types::RuntimeType,
BodyType, ExecState, ExecutorContext, KclValue, Metadata, StatementKind, TagEngineInfo, TagIdentifier,
}, },
parsing::ast::types::{CallExpressionKw, DefaultParamVal, FunctionExpression, Node, Program, Type}, parsing::ast::types::{CallExpressionKw, DefaultParamVal, FunctionExpression, Node, Program, Type},
source_range::SourceRange, source_range::SourceRange,
@ -18,6 +15,9 @@ use crate::{
CompilationError, CompilationError,
}; };
use super::types::ArrayLen;
use super::EnvironmentRef;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Args { pub struct Args {
/// Positional args. /// Positional args.
@ -281,13 +281,6 @@ impl Node<CallExpressionKw> {
def.call_kw(Some(func.name()), exec_state, ctx, args, callsite) def.call_kw(Some(func.name()), exec_state, ctx, args, callsite)
.await .await
.map(Option::unwrap) .map(Option::unwrap)
.map_err(|e| {
// This is used for the backtrace display. We don't add
// another location the way we do for user-defined
// functions because the error uses the Args, which
// already points here.
e.set_last_backtrace_fn_name(Some(func.name()))
})
} }
None => { None => {
// Clone the function so that we can use a mutable reference to // Clone the function so that we can use a mutable reference to
@ -295,10 +288,10 @@ impl Node<CallExpressionKw> {
let func = fn_name.get_result(exec_state, ctx).await?.clone(); let func = fn_name.get_result(exec_state, ctx).await?.clone();
let Some(fn_src) = func.as_fn() else { let Some(fn_src) = func.as_fn() else {
return Err(KclError::Semantic(KclErrorDetails::new( return Err(KclError::Semantic(KclErrorDetails {
"cannot call this because it isn't a function".to_string(), message: "cannot call this because it isn't a function".to_string(),
vec![callsite], source_ranges: vec![callsite],
))); }));
}; };
let return_value = fn_src let return_value = fn_src
@ -306,10 +299,7 @@ impl Node<CallExpressionKw> {
.await .await
.map_err(|e| { .map_err(|e| {
// Add the call expression to the source ranges. // Add the call expression to the source ranges.
// e.add_source_ranges(vec![callsite])
// TODO: Use the name that the function was defined
// with, not the identifier it was used with.
e.add_unwind_location(Some(fn_name.name.name.clone()), callsite)
})?; })?;
let result = return_value.ok_or_else(move || { let result = return_value.ok_or_else(move || {
@ -318,10 +308,10 @@ impl Node<CallExpressionKw> {
if let KclValue::Function { meta, .. } = func { if let KclValue::Function { meta, .. } = func {
source_ranges = meta.iter().map(|m| m.source_range).collect(); source_ranges = meta.iter().map(|m| m.source_range).collect();
}; };
KclError::UndefinedValue(KclErrorDetails::new( KclError::UndefinedValue(KclErrorDetails {
format!("Result of user-defined function {} is undefined", fn_name), message: format!("Result of user-defined function {} is undefined", fn_name),
source_ranges, source_ranges,
)) })
})?; })?;
Ok(result) Ok(result)
@ -500,10 +490,10 @@ fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut Ex
let tag_id = if let Some(t) = value.sketch.tags.get(&tag.name) { let tag_id = if let Some(t) = value.sketch.tags.get(&tag.name) {
let mut t = t.clone(); let mut t = t.clone();
let Some(info) = t.get_cur_info() else { let Some(info) = t.get_cur_info() else {
return Err(KclError::Internal(KclErrorDetails::new( return Err(KclError::Internal(KclErrorDetails {
format!("Tag {} does not have path info", tag.name), message: format!("Tag {} does not have path info", tag.name),
vec![tag.into()], source_ranges: vec![tag.into()],
))); }));
}; };
let mut info = info.clone(); let mut info = info.clone();
@ -618,10 +608,10 @@ fn type_check_params_kw(
// TODO if we have access to the AST for the argument we could choose which example to suggest. // 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})`"); 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::new( KclError::Semantic(KclErrorDetails {
message, message,
vec![arg.source_range], source_ranges: vec![arg.source_range],
)) })
})?; })?;
} }
} }
@ -683,8 +673,8 @@ fn type_check_params_kw(
exec_state, exec_state,
) )
.map_err(|_| { .map_err(|_| {
KclError::Semantic(KclErrorDetails::new( KclError::Semantic(KclErrorDetails {
format!( message: format!(
"The input argument of {} requires a value with type `{}`, but found {}", "The input argument of {} requires a value with type `{}`, but found {}",
fn_name fn_name
.map(|n| format!("`{}`", n)) .map(|n| format!("`{}`", n))
@ -692,8 +682,8 @@ fn type_check_params_kw(
ty, ty,
arg.1.value.human_friendly_type() arg.1.value.human_friendly_type()
), ),
vec![arg.1.source_range], source_ranges: vec![arg.1.source_range],
)) })
})?; })?;
} }
} else if let Some((name, _)) = &fn_def.input_arg { } else if let Some((name, _)) = &fn_def.input_arg {
@ -740,13 +730,13 @@ fn assign_args_to_params_kw(
.add(name.clone(), value, default_val.source_range())?; .add(name.clone(), value, default_val.source_range())?;
} }
None => { None => {
return Err(KclError::Semantic(KclErrorDetails::new( return Err(KclError::Semantic(KclErrorDetails {
format!( source_ranges,
message: format!(
"This function requires a parameter {}, but you haven't passed it one.", "This function requires a parameter {}, but you haven't passed it one.",
name name
), ),
source_ranges, }));
)));
} }
}, },
} }
@ -757,15 +747,16 @@ fn assign_args_to_params_kw(
let Some(unlabeled) = unlabelled else { let Some(unlabeled) = unlabelled else {
return Err(if args.kw_args.labeled.contains_key(param_name) { return Err(if args.kw_args.labeled.contains_key(param_name) {
KclError::Semantic(KclErrorDetails::new( KclError::Semantic(KclErrorDetails {
format!("The function does declare a parameter named '{param_name}', but this parameter doesn't use a label. Try removing the `{param_name}:`"),
source_ranges, source_ranges,
)) message: format!("The function does declare a parameter named '{param_name}', but this parameter doesn't use a label. Try removing the `{param_name}:`"),
})
} else { } else {
KclError::Semantic(KclErrorDetails::new( KclError::Semantic(KclErrorDetails {
"This function expects an unlabeled first parameter, but you haven't passed it one.".to_owned(),
source_ranges, source_ranges,
)) message: "This function expects an unlabeled first parameter, but you haven't passed it one."
.to_owned(),
})
}); });
}; };
exec_state.mut_stack().add( exec_state.mut_stack().add(
@ -798,14 +789,14 @@ fn coerce_result_type(
ty = RuntimeType::Union(vec![(**inner).clone(), ty]); ty = RuntimeType::Union(vec![(**inner).clone(), ty]);
} }
let val = val.coerce(&ty, exec_state).map_err(|_| { let val = val.coerce(&ty, exec_state).map_err(|_| {
KclError::Semantic(KclErrorDetails::new( KclError::Semantic(KclErrorDetails {
format!( message: format!(
"This function requires its result to be of type `{}`, but found {}", "This function requires its result to be of type `{}`, but found {}",
ty.human_friendly_type(), ty.human_friendly_type(),
val.human_friendly_type(), val.human_friendly_type(),
), ),
ret_ty.as_source_ranges(), source_ranges: ret_ty.as_source_ranges(),
)) })
})?; })?;
Ok(Some(val)) Ok(Some(val))
} else { } else {
@ -882,10 +873,10 @@ mod test {
"all params required, none given, should error", "all params required, none given, should error",
vec![req_param("x")], vec![req_param("x")],
vec![], vec![],
Err(KclError::Semantic(KclErrorDetails::new( Err(KclError::Semantic(KclErrorDetails {
"This function requires a parameter x, but you haven't passed it one.".to_owned(), source_ranges: vec![SourceRange::default()],
vec![SourceRange::default()], message: "This function requires a parameter x, but you haven't passed it one.".to_owned(),
))), })),
), ),
( (
"all params optional, none given, should be OK", "all params optional, none given, should be OK",
@ -897,10 +888,10 @@ mod test {
"mixed params, too few given", "mixed params, too few given",
vec![req_param("x"), opt_param("y")], vec![req_param("x"), opt_param("y")],
vec![], vec![],
Err(KclError::Semantic(KclErrorDetails::new( Err(KclError::Semantic(KclErrorDetails {
"This function requires a parameter x, but you haven't passed it one.".to_owned(), source_ranges: vec![SourceRange::default()],
vec![SourceRange::default()], message: "This function requires a parameter x, but you haven't passed it one.".to_owned(),
))), })),
), ),
( (
"mixed params, minimum given, should be OK", "mixed params, minimum given, should be OK",

View File

@ -469,18 +469,18 @@ impl TryFrom<PlaneData> for PlaneInfo {
PlaneData::NegYZ => PlaneName::NegYz, PlaneData::NegYZ => PlaneName::NegYz,
PlaneData::Plane(_) => { PlaneData::Plane(_) => {
// We will never get here since we already checked for PlaneData::Plane. // We will never get here since we already checked for PlaneData::Plane.
return Err(KclError::Internal(KclErrorDetails::new( return Err(KclError::Internal(KclErrorDetails {
format!("PlaneData {:?} not found", value), message: format!("PlaneData {:?} not found", value),
Default::default(), source_ranges: Default::default(),
))); }));
} }
}; };
let info = DEFAULT_PLANE_INFO.get(&name).ok_or_else(|| { let info = DEFAULT_PLANE_INFO.get(&name).ok_or_else(|| {
KclError::Internal(KclErrorDetails::new( KclError::Internal(KclErrorDetails {
format!("Plane {} not found", name), message: format!("Plane {} not found", name),
Default::default(), source_ranges: Default::default(),
)) })
})?; })?;
Ok(info.clone()) Ok(info.clone())

View File

@ -37,43 +37,53 @@ pub async fn import_foreign(
) -> Result<PreImportedGeometry, KclError> { ) -> Result<PreImportedGeometry, KclError> {
// Make sure the file exists. // Make sure the file exists.
if !ctxt.fs.exists(file_path, source_range).await? { if !ctxt.fs.exists(file_path, source_range).await? {
return Err(KclError::Semantic(KclErrorDetails::new( return Err(KclError::Semantic(KclErrorDetails {
format!("File `{}` does not exist.", file_path.display()), message: format!("File `{}` does not exist.", file_path.display()),
vec![source_range], source_ranges: vec![source_range],
))); }));
} }
let ext_format = get_import_format_from_extension(file_path.extension().ok_or_else(|| { let ext_format = get_import_format_from_extension(file_path.extension().ok_or_else(|| {
KclError::Semantic(KclErrorDetails::new( KclError::Semantic(KclErrorDetails {
format!("No file extension found for `{}`", file_path.display()), message: format!("No file extension found for `{}`", file_path.display()),
vec![source_range], source_ranges: vec![source_range],
)) })
})?) })?)
.map_err(|e| KclError::Semantic(KclErrorDetails::new(e.to_string(), vec![source_range])))?; .map_err(|e| {
KclError::Semantic(KclErrorDetails {
message: e.to_string(),
source_ranges: vec![source_range],
})
})?;
// Get the format type from the extension of the file. // Get the format type from the extension of the file.
let format = if let Some(format) = format { let format = if let Some(format) = format {
// Validate the given format with the extension format. // Validate the given format with the extension format.
validate_extension_format(ext_format, format.clone()) validate_extension_format(ext_format, format.clone()).map_err(|e| {
.map_err(|e| KclError::Semantic(KclErrorDetails::new(e.to_string(), vec![source_range])))?; KclError::Semantic(KclErrorDetails {
message: e.to_string(),
source_ranges: vec![source_range],
})
})?;
format format
} else { } else {
ext_format ext_format
}; };
// Get the file contents for each file path. // Get the file contents for each file path.
let file_contents = ctxt let file_contents = ctxt.fs.read(file_path, source_range).await.map_err(|e| {
.fs KclError::Semantic(KclErrorDetails {
.read(file_path, source_range) message: e.to_string(),
.await source_ranges: vec![source_range],
.map_err(|e| KclError::Semantic(KclErrorDetails::new(e.to_string(), vec![source_range])))?; })
})?;
// We want the file_path to be without the parent. // We want the file_path to be without the parent.
let file_name = file_path.file_name().map(|p| p.to_string()).ok_or_else(|| { let file_name = file_path.file_name().map(|p| p.to_string()).ok_or_else(|| {
KclError::Semantic(KclErrorDetails::new( KclError::Semantic(KclErrorDetails {
format!("Could not get the file name from the path `{}`", file_path.display()), message: format!("Could not get the file name from the path `{}`", file_path.display()),
vec![source_range], source_ranges: vec![source_range],
)) })
})?; })?;
let mut import_files = vec![kcmc::ImportFile { let mut import_files = vec![kcmc::ImportFile {
path: file_name.to_string(), path: file_name.to_string(),
@ -86,8 +96,12 @@ pub async fn import_foreign(
// Check if the file is a binary gltf file, in that case we don't need to import the bin // Check if the file is a binary gltf file, in that case we don't need to import the bin
// file. // file.
if !file_contents.starts_with(b"glTF") { if !file_contents.starts_with(b"glTF") {
let json = gltf_json::Root::from_slice(&file_contents) let json = gltf_json::Root::from_slice(&file_contents).map_err(|e| {
.map_err(|e| KclError::Semantic(KclErrorDetails::new(e.to_string(), vec![source_range])))?; KclError::Semantic(KclErrorDetails {
message: e.to_string(),
source_ranges: vec![source_range],
})
})?;
// Read the gltf file and check if there is a bin file. // Read the gltf file and check if there is a bin file.
for buffer in json.buffers.iter() { for buffer in json.buffers.iter() {
@ -95,16 +109,18 @@ pub async fn import_foreign(
if !uri.starts_with("data:") { if !uri.starts_with("data:") {
// We want this path relative to the file_path given. // We want this path relative to the file_path given.
let bin_path = file_path.parent().map(|p| p.join(uri)).ok_or_else(|| { let bin_path = file_path.parent().map(|p| p.join(uri)).ok_or_else(|| {
KclError::Semantic(KclErrorDetails::new( KclError::Semantic(KclErrorDetails {
format!("Could not get the parent path of the file `{}`", file_path.display()), message: format!("Could not get the parent path of the file `{}`", file_path.display()),
vec![source_range], source_ranges: vec![source_range],
)) })
})?; })?;
let bin_contents = let bin_contents = ctxt.fs.read(&bin_path, source_range).await.map_err(|e| {
ctxt.fs.read(&bin_path, source_range).await.map_err(|e| { KclError::Semantic(KclErrorDetails {
KclError::Semantic(KclErrorDetails::new(e.to_string(), vec![source_range])) message: e.to_string(),
})?; source_ranges: vec![source_range],
})
})?;
import_files.push(ImportFile { import_files.push(ImportFile {
path: uri.to_string(), path: uri.to_string(),
@ -141,13 +157,13 @@ pub(super) fn format_from_annotations(
if p.key.name == annotations::IMPORT_FORMAT { if p.key.name == annotations::IMPORT_FORMAT {
result = Some( result = Some(
get_import_format_from_extension(annotations::expect_ident(&p.value)?).map_err(|_| { get_import_format_from_extension(annotations::expect_ident(&p.value)?).map_err(|_| {
KclError::Semantic(KclErrorDetails::new( KclError::Semantic(KclErrorDetails {
format!( message: format!(
"Unknown format for import, expected one of: {}", "Unknown format for import, expected one of: {}",
crate::IMPORT_FILE_EXTENSIONS.join(", ") crate::IMPORT_FILE_EXTENSIONS.join(", ")
), ),
vec![p.as_source_range()], source_ranges: vec![p.as_source_range()],
)) })
})?, })?,
); );
break; break;
@ -159,10 +175,10 @@ pub(super) fn format_from_annotations(
path.extension() path.extension()
.and_then(|ext| get_import_format_from_extension(ext).ok()) .and_then(|ext| get_import_format_from_extension(ext).ok())
}) })
.ok_or(KclError::Semantic(KclErrorDetails::new( .ok_or(KclError::Semantic(KclErrorDetails {
"Unknown or missing extension, and no specified format for imported file".to_owned(), message: "Unknown or missing extension, and no specified format for imported file".to_owned(),
vec![import_source_range], source_ranges: vec![import_source_range],
)))?; }))?;
for p in props { for p in props {
match p.key.name.as_str() { match p.key.name.as_str() {
@ -174,15 +190,15 @@ pub(super) fn format_from_annotations(
} }
annotations::IMPORT_FORMAT => {} annotations::IMPORT_FORMAT => {}
_ => { _ => {
return Err(KclError::Semantic(KclErrorDetails::new( return Err(KclError::Semantic(KclErrorDetails {
format!( message: format!(
"Unexpected annotation for import, expected one of: {}, {}, {}", "Unexpected annotation for import, expected one of: {}, {}, {}",
annotations::IMPORT_FORMAT, annotations::IMPORT_FORMAT,
annotations::IMPORT_COORDS, annotations::IMPORT_COORDS,
annotations::IMPORT_LENGTH_UNIT annotations::IMPORT_LENGTH_UNIT
), ),
vec![p.as_source_range()], source_ranges: vec![p.as_source_range()],
))) }))
} }
} }
} }
@ -199,8 +215,8 @@ fn set_coords(fmt: &mut InputFormat3d, coords_str: &str, source_range: SourceRan
} }
let Some(coords) = coords else { let Some(coords) = coords else {
return Err(KclError::Semantic(KclErrorDetails::new( return Err(KclError::Semantic(KclErrorDetails {
format!( message: format!(
"Unknown coordinate system: {coords_str}, expected one of: {}", "Unknown coordinate system: {coords_str}, expected one of: {}",
annotations::IMPORT_COORDS_VALUES annotations::IMPORT_COORDS_VALUES
.iter() .iter()
@ -208,8 +224,8 @@ fn set_coords(fmt: &mut InputFormat3d, coords_str: &str, source_range: SourceRan
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(", ") .join(", ")
), ),
vec![source_range], source_ranges: vec![source_range],
))); }));
}; };
match fmt { match fmt {
@ -217,13 +233,13 @@ fn set_coords(fmt: &mut InputFormat3d, coords_str: &str, source_range: SourceRan
InputFormat3d::Ply(opts) => opts.coords = coords, InputFormat3d::Ply(opts) => opts.coords = coords,
InputFormat3d::Stl(opts) => opts.coords = coords, InputFormat3d::Stl(opts) => opts.coords = coords,
_ => { _ => {
return Err(KclError::Semantic(KclErrorDetails::new( return Err(KclError::Semantic(KclErrorDetails {
format!( message: format!(
"`{}` option cannot be applied to the specified format", "`{}` option cannot be applied to the specified format",
annotations::IMPORT_COORDS annotations::IMPORT_COORDS
), ),
vec![source_range], source_ranges: vec![source_range],
))) }))
} }
} }
@ -238,13 +254,13 @@ fn set_length_unit(fmt: &mut InputFormat3d, units_str: &str, source_range: Sourc
InputFormat3d::Ply(opts) => opts.units = units.into(), InputFormat3d::Ply(opts) => opts.units = units.into(),
InputFormat3d::Stl(opts) => opts.units = units.into(), InputFormat3d::Stl(opts) => opts.units = units.into(),
_ => { _ => {
return Err(KclError::Semantic(KclErrorDetails::new( return Err(KclError::Semantic(KclErrorDetails {
format!( message: format!(
"`{}` option cannot be applied to the specified format", "`{}` option cannot be applied to the specified format",
annotations::IMPORT_LENGTH_UNIT annotations::IMPORT_LENGTH_UNIT
), ),
vec![source_range], source_ranges: vec![source_range],
))) }))
} }
} }

View File

@ -543,13 +543,17 @@ impl KclValue {
/// If this value fits in a u32, return it. /// If this value fits in a u32, return it.
pub fn get_u32(&self, source_ranges: Vec<SourceRange>) -> Result<u32, KclError> { pub fn get_u32(&self, source_ranges: Vec<SourceRange>) -> Result<u32, KclError> {
let u = self.as_int().and_then(|n| u64::try_from(n).ok()).ok_or_else(|| { let u = self.as_int().and_then(|n| u64::try_from(n).ok()).ok_or_else(|| {
KclError::Semantic(KclErrorDetails::new( KclError::Semantic(KclErrorDetails {
"Expected an integer >= 0".to_owned(), message: "Expected an integer >= 0".to_owned(),
source_ranges.clone(), source_ranges: source_ranges.clone(),
)) })
})?; })?;
u32::try_from(u) u32::try_from(u).map_err(|_| {
.map_err(|_| KclError::Semantic(KclErrorDetails::new("Number was too big".to_owned(), source_ranges))) KclError::Semantic(KclErrorDetails {
message: "Number was too big".to_owned(),
source_ranges,
})
})
} }
/// If this value is of type function, return it. /// If this value is of type function, return it.
@ -564,10 +568,10 @@ impl KclValue {
pub fn get_tag_identifier(&self) -> Result<TagIdentifier, KclError> { pub fn get_tag_identifier(&self) -> Result<TagIdentifier, KclError> {
match self { match self {
KclValue::TagIdentifier(t) => Ok(*t.clone()), KclValue::TagIdentifier(t) => Ok(*t.clone()),
_ => Err(KclError::Semantic(KclErrorDetails::new( _ => Err(KclError::Semantic(KclErrorDetails {
format!("Not a tag identifier: {:?}", self), message: format!("Not a tag identifier: {:?}", self),
self.clone().into(), source_ranges: self.clone().into(),
))), })),
} }
} }
@ -575,20 +579,20 @@ impl KclValue {
pub fn get_tag_declarator(&self) -> Result<TagNode, KclError> { pub fn get_tag_declarator(&self) -> Result<TagNode, KclError> {
match self { match self {
KclValue::TagDeclarator(t) => Ok((**t).clone()), KclValue::TagDeclarator(t) => Ok((**t).clone()),
_ => Err(KclError::Semantic(KclErrorDetails::new( _ => Err(KclError::Semantic(KclErrorDetails {
format!("Not a tag declarator: {:?}", self), message: format!("Not a tag declarator: {:?}", self),
self.clone().into(), source_ranges: self.clone().into(),
))), })),
} }
} }
/// If this KCL value is a bool, retrieve it. /// If this KCL value is a bool, retrieve it.
pub fn get_bool(&self) -> Result<bool, KclError> { pub fn get_bool(&self) -> Result<bool, KclError> {
let Self::Bool { value: b, .. } = self else { let Self::Bool { value: b, .. } = self else {
return Err(KclError::Type(KclErrorDetails::new( return Err(KclError::Type(KclErrorDetails {
format!("Expected bool, found {}", self.human_friendly_type()), source_ranges: self.into(),
self.into(), message: format!("Expected bool, found {}", self.human_friendly_type()),
))); }));
}; };
Ok(*b) Ok(*b)
} }

View File

@ -364,10 +364,10 @@ impl ProgramMemory {
}; };
} }
Err(KclError::UndefinedValue(KclErrorDetails::new( Err(KclError::UndefinedValue(KclErrorDetails {
format!("`{}` is not defined", var), message: format!("`{}` is not defined", var),
vec![source_range], source_ranges: vec![source_range],
))) }))
} }
/// Iterate over all key/value pairs in the specified environment which satisfy the provided /// Iterate over all key/value pairs in the specified environment which satisfy the provided
@ -485,10 +485,10 @@ impl ProgramMemory {
}; };
} }
Err(KclError::UndefinedValue(KclErrorDetails::new( Err(KclError::UndefinedValue(KclErrorDetails {
format!("`{}` is not defined", var), message: format!("`{}` is not defined", var),
vec![], source_ranges: vec![],
))) }))
} }
} }
@ -643,10 +643,10 @@ impl Stack {
pub fn add(&mut self, key: String, value: KclValue, source_range: SourceRange) -> Result<(), KclError> { pub fn add(&mut self, key: String, value: KclValue, source_range: SourceRange) -> Result<(), KclError> {
let env = self.memory.get_env(self.current_env.index()); let env = self.memory.get_env(self.current_env.index());
if env.contains_key(&key) { if env.contains_key(&key) {
return Err(KclError::ValueAlreadyDefined(KclErrorDetails::new( return Err(KclError::ValueAlreadyDefined(KclErrorDetails {
format!("Cannot redefine `{}`", key), message: format!("Cannot redefine `{}`", key),
vec![source_range], source_ranges: vec![source_range],
))); }));
} }
self.memory.stats.mutation_count.fetch_add(1, Ordering::Relaxed); self.memory.stats.mutation_count.fetch_add(1, Ordering::Relaxed);

View File

@ -858,9 +858,10 @@ impl ExecutorContext {
for module in modules { for module in modules {
let Some((import_stmt, module_id, module_path, repr)) = universe.get(&module) else { let Some((import_stmt, module_id, module_path, repr)) = universe.get(&module) else {
return Err(KclErrorWithOutputs::no_outputs(KclError::Internal( return Err(KclErrorWithOutputs::no_outputs(KclError::Internal(KclErrorDetails {
KclErrorDetails::new(format!("Module {module} not found in universe"), Default::default()), message: format!("Module {module} not found in universe"),
))); source_ranges: Default::default(),
})));
}; };
let module_id = *module_id; let module_id = *module_id;
let module_path = module_path.clone(); let module_path = module_path.clone();
@ -920,10 +921,10 @@ impl ExecutorContext {
result.map(|val| ModuleRepr::Foreign(geom.clone(), val)) result.map(|val| ModuleRepr::Foreign(geom.clone(), val))
} }
ModuleRepr::Dummy | ModuleRepr::Root => Err(KclError::Internal(KclErrorDetails::new( ModuleRepr::Dummy | ModuleRepr::Root => Err(KclError::Internal(KclErrorDetails {
format!("Module {module_path} not found in universe"), message: format!("Module {module_path} not found in universe"),
vec![source_range], source_ranges: vec![source_range],
))), })),
} }
}; };
@ -1287,10 +1288,10 @@ impl ExecutorContext {
.await?; .await?;
let kittycad_modeling_cmds::websocket::OkWebSocketResponseData::Export { files } = resp else { let kittycad_modeling_cmds::websocket::OkWebSocketResponseData::Export { files } = resp else {
return Err(KclError::Internal(crate::errors::KclErrorDetails::new( return Err(KclError::Internal(crate::errors::KclErrorDetails {
format!("Expected Export response, got {resp:?}",), message: format!("Expected Export response, got {resp:?}",),
vec![SourceRange::default()], source_ranges: vec![SourceRange::default()],
))); }));
}; };
Ok(files) Ok(files)
@ -1307,10 +1308,10 @@ impl ExecutorContext {
coords: *kittycad_modeling_cmds::coord::KITTYCAD, coords: *kittycad_modeling_cmds::coord::KITTYCAD,
created: if deterministic_time { created: if deterministic_time {
Some("2021-01-01T00:00:00Z".parse().map_err(|e| { Some("2021-01-01T00:00:00Z".parse().map_err(|e| {
KclError::Internal(crate::errors::KclErrorDetails::new( KclError::Internal(crate::errors::KclErrorDetails {
format!("Failed to parse date: {}", e), message: format!("Failed to parse date: {}", e),
vec![SourceRange::default()], source_ranges: vec![SourceRange::default()],
)) })
})?) })?)
} else { } else {
None None
@ -1387,10 +1388,10 @@ pub(crate) async fn parse_execute_with_project_dir(
let exec_ctxt = ExecutorContext { let exec_ctxt = ExecutorContext {
engine: Arc::new(Box::new( engine: Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new().await.map_err(|err| { crate::engine::conn_mock::EngineConnection::new().await.map_err(|err| {
KclError::Internal(crate::errors::KclErrorDetails::new( KclError::Internal(crate::errors::KclErrorDetails {
format!("Failed to create mock engine connection: {}", err), message: format!("Failed to create mock engine connection: {}", err),
vec![SourceRange::default()], source_ranges: vec![SourceRange::default()],
)) })
})?, })?,
)), )),
fs: Arc::new(crate::fs::FileManager::new()), fs: Arc::new(crate::fs::FileManager::new()),
@ -1803,10 +1804,10 @@ foo
let err = result.unwrap_err(); let err = result.unwrap_err();
assert_eq!( assert_eq!(
err, err,
KclError::Syntax(KclErrorDetails::new( KclError::Syntax(KclErrorDetails {
"Unexpected token: #".to_owned(), message: "Unexpected token: #".to_owned(),
vec![SourceRange::new(14, 15, ModuleId::default())], source_ranges: vec![SourceRange::new(14, 15, ModuleId::default())],
)), }),
); );
} }
@ -2062,10 +2063,10 @@ notTagIdentifier = !myTag";
// TODO: We don't currently parse this, but we should. It should be // TODO: We don't currently parse this, but we should. It should be
// a runtime error instead. // a runtime error instead.
parse_execute(code10).await.unwrap_err(), parse_execute(code10).await.unwrap_err(),
KclError::Syntax(KclErrorDetails::new( KclError::Syntax(KclErrorDetails {
"Unexpected token: !".to_owned(), message: "Unexpected token: !".to_owned(),
vec![SourceRange::new(10, 11, ModuleId::default())], source_ranges: vec![SourceRange::new(10, 11, ModuleId::default())],
)) })
); );
let code11 = " let code11 = "
@ -2075,10 +2076,10 @@ notPipeSub = 1 |> identity(!%))";
// TODO: We don't currently parse this, but we should. It should be // TODO: We don't currently parse this, but we should. It should be
// a runtime error instead. // a runtime error instead.
parse_execute(code11).await.unwrap_err(), parse_execute(code11).await.unwrap_err(),
KclError::Syntax(KclErrorDetails::new( KclError::Syntax(KclErrorDetails {
"Unexpected token: |>".to_owned(), message: "Unexpected token: |>".to_owned(),
vec![SourceRange::new(44, 46, ModuleId::default())], source_ranges: vec![SourceRange::new(44, 46, ModuleId::default())],
)) })
); );
// TODO: Add these tests when we support these types. // TODO: Add these tests when we support these types.

View File

@ -276,8 +276,8 @@ impl ExecState {
} }
pub(super) fn circular_import_error(&self, path: &ModulePath, source_range: SourceRange) -> KclError { pub(super) fn circular_import_error(&self, path: &ModulePath, source_range: SourceRange) -> KclError {
KclError::ImportCycle(KclErrorDetails::new( KclError::ImportCycle(KclErrorDetails {
format!( message: format!(
"circular import of modules is not allowed: {} -> {}", "circular import of modules is not allowed: {} -> {}",
self.global self.global
.mod_loader .mod_loader
@ -288,8 +288,8 @@ impl ExecState {
.join(" -> "), .join(" -> "),
path, path,
), ),
vec![source_range], source_ranges: vec![source_range],
)) })
} }
pub(crate) fn pipe_value(&self) -> Option<&KclValue> { pub(crate) fn pipe_value(&self) -> Option<&KclValue> {
@ -389,14 +389,14 @@ impl MetaSettings {
self.kcl_version = value; self.kcl_version = value;
} }
name => { name => {
return Err(KclError::Semantic(KclErrorDetails::new( return Err(KclError::Semantic(KclErrorDetails {
format!( message: format!(
"Unexpected settings key: `{name}`; expected one of `{}`, `{}`", "Unexpected settings key: `{name}`; expected one of `{}`, `{}`",
annotations::SETTINGS_UNIT_LENGTH, annotations::SETTINGS_UNIT_LENGTH,
annotations::SETTINGS_UNIT_ANGLE annotations::SETTINGS_UNIT_ANGLE
), ),
vec![annotation.as_source_range()], source_ranges: vec![annotation.as_source_range()],
))) }))
} }
} }
} }

View File

@ -35,28 +35,31 @@ impl Default for TypedPath {
impl From<&String> for TypedPath { impl From<&String> for TypedPath {
fn from(path: &String) -> Self { fn from(path: &String) -> Self {
TypedPath::new(path)
}
}
impl From<&str> for TypedPath {
fn from(path: &str) -> Self {
TypedPath::new(path)
}
}
impl TypedPath {
pub fn new(path: &str) -> Self {
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
{ {
TypedPath(typed_path::TypedPath::derive(path).to_path_buf()) TypedPath(typed_path::TypedPath::derive(path).to_path_buf())
} }
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
{ {
TypedPath(normalise_import(path)) TypedPath(std::path::PathBuf::from(path))
} }
} }
}
impl From<&str> for TypedPath {
fn from(path: &str) -> Self {
#[cfg(target_arch = "wasm32")]
{
TypedPath(typed_path::TypedPath::derive(path).to_path_buf())
}
#[cfg(not(target_arch = "wasm32"))]
{
TypedPath(std::path::PathBuf::from(path))
}
}
}
impl TypedPath {
pub fn extension(&self) -> Option<&str> { pub fn extension(&self) -> Option<&str> {
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
{ {
@ -82,17 +85,6 @@ impl TypedPath {
} }
} }
pub fn join_typed(&self, path: &TypedPath) -> Self {
#[cfg(target_arch = "wasm32")]
{
TypedPath(self.0.join(path.0.to_path()))
}
#[cfg(not(target_arch = "wasm32"))]
{
TypedPath(self.0.join(&path.0))
}
}
pub fn parent(&self) -> Option<Self> { pub fn parent(&self) -> Option<Self> {
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
{ {
@ -214,19 +206,3 @@ impl schemars::JsonSchema for TypedPath {
gen.subschema_for::<std::path::PathBuf>() gen.subschema_for::<std::path::PathBuf>()
} }
} }
/// Turn `nested\foo\bar\main.kcl` or `nested/foo/bar/main.kcl`
/// into a PathBuf that works on the host OS.
///
/// * Does **not** touch `..` or symlinks call `canonicalize()` if you need that.
/// * Returns an owned `PathBuf` only when normalisation was required.
fn normalise_import<S: AsRef<str>>(raw: S) -> std::path::PathBuf {
let s = raw.as_ref();
// On Unix we need to swap `\` → `/`. On Windows we leave it alone.
// (Windows happily consumes `/`)
if cfg!(unix) && s.contains('\\') {
std::path::PathBuf::from(s.replace('\\', "/"))
} else {
std::path::Path::new(s).to_path_buf()
}
}

View File

@ -155,8 +155,9 @@ impl RuntimeType {
.map(RuntimeType::Union), .map(RuntimeType::Union),
Type::Object { properties } => properties Type::Object { properties } => properties
.into_iter() .into_iter()
.map(|(id, ty)| { .map(|p| {
RuntimeType::from_parsed(ty.inner, exec_state, source_range).map(|ty| (id.name.clone(), ty)) RuntimeType::from_parsed(p.type_.unwrap().inner, exec_state, source_range)
.map(|ty| (p.identifier.inner.name, ty))
}) })
.collect::<Result<Vec<_>, CompilationError>>() .collect::<Result<Vec<_>, CompilationError>>()
.map(RuntimeType::Object), .map(RuntimeType::Object),

View File

@ -28,19 +28,19 @@ impl Default for FileManager {
impl FileSystem for FileManager { impl FileSystem for FileManager {
async fn read(&self, path: &TypedPath, source_range: SourceRange) -> Result<Vec<u8>, KclError> { async fn read(&self, path: &TypedPath, source_range: SourceRange) -> Result<Vec<u8>, KclError> {
tokio::fs::read(&path.0).await.map_err(|e| { tokio::fs::read(&path.0).await.map_err(|e| {
KclError::Io(KclErrorDetails::new( KclError::Io(KclErrorDetails {
format!("Failed to read file `{}`: {}", path.display(), e), message: format!("Failed to read file `{}`: {}", path.display(), e),
vec![source_range], source_ranges: vec![source_range],
)) })
}) })
} }
async fn read_to_string(&self, path: &TypedPath, source_range: SourceRange) -> Result<String, KclError> { async fn read_to_string(&self, path: &TypedPath, source_range: SourceRange) -> Result<String, KclError> {
tokio::fs::read_to_string(&path.0).await.map_err(|e| { tokio::fs::read_to_string(&path.0).await.map_err(|e| {
KclError::Io(KclErrorDetails::new( KclError::Io(KclErrorDetails {
format!("Failed to read file `{}`: {}", path.display(), e), message: format!("Failed to read file `{}`: {}", path.display(), e),
vec![source_range], source_ranges: vec![source_range],
)) })
}) })
} }
@ -49,10 +49,10 @@ impl FileSystem for FileManager {
if e.kind() == std::io::ErrorKind::NotFound { if e.kind() == std::io::ErrorKind::NotFound {
Ok(false) Ok(false)
} else { } else {
Err(KclError::Io(KclErrorDetails::new( Err(KclError::Io(KclErrorDetails {
format!("Failed to check if file `{}` exists: {}", path.display(), e), message: format!("Failed to check if file `{}` exists: {}", path.display(), e),
vec![source_range], source_ranges: vec![source_range],
))) }))
} }
}) })
} }
@ -71,10 +71,10 @@ impl FileSystem for FileManager {
} }
let mut read_dir = tokio::fs::read_dir(&path).await.map_err(|e| { let mut read_dir = tokio::fs::read_dir(&path).await.map_err(|e| {
KclError::Io(KclErrorDetails::new( KclError::Io(KclErrorDetails {
format!("Failed to read directory `{}`: {}", path.display(), e), message: format!("Failed to read directory `{}`: {}", path.display(), e),
vec![source_range], source_ranges: vec![source_range],
)) })
})?; })?;
while let Ok(Some(entry)) = read_dir.next_entry().await { while let Ok(Some(entry)) = read_dir.next_entry().await {

View File

@ -46,16 +46,18 @@ unsafe impl Sync for FileManager {}
#[async_trait::async_trait] #[async_trait::async_trait]
impl FileSystem for FileManager { impl FileSystem for FileManager {
async fn read(&self, path: &TypedPath, source_range: SourceRange) -> Result<Vec<u8>, KclError> { async fn read(&self, path: &TypedPath, source_range: SourceRange) -> Result<Vec<u8>, KclError> {
let promise = self let promise = self.manager.read_file(path.to_string_lossy()).map_err(|e| {
.manager KclError::Engine(KclErrorDetails {
.read_file(path.to_string_lossy()) message: e.to_string().into(),
.map_err(|e| KclError::Engine(KclErrorDetails::new(e.to_string().into(), vec![source_range])))?; source_ranges: vec![source_range],
})
})?;
let value = JsFuture::from(promise).await.map_err(|e| { let value = JsFuture::from(promise).await.map_err(|e| {
KclError::Engine(KclErrorDetails::new( KclError::Engine(KclErrorDetails {
format!("Failed to wait for promise from engine: {:?}", e), message: format!("Failed to wait for promise from engine: {:?}", e),
vec![source_range], source_ranges: vec![source_range],
)) })
})?; })?;
let array = js_sys::Uint8Array::new(&value); let array = js_sys::Uint8Array::new(&value);
@ -67,33 +69,35 @@ impl FileSystem for FileManager {
async fn read_to_string(&self, path: &TypedPath, source_range: SourceRange) -> Result<String, KclError> { async fn read_to_string(&self, path: &TypedPath, source_range: SourceRange) -> Result<String, KclError> {
let bytes = self.read(path, source_range).await?; let bytes = self.read(path, source_range).await?;
let string = String::from_utf8(bytes).map_err(|e| { let string = String::from_utf8(bytes).map_err(|e| {
KclError::Engine(KclErrorDetails::new( KclError::Engine(KclErrorDetails {
format!("Failed to convert bytes to string: {:?}", e), message: format!("Failed to convert bytes to string: {:?}", e),
vec![source_range], source_ranges: vec![source_range],
)) })
})?; })?;
Ok(string) Ok(string)
} }
async fn exists(&self, path: &TypedPath, source_range: SourceRange) -> Result<bool, crate::errors::KclError> { async fn exists(&self, path: &TypedPath, source_range: SourceRange) -> Result<bool, crate::errors::KclError> {
let promise = self let promise = self.manager.exists(path.to_string_lossy()).map_err(|e| {
.manager KclError::Engine(KclErrorDetails {
.exists(path.to_string_lossy()) message: e.to_string().into(),
.map_err(|e| KclError::Engine(KclErrorDetails::new(e.to_string().into(), vec![source_range])))?; source_ranges: vec![source_range],
})
})?;
let value = JsFuture::from(promise).await.map_err(|e| { let value = JsFuture::from(promise).await.map_err(|e| {
KclError::Engine(KclErrorDetails::new( KclError::Engine(KclErrorDetails {
format!("Failed to wait for promise from engine: {:?}", e), message: format!("Failed to wait for promise from engine: {:?}", e),
vec![source_range], source_ranges: vec![source_range],
)) })
})?; })?;
let it_exists = value.as_bool().ok_or_else(|| { let it_exists = value.as_bool().ok_or_else(|| {
KclError::Engine(KclErrorDetails::new( KclError::Engine(KclErrorDetails {
"Failed to convert value to bool".to_string(), message: "Failed to convert value to bool".to_string(),
vec![source_range], source_ranges: vec![source_range],
)) })
})?; })?;
Ok(it_exists) Ok(it_exists)
@ -104,30 +108,32 @@ impl FileSystem for FileManager {
path: &TypedPath, path: &TypedPath,
source_range: SourceRange, source_range: SourceRange,
) -> Result<Vec<TypedPath>, crate::errors::KclError> { ) -> Result<Vec<TypedPath>, crate::errors::KclError> {
let promise = self let promise = self.manager.get_all_files(path.to_string_lossy()).map_err(|e| {
.manager KclError::Engine(KclErrorDetails {
.get_all_files(path.to_string_lossy()) message: e.to_string().into(),
.map_err(|e| KclError::Engine(KclErrorDetails::new(e.to_string().into(), vec![source_range])))?; source_ranges: vec![source_range],
})
})?;
let value = JsFuture::from(promise).await.map_err(|e| { let value = JsFuture::from(promise).await.map_err(|e| {
KclError::Engine(KclErrorDetails::new( KclError::Engine(KclErrorDetails {
format!("Failed to wait for promise from javascript: {:?}", e), message: format!("Failed to wait for promise from javascript: {:?}", e),
vec![source_range], source_ranges: vec![source_range],
)) })
})?; })?;
let s = value.as_string().ok_or_else(|| { let s = value.as_string().ok_or_else(|| {
KclError::Engine(KclErrorDetails::new( KclError::Engine(KclErrorDetails {
format!("Failed to get string from response from javascript: `{:?}`", value), message: format!("Failed to get string from response from javascript: `{:?}`", value),
vec![source_range], source_ranges: vec![source_range],
)) })
})?; })?;
let files: Vec<String> = serde_json::from_str(&s).map_err(|e| { let files: Vec<String> = serde_json::from_str(&s).map_err(|e| {
KclError::Engine(KclErrorDetails::new( KclError::Engine(KclErrorDetails {
format!("Failed to parse json from javascript: `{}` `{:?}`", s, e), message: format!("Failed to parse json from javascript: `{}` `{:?}`", s, e),
vec![source_range], source_ranges: vec![source_range],
)) })
})?; })?;
Ok(files.into_iter().map(|s| TypedPath::from(&s)).collect()) Ok(files.into_iter().map(|s| TypedPath::from(&s)).collect())

View File

@ -86,8 +86,7 @@ mod wasm;
pub use coredump::CoreDump; pub use coredump::CoreDump;
pub use engine::{AsyncTasks, EngineManager, EngineStats}; pub use engine::{AsyncTasks, EngineManager, EngineStats};
pub use errors::{ pub use errors::{
BacktraceItem, CompilationError, ConnectionError, ExecError, KclError, KclErrorWithOutputs, Report, CompilationError, ConnectionError, ExecError, KclError, KclErrorWithOutputs, Report, ReportWithOutputs,
ReportWithOutputs,
}; };
pub use execution::{ pub use execution::{
bust_cache, clear_mem_cache, bust_cache, clear_mem_cache,

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