Compare commits
1 Commits
pierremtb/
...
codex/fix-
Author | SHA1 | Date | |
---|---|---|---|
9b901dfe51 |
4
.github/workflows/cargo-test.yml
vendored
@ -88,7 +88,6 @@ jobs:
|
||||
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}}
|
||||
ZOO_HOST: https://api.dev.zoo.dev
|
||||
RUST_BACKTRACE: full
|
||||
RUST_MIN_STACK: 10485760000
|
||||
- name: Commit differences
|
||||
if: steps.path-changes.outputs.outside-kcl-samples == 'false' && steps.cargo-test-kcl-samples.outcome == 'failure'
|
||||
shell: bash
|
||||
@ -120,7 +119,6 @@ jobs:
|
||||
# Configure nextest when it's run by insta (via just).
|
||||
NEXTEST_PROFILE: ci
|
||||
RUST_BACKTRACE: full
|
||||
RUST_MIN_STACK: 10485760000
|
||||
- name: Build and archive tests
|
||||
run: |
|
||||
cd rust
|
||||
@ -184,7 +182,6 @@ jobs:
|
||||
env:
|
||||
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}}
|
||||
ZOO_HOST: https://api.dev.zoo.dev
|
||||
RUST_MIN_STACK: 10485760000
|
||||
- name: Upload results
|
||||
if: always()
|
||||
run: .github/ci-cd-scripts/upload-results.sh
|
||||
@ -241,7 +238,6 @@ jobs:
|
||||
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}}
|
||||
ZOO_HOST: https://api.dev.zoo.dev
|
||||
MODELING_APP_INTERNAL_SAMPLES_SECRET: ${{secrets.MODELING_APP_INTERNAL_SAMPLES_SECRET}}
|
||||
RUST_MIN_STACK: 10485760000
|
||||
run-wasm-tests:
|
||||
name: Run wasm tests
|
||||
strategy:
|
||||
|
15
.github/workflows/e2e-tests.yml
vendored
@ -143,7 +143,7 @@ jobs:
|
||||
- name: Install browsers
|
||||
run: npm run playwright install --with-deps
|
||||
|
||||
- name: Test snapshots
|
||||
- name: Capture snapshots
|
||||
uses: nick-fields/retry@v3.0.2
|
||||
with:
|
||||
shell: bash
|
||||
@ -158,17 +158,6 @@ jobs:
|
||||
CI_PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
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 }}
|
||||
TARGET: web
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ !cancelled() && (success() || failure()) }}
|
||||
with:
|
||||
@ -184,7 +173,7 @@ jobs:
|
||||
id: git-check
|
||||
run: |
|
||||
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
|
||||
else echo "modified=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
@ -42,6 +42,8 @@ The 3D view in Design Studio is just a video stream from our hosted geometry eng
|
||||
|
||||
We recommend downloading the latest application binary from our [releases](https://github.com/KittyCAD/modeling-app/releases) page. If you don't see your platform or architecture supported there, please file an issue.
|
||||
|
||||
If you'd like to try out upcoming changes sooner, you can also download those from our [nightly releases](https://zoo.dev/modeling-app/download/nightly) page.
|
||||
|
||||
## Developing
|
||||
|
||||
Finally, if you'd like to run a development build or contribute to the project, please visit our [contributor guide](CONTRIBUTING.md) to get started.
|
||||
|
@ -4,7 +4,7 @@ excerpt: "Documentation of the KCL language for the Zoo Design Studio."
|
||||
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).
|
||||
|
||||
## Topics
|
||||
|
@ -27,6 +27,9 @@ import increment from "util.kcl"
|
||||
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
|
||||
an `import` statement inside a function or in the body of an if‑else.
|
||||
|
||||
@ -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"
|
||||
```
|
||||
|
||||
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`
|
||||
@ -177,7 +177,7 @@ You can also import the whole module. This is useful if you want to use the
|
||||
result of a module as a variable, like a part.
|
||||
|
||||
```norun
|
||||
import "cube.kcl"
|
||||
import "tests/inputs/cube.kcl" as cube
|
||||
cube
|
||||
|> translate(x=10)
|
||||
```
|
||||
@ -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
|
||||
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
|
||||
@ -254,7 +241,7 @@ If you want to have multiple instances of the same object, you can use the
|
||||
[`clone`](/docs/kcl/clone) function. This will render a new instance of the object in memory.
|
||||
|
||||
```norun
|
||||
import cube from "cube.kcl"
|
||||
import cube from "tests/inputs/cube.kcl"
|
||||
|
||||
cube
|
||||
|> translate(x=10)
|
||||
@ -270,7 +257,7 @@ separate objects in memory, and can be manipulated independently.
|
||||
Here is an example with a file from another CAD system:
|
||||
|
||||
```kcl
|
||||
import "tests/inputs/cube.step"
|
||||
import "tests/inputs/cube.step" as cube
|
||||
|
||||
cube
|
||||
|> translate(x=10)
|
||||
|
@ -12,7 +12,7 @@ reduce(
|
||||
@array: [any],
|
||||
initial: any,
|
||||
f: fn(any, accum: any): any,
|
||||
): any
|
||||
): [any]
|
||||
```
|
||||
|
||||
Take a starting value. Then, for each element of an array, calculate the next value,
|
||||
@ -28,7 +28,7 @@ using the previous value and the element.
|
||||
|
||||
### Returns
|
||||
|
||||
[`any`](/docs/kcl-std/types/std-types-any)
|
||||
[`[any]`](/docs/kcl-std/types/std-types-any)
|
||||
|
||||
|
||||
### Examples
|
||||
|
@ -11,7 +11,7 @@ Compute the length of the given leg.
|
||||
legLen(
|
||||
hypotenuse: number(Length),
|
||||
leg: number(Length),
|
||||
): number(Length)
|
||||
): number(deg)
|
||||
```
|
||||
|
||||
|
||||
@ -25,7 +25,7 @@ legLen(
|
||||
|
||||
### Returns
|
||||
|
||||
[`number(Length)`](/docs/kcl-std/types/std-types-number) - A number.
|
||||
[`number(deg)`](/docs/kcl-std/types/std-types-number) - A number.
|
||||
|
||||
|
||||
### Examples
|
||||
|
@ -14,6 +14,8 @@ mirror2d(
|
||||
): Sketch
|
||||
```
|
||||
|
||||
Only works on unclosed sketches for now.
|
||||
|
||||
Mirror occurs around a local sketch axis rather than a global axis.
|
||||
|
||||
### Arguments
|
||||
|
@ -65,7 +65,7 @@ layout: manual
|
||||
* [`line`](/docs/kcl-std/line)
|
||||
* [`loft`](/docs/kcl-std/loft)
|
||||
* [`patternCircular2d`](/docs/kcl-std/patternCircular2d)
|
||||
* [`patternTransform2d`](/docs/kcl-std/functions/std-sketch-patternTransform2d)
|
||||
* [`patternTransform2d`](/docs/kcl-std/patternTransform2d)
|
||||
* [`polygon`](/docs/kcl-std/polygon)
|
||||
* [`profileStart`](/docs/kcl-std/profileStart)
|
||||
* [`profileStartX`](/docs/kcl-std/profileStartX)
|
||||
@ -94,7 +94,7 @@ layout: manual
|
||||
* [`intersect`](/docs/kcl-std/intersect)
|
||||
* [`patternCircular3d`](/docs/kcl-std/patternCircular3d)
|
||||
* [`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)
|
||||
* [`subtract`](/docs/kcl-std/subtract)
|
||||
* [`union`](/docs/kcl-std/union)
|
||||
|
@ -30,7 +30,7 @@ This module contains functions for creating and manipulating sketches, and makin
|
||||
* [`line`](/docs/kcl-std/line)
|
||||
* [`loft`](/docs/kcl-std/loft)
|
||||
* [`patternCircular2d`](/docs/kcl-std/patternCircular2d)
|
||||
* [`patternTransform2d`](/docs/kcl-std/functions/std-sketch-patternTransform2d)
|
||||
* [`patternTransform2d`](/docs/kcl-std/patternTransform2d)
|
||||
* [`polygon`](/docs/kcl-std/polygon)
|
||||
* [`profileStart`](/docs/kcl-std/profileStart)
|
||||
* [`profileStartX`](/docs/kcl-std/profileStartX)
|
||||
|
@ -18,7 +18,7 @@ This module contains functions for modifying solids, e.g., by adding a fillet or
|
||||
* [`intersect`](/docs/kcl-std/intersect)
|
||||
* [`patternCircular3d`](/docs/kcl-std/patternCircular3d)
|
||||
* [`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)
|
||||
* [`subtract`](/docs/kcl-std/subtract)
|
||||
* [`union`](/docs/kcl-std/union)
|
||||
|
@ -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.
|
||||
|
||||
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
|
||||
|
||||
|
@ -12,8 +12,8 @@ patternCircular2d(
|
||||
@sketchSet: [Sketch],
|
||||
instances: number,
|
||||
center: Point2d,
|
||||
arcDegrees?: number,
|
||||
rotateDuplicates?: bool,
|
||||
arcDegrees: number,
|
||||
rotateDuplicates: bool,
|
||||
useOriginal?: bool,
|
||||
): [Sketch]
|
||||
```
|
||||
@ -27,8 +27,8 @@ patternCircular2d(
|
||||
| `sketchSet` | [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) | Which sketch(es) to pattern | 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 |
|
||||
| `center` | [`Point2d`](/docs/kcl-std/types/std-types-Point2d) | The center about which to make the pattern. This is a 2D vector. | Yes |
|
||||
| `arcDegrees` | [`number`](/docs/kcl-std/types/std-types-number) | The arc angle (in degrees) to place the repetitions. Must be greater than 0. Defaults to 360. | No |
|
||||
| `rotateDuplicates` | [`bool`](/docs/kcl-std/types/std-types-bool) | Whether or not to rotate the duplicates as they are copied. Defaults to true. | No |
|
||||
| `arcDegrees` | [`number`](/docs/kcl-std/types/std-types-number) | The arc angle (in degrees) to place the repetitions. Must be greater than 0. | Yes |
|
||||
| `rotateDuplicates` | [`bool`](/docs/kcl-std/types/std-types-bool) | Whether or not to rotate the duplicates as they are copied. | Yes |
|
||||
| `useOriginal` | [`bool`](/docs/kcl-std/types/std-types-bool) | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |
|
||||
|
||||
### Returns
|
||||
|
@ -1,19 +1,19 @@
|
||||
---
|
||||
title: "patternTransform2d"
|
||||
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
|
||||
---
|
||||
|
||||
Just like `patternTransform`, but works on 2D sketches not 3D solids.
|
||||
Just like patternTransform, but works on 2D sketches not 3D solids.
|
||||
|
||||
```kcl
|
||||
patternTransform2d(
|
||||
@sketches: [Sketch; 1+],
|
||||
instances: number(_),
|
||||
transform: fn(number(_)): { },
|
||||
useOriginal?: boolean,
|
||||
): [Sketch; 1+]
|
||||
@sketches: [Sketch],
|
||||
instances: number,
|
||||
transform: FunctionSource,
|
||||
useOriginal?: bool,
|
||||
): [Sketch]
|
||||
```
|
||||
|
||||
|
||||
@ -22,14 +22,14 @@ patternTransform2d(
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `sketches` | [`[Sketch; 1+]`](/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 |
|
||||
| `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 |
|
||||
| `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 |
|
||||
| `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 |
|
||||
| `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` | [`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
|
||||
|
||||
[`[Sketch; 1+]`](/docs/kcl-std/types/std-types-Sketch)
|
||||
[`[Sketch]`](/docs/kcl-std/types/std-types-Sketch)
|
||||
|
||||
|
||||
### Examples
|
16168
docs/kcl-std/std.json
@ -235,48 +235,6 @@ extrude001 = extrude(sketch001, length = 5)`
|
||||
.first()
|
||||
).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(
|
||||
|
@ -36,29 +36,27 @@ test.describe('Command bar tests', () => {
|
||||
await u.closeDebugPanel()
|
||||
|
||||
// Click the line of code for xLine.
|
||||
await page.getByText(`startProfile(at = [-10, -10])`).click()
|
||||
|
||||
// Wait for the selection to register (TODO: we need a definitive way to wait for this)
|
||||
await page.waitForTimeout(200)
|
||||
await page.getByText(`close()`).click() // TODO remove this and reinstate // await topHorzSegmentClick()
|
||||
|
||||
await toolbar.extrudeButton.click()
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
commandName: 'Extrude',
|
||||
currentArgKey: 'length',
|
||||
currentArgValue: '5',
|
||||
currentArgKey: 'sketches',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Profiles: '1 profile',
|
||||
Sketches: '',
|
||||
Length: '',
|
||||
},
|
||||
highlightedHeaderArg: 'length',
|
||||
highlightedHeaderArg: 'sketches',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
commandName: 'Extrude',
|
||||
headerArguments: {
|
||||
Profiles: '1 profile',
|
||||
Sketches: '1 segment',
|
||||
Length: '5',
|
||||
},
|
||||
})
|
||||
@ -288,7 +286,7 @@ test.describe('Command bar tests', () => {
|
||||
await cmdBar.cmdOptions.getByText('Extrude').click()
|
||||
|
||||
// Assert that we're on the selection step
|
||||
await expect(page.getByRole('button', { name: 'Profiles' })).toBeDisabled()
|
||||
await expect(page.getByRole('button', { name: 'sketches' })).toBeDisabled()
|
||||
// Select a face
|
||||
await page.mouse.move(700, 200)
|
||||
await page.mouse.click(700, 200)
|
||||
@ -401,6 +399,7 @@ test.describe('Command bar tests', () => {
|
||||
sortBy: 'last-modified-desc',
|
||||
})
|
||||
await page.goto(page.url() + targetURL)
|
||||
expect(page.url()).toContain(targetURL)
|
||||
})
|
||||
|
||||
await test.step(`Submit the command`, async () => {
|
||||
@ -411,7 +410,7 @@ test.describe('Command bar tests', () => {
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Method: '',
|
||||
Name: 'main.kcl',
|
||||
Name: 'test',
|
||||
Code: '1 line',
|
||||
},
|
||||
highlightedHeaderArg: 'method',
|
||||
@ -422,7 +421,7 @@ test.describe('Command bar tests', () => {
|
||||
commandName: 'Import file from URL',
|
||||
headerArguments: {
|
||||
Method: 'New project',
|
||||
Name: 'main.kcl',
|
||||
Name: 'test',
|
||||
Code: '1 line',
|
||||
},
|
||||
})
|
||||
@ -464,6 +463,7 @@ test.describe('Command bar tests', () => {
|
||||
sortBy: 'last-modified-desc',
|
||||
})
|
||||
await page.goto(page.url() + targetURL)
|
||||
expect(page.url()).toContain(targetURL)
|
||||
})
|
||||
|
||||
await test.step(`Submit the command`, async () => {
|
||||
@ -474,7 +474,7 @@ test.describe('Command bar tests', () => {
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Method: '',
|
||||
Name: 'main.kcl',
|
||||
Name: 'test',
|
||||
Code: '1 line',
|
||||
},
|
||||
highlightedHeaderArg: 'method',
|
||||
@ -487,7 +487,7 @@ test.describe('Command bar tests', () => {
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Method: 'Existing project',
|
||||
Name: 'main.kcl',
|
||||
Name: 'test',
|
||||
ProjectName: '',
|
||||
Code: '1 line',
|
||||
},
|
||||
@ -500,7 +500,7 @@ test.describe('Command bar tests', () => {
|
||||
headerArguments: {
|
||||
Method: 'Existing project',
|
||||
ProjectName: 'testProjectDir',
|
||||
Name: 'main.kcl',
|
||||
Name: 'test',
|
||||
Code: '1 line',
|
||||
},
|
||||
})
|
||||
@ -510,7 +510,7 @@ test.describe('Command bar tests', () => {
|
||||
await test.step(`Ensure we created the project and are in the modeling scene`, async () => {
|
||||
await editor.expectEditor.toContain('extrusionDistance = 12')
|
||||
await toolbar.openPane('files')
|
||||
await toolbar.expectFileTreeState(['main-1.kcl', 'main.kcl'])
|
||||
await toolbar.expectFileTreeState(['main.kcl', 'test.kcl'])
|
||||
})
|
||||
})
|
||||
|
||||
@ -661,56 +661,4 @@ c = 3 + a`
|
||||
`a = 5b = a * amyParameter001 = ${newValue}c = 3 + a`
|
||||
)
|
||||
})
|
||||
|
||||
test('Command palette can be opened via query parameter', async ({
|
||||
page,
|
||||
homePage,
|
||||
cmdBar,
|
||||
}) => {
|
||||
await page.goto(`${page.url()}/?cmd=app.theme&groupId=settings`)
|
||||
await homePage.expectState({
|
||||
projectCards: [],
|
||||
sortBy: 'last-modified-desc',
|
||||
})
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
commandName: 'Settings · app · theme',
|
||||
currentArgKey: 'value',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Level: 'user',
|
||||
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',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -1131,15 +1131,14 @@ sketch001 = startSketchOn(XZ)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await page.getByText('startProfile(at = [4.61, -14.01])').click()
|
||||
// Wait for the selection to register (TODO: we need a definitive way to wait for this)
|
||||
await page.waitForTimeout(200)
|
||||
await toolbar.extrudeButton.click()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'length',
|
||||
currentArgValue: '5',
|
||||
headerArguments: {
|
||||
Profiles: '1 profile',
|
||||
Sketches: '1 face',
|
||||
Length: '',
|
||||
},
|
||||
highlightedHeaderArg: 'length',
|
||||
@ -1149,7 +1148,7 @@ sketch001 = startSketchOn(XZ)
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
Profiles: '1 profile',
|
||||
Sketches: '1 face',
|
||||
Length: '5',
|
||||
},
|
||||
commandName: 'Extrude',
|
||||
@ -1355,9 +1354,7 @@ sketch001 = startSketchOn(XZ)
|
||||
const u = await getUtils(page)
|
||||
const projectLink = page.getByRole('link', { name: 'cube' })
|
||||
const gizmo = page.locator('[aria-label*=gizmo]')
|
||||
const resetCameraButton = page.getByRole('button', {
|
||||
name: 'Reset view',
|
||||
})
|
||||
const resetCameraButton = page.getByRole('button', { name: 'Reset view' })
|
||||
const locationToHaveColor = async (
|
||||
position: { x: number; y: number },
|
||||
color: [number, number, number]
|
||||
|
@ -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(
|
||||
'deleting all files recreates a default main.kcl with no code',
|
||||
{ tag: '@electron' },
|
||||
|
@ -146,7 +146,9 @@ export class CmdBarFixture {
|
||||
await this.cmdBarOpenBtn.click()
|
||||
await expect(this.page.getByPlaceholder('Search commands')).toBeVisible()
|
||||
if (selectCmd === 'promptToEdit') {
|
||||
const promptEditCommand = this.selectOption({ name: 'Text-to-CAD Edit' })
|
||||
const promptEditCommand = this.page.getByText(
|
||||
'Use Zoo AI to edit your parts and code.'
|
||||
)
|
||||
await expect(promptEditCommand.first()).toBeVisible()
|
||||
await promptEditCommand.first().scrollIntoViewIfNeeded()
|
||||
await promptEditCommand.first().click()
|
||||
@ -308,11 +310,6 @@ export class CmdBarFixture {
|
||||
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) {
|
||||
// Check the placeholder project name exists
|
||||
const actualArgument = await this.cmdBarElement
|
||||
|
@ -26,7 +26,6 @@ export class HomePageFixture {
|
||||
sortByNameBtn!: Locator
|
||||
appHeader!: Locator
|
||||
tutorialBtn!: Locator
|
||||
textToCadBtn!: Locator
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page
|
||||
@ -48,7 +47,6 @@ export class HomePageFixture {
|
||||
this.sortByNameBtn = this.page.getByTestId('home-sort-by-name')
|
||||
this.appHeader = this.page.getByTestId('app-header')
|
||||
this.tutorialBtn = this.page.getByTestId('home-tutorial-button')
|
||||
this.textToCadBtn = this.page.getByTestId('home-text-to-cad')
|
||||
}
|
||||
|
||||
private _serialiseSortBy = async (): Promise<
|
||||
@ -123,13 +121,11 @@ export class HomePageFixture {
|
||||
await projectCard.click()
|
||||
}
|
||||
|
||||
/** Returns the project name in case caller has used the default and needs it */
|
||||
goToModelingScene = async (name = 'testDefault') => {
|
||||
goToModelingScene = async (name: string = 'testDefault') => {
|
||||
// On web this is a no-op. There is no project view.
|
||||
if (process.env.PLATFORM === 'web') return ''
|
||||
if (process.env.PLATFORM === 'web') return
|
||||
|
||||
await this.createAndGoToProject(name)
|
||||
return name
|
||||
}
|
||||
|
||||
isNativeFileMenuCreated = async () => {
|
||||
|
@ -252,7 +252,7 @@ test.describe(
|
||||
tronApp,
|
||||
'Edit.Modify with Zoo Text-To-CAD'
|
||||
)
|
||||
await cmdBar.expectCommandName('Text-to-CAD Edit')
|
||||
await cmdBar.expectCommandName('Prompt-to-edit')
|
||||
})
|
||||
await test.step('Modeling.Edit.Edit parameter', async () => {
|
||||
await page.waitForTimeout(250)
|
||||
@ -518,7 +518,7 @@ test.describe(
|
||||
'Design.Create with Zoo Text-To-CAD'
|
||||
)
|
||||
await cmdBar.toBeOpened()
|
||||
await cmdBar.expectCommandName('Text-to-CAD Create')
|
||||
await cmdBar.expectCommandName('Text to CAD')
|
||||
})
|
||||
|
||||
await test.step('Modeling.Design.Modify with Zoo Text-To-CAD', async () => {
|
||||
@ -528,7 +528,7 @@ test.describe(
|
||||
'Design.Modify with Zoo Text-To-CAD'
|
||||
)
|
||||
await cmdBar.toBeOpened()
|
||||
await cmdBar.expectCommandName('Text-to-CAD Edit')
|
||||
await cmdBar.expectCommandName('Prompt-to-edit')
|
||||
})
|
||||
|
||||
await test.step('Modeling.Help.KCL code samples', async () => {
|
||||
|
@ -74,11 +74,20 @@ test.describe('Point-and-click tests', () => {
|
||||
|
||||
await test.step('do extrude flow and check extrude code is added to editor', async () => {
|
||||
await toolbar.extrudeButton.click()
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'sketches',
|
||||
currentArgValue: '',
|
||||
headerArguments: { Sketches: '', Length: '' },
|
||||
highlightedHeaderArg: 'sketches',
|
||||
commandName: 'Extrude',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'length',
|
||||
currentArgValue: '5',
|
||||
headerArguments: { Profiles: '1 profile', Length: '' },
|
||||
headerArguments: { Sketches: '1 face', Length: '' },
|
||||
highlightedHeaderArg: 'length',
|
||||
commandName: 'Extrude',
|
||||
})
|
||||
@ -89,7 +98,7 @@ test.describe('Point-and-click tests', () => {
|
||||
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: { Profiles: '1 profile', Length: '5' },
|
||||
headerArguments: { Sketches: '1 face', Length: '5' },
|
||||
commandName: 'Extrude',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
@ -1625,15 +1634,15 @@ sketch002 = startSketchOn(plane001)
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'sketches',
|
||||
currentArgValue: '',
|
||||
headerArguments: { Profiles: '' },
|
||||
highlightedHeaderArg: 'Profiles',
|
||||
headerArguments: { Sketches: '' },
|
||||
highlightedHeaderArg: 'sketches',
|
||||
commandName: 'Loft',
|
||||
})
|
||||
await selectSketches()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: { Profiles: '2 profiles' },
|
||||
headerArguments: { Sketches: '2 faces' },
|
||||
commandName: 'Loft',
|
||||
})
|
||||
await cmdBar.submit()
|
||||
@ -1645,9 +1654,18 @@ sketch002 = startSketchOn(plane001)
|
||||
|
||||
await test.step(`Go through the command bar flow with preselected sketches`, async () => {
|
||||
await toolbar.loftButton.click()
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'sketches',
|
||||
currentArgValue: '',
|
||||
headerArguments: { Sketches: '' },
|
||||
highlightedHeaderArg: 'sketches',
|
||||
commandName: 'Loft',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: { Profiles: '2 profiles' },
|
||||
headerArguments: { Sketches: '2 faces' },
|
||||
commandName: 'Loft',
|
||||
})
|
||||
await cmdBar.submit()
|
||||
@ -1812,10 +1830,10 @@ sketch002 = startSketchOn(XZ)
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Sectional: '',
|
||||
Profiles: '',
|
||||
Sketches: '',
|
||||
Path: '',
|
||||
},
|
||||
highlightedHeaderArg: 'Profiles',
|
||||
highlightedHeaderArg: 'sketches',
|
||||
stage: 'arguments',
|
||||
})
|
||||
await clickOnSketch1()
|
||||
@ -1826,7 +1844,7 @@ sketch002 = startSketchOn(XZ)
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Sectional: '',
|
||||
Profiles: '1 profile',
|
||||
Sketches: '1 face',
|
||||
Path: '',
|
||||
},
|
||||
highlightedHeaderArg: 'path',
|
||||
@ -1839,7 +1857,7 @@ sketch002 = startSketchOn(XZ)
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Sectional: '',
|
||||
Profiles: '1 profile',
|
||||
Sketches: '1 face',
|
||||
Path: '',
|
||||
},
|
||||
highlightedHeaderArg: 'path',
|
||||
@ -1849,7 +1867,7 @@ sketch002 = startSketchOn(XZ)
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Sweep',
|
||||
headerArguments: {
|
||||
Profiles: '1 profile',
|
||||
Sketches: '1 face',
|
||||
Path: '1 segment',
|
||||
Sectional: '',
|
||||
},
|
||||
@ -1950,10 +1968,10 @@ profile001 = ${circleCode}`
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Sectional: '',
|
||||
Profiles: '',
|
||||
Sketches: '',
|
||||
Path: '',
|
||||
},
|
||||
highlightedHeaderArg: 'Profiles',
|
||||
highlightedHeaderArg: 'sketches',
|
||||
stage: 'arguments',
|
||||
})
|
||||
await editor.scrollToText(circleCode)
|
||||
@ -1965,7 +1983,7 @@ profile001 = ${circleCode}`
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Sectional: '',
|
||||
Profiles: '1 profile',
|
||||
Sketches: '1 face',
|
||||
Path: '',
|
||||
},
|
||||
highlightedHeaderArg: 'path',
|
||||
@ -1979,7 +1997,7 @@ profile001 = ${circleCode}`
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Sectional: '',
|
||||
Profiles: '1 profile',
|
||||
Sketches: '1 face',
|
||||
Path: '',
|
||||
},
|
||||
highlightedHeaderArg: 'path',
|
||||
@ -1989,7 +2007,7 @@ profile001 = ${circleCode}`
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Sweep',
|
||||
headerArguments: {
|
||||
Profiles: '1 profile',
|
||||
Sketches: '1 face',
|
||||
Path: '1 helix',
|
||||
Sectional: '',
|
||||
},
|
||||
@ -2088,6 +2106,18 @@ extrude001 = extrude(sketch001, length = -12)
|
||||
await test.step(`Apply fillet to the preselected edge`, async () => {
|
||||
await page.waitForTimeout(100)
|
||||
await toolbar.filletButton.click()
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Fillet',
|
||||
highlightedHeaderArg: 'selection',
|
||||
currentArgKey: 'selection',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Selection: '',
|
||||
Radius: '',
|
||||
},
|
||||
stage: 'arguments',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Fillet',
|
||||
highlightedHeaderArg: 'radius',
|
||||
@ -2617,6 +2647,18 @@ extrude001 = extrude(profile001, length = 5)
|
||||
await test.step(`Apply fillet`, async () => {
|
||||
await page.waitForTimeout(100)
|
||||
await toolbar.filletButton.click()
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Fillet',
|
||||
highlightedHeaderArg: 'selection',
|
||||
currentArgKey: 'selection',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Selection: '',
|
||||
Radius: '',
|
||||
},
|
||||
stage: 'arguments',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Fillet',
|
||||
highlightedHeaderArg: 'radius',
|
||||
@ -2722,6 +2764,19 @@ extrude001 = extrude(sketch001, length = -12)
|
||||
await test.step(`Apply chamfer to the preselected edge`, async () => {
|
||||
await page.waitForTimeout(100)
|
||||
await toolbar.chamferButton.click()
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Chamfer',
|
||||
highlightedHeaderArg: 'selection',
|
||||
currentArgKey: 'selection',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Selection: '',
|
||||
Length: '',
|
||||
},
|
||||
stage: 'arguments',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
await page.waitForTimeout(1000)
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Chamfer',
|
||||
highlightedHeaderArg: 'length',
|
||||
@ -3205,6 +3260,8 @@ extrude001 = extrude(sketch001, length = 30)
|
||||
await test.step(`Go through the command bar flow with a preselected face (cap)`, async () => {
|
||||
await toolbar.shellButton.click()
|
||||
await cmdBar.progressCmdBar()
|
||||
await page.waitForTimeout(500)
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
@ -3634,17 +3691,16 @@ tag=$rectangleSegmentC002,
|
||||
await scene.settled(cmdBar)
|
||||
|
||||
// select line of code
|
||||
const codeToSelection = `startProfile(at = [-66.77, 84.81])`
|
||||
const codeToSelection = `segAng(rectangleSegmentA002) - 90,`
|
||||
// revolve
|
||||
await editor.scrollToText(codeToSelection)
|
||||
await page.getByText(codeToSelection).click()
|
||||
// Wait for the selection to register (TODO: we need a definitive way to wait for this)
|
||||
await page.waitForTimeout(200)
|
||||
await toolbar.revolveButton.click()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.progressCmdBar()
|
||||
|
||||
const newCodeToFind = `revolve001 = revolve(sketch002, angle = 360, axis = X)`
|
||||
expect(editor.expectEditor.toContain(newCodeToFind)).toBeTruthy()
|
||||
@ -4573,12 +4629,24 @@ path001 = startProfile(sketch001, at = [0, 0])
|
||||
|
||||
await test.step('Go through command bar flow', async () => {
|
||||
await toolbar.extrudeButton.click()
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'sketches',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Sketches: '',
|
||||
Length: '',
|
||||
},
|
||||
highlightedHeaderArg: 'sketches',
|
||||
commandName: 'Extrude',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'length',
|
||||
currentArgValue: '5',
|
||||
headerArguments: {
|
||||
Profiles: '2 profiles',
|
||||
Sketches: '2 faces',
|
||||
Length: '',
|
||||
},
|
||||
highlightedHeaderArg: 'length',
|
||||
@ -4589,7 +4657,7 @@ path001 = startProfile(sketch001, at = [0, 0])
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
Profiles: '2 profiles',
|
||||
Sketches: '2 faces',
|
||||
Length: '1',
|
||||
},
|
||||
commandName: 'Extrude',
|
||||
@ -4655,12 +4723,25 @@ path001 = startProfile(sketch001, at = [0, 0])
|
||||
|
||||
await test.step('Go through command bar flow', async () => {
|
||||
await toolbar.sweepButton.click()
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'sketches',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Sketches: '',
|
||||
Path: '',
|
||||
Sectional: '',
|
||||
},
|
||||
highlightedHeaderArg: 'sketches',
|
||||
commandName: 'Sweep',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'path',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Profiles: '2 profiles',
|
||||
Sketches: '2 faces',
|
||||
Path: '',
|
||||
Sectional: '',
|
||||
},
|
||||
@ -4673,7 +4754,7 @@ path001 = startProfile(sketch001, at = [0, 0])
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
Profiles: '2 profiles',
|
||||
Sketches: '2 faces',
|
||||
Path: '1 segment',
|
||||
Sectional: '',
|
||||
},
|
||||
@ -4739,12 +4820,25 @@ path001 = startProfile(sketch001, at = [0, 0])
|
||||
await test.step('Go through command bar flow', async () => {
|
||||
await toolbar.closePane('code')
|
||||
await toolbar.revolveButton.click()
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'sketches',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Sketches: '',
|
||||
AxisOrEdge: '',
|
||||
Angle: '',
|
||||
},
|
||||
highlightedHeaderArg: 'sketches',
|
||||
commandName: 'Revolve',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'axisOrEdge',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Profiles: '2 profiles',
|
||||
Sketches: '2 faces',
|
||||
AxisOrEdge: '',
|
||||
Angle: '',
|
||||
},
|
||||
@ -4760,7 +4854,7 @@ path001 = startProfile(sketch001, at = [0, 0])
|
||||
currentArgKey: 'angle',
|
||||
currentArgValue: '360',
|
||||
headerArguments: {
|
||||
Profiles: '2 profiles',
|
||||
Sketches: '2 faces',
|
||||
AxisOrEdge: 'Edge',
|
||||
Edge: '1 segment',
|
||||
Angle: '',
|
||||
@ -4773,7 +4867,7 @@ path001 = startProfile(sketch001, at = [0, 0])
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
Profiles: '2 profiles',
|
||||
Sketches: '2 faces',
|
||||
AxisOrEdge: 'Edge',
|
||||
Edge: '1 segment',
|
||||
Angle: '180',
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
@ -995,8 +995,8 @@ profile001 = startProfile(sketch001, at = [${roundOff(scale * 69.6)}, ${roundOff
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
// click profile in code
|
||||
await page.getByText(`startProfile(at = [-0.45, 0.87])`).click()
|
||||
// click "line(end = [1.32, 0.38])"
|
||||
await page.getByText(`line(end = [1.32, 0.38])`).click()
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeEnabled(
|
||||
{ timeout: 10_000 }
|
||||
@ -1014,13 +1014,14 @@ profile001 = startProfile(sketch001, at = [${roundOff(scale * 69.6)}, ${roundOff
|
||||
// click extrude
|
||||
await toolbar.extrudeButton.click()
|
||||
|
||||
// sketch selection should already have been made.
|
||||
// sketch selection should already have been made. "Sketches: 1 face" only show up when the selection has been made already
|
||||
// otherwise the cmdbar would be waiting for a selection.
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'length',
|
||||
currentArgValue: '5',
|
||||
headerArguments: { Profiles: '1 profile', Length: '' },
|
||||
headerArguments: { Sketches: '1 segment', Length: '' },
|
||||
highlightedHeaderArg: 'length',
|
||||
commandName: 'Extrude',
|
||||
})
|
||||
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 132 KiB |
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 116 KiB |
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 132 KiB |
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 64 KiB |
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 63 KiB |
@ -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) => {
|
||||
return test?.step(`Cloning file '${name}'`, async () => {
|
||||
await page
|
||||
|
@ -4,10 +4,9 @@ import type { Page } from '@playwright/test'
|
||||
|
||||
import { createProject, getUtils } from '@e2e/playwright/test-utils'
|
||||
import { expect, test } from '@e2e/playwright/zoo-test'
|
||||
import type { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
|
||||
|
||||
test.describe('Text-to-CAD tests', () => {
|
||||
test('basic lego happy case', async ({ page, homePage, cmdBar }) => {
|
||||
test('basic lego happy case', async ({ page, homePage }) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
await test.step('Set up', async () => {
|
||||
@ -16,11 +15,7 @@ test.describe('Text-to-CAD tests', () => {
|
||||
await u.waitForPageLoad()
|
||||
})
|
||||
|
||||
await sendPromptFromCommandBarAndSetExistingProject(
|
||||
page,
|
||||
'a 2x4 lego',
|
||||
cmdBar
|
||||
)
|
||||
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
|
||||
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
@ -61,7 +56,6 @@ test.describe('Text-to-CAD tests', () => {
|
||||
test('success model, then ignore success toast, user can create new prompt from command bar', async ({
|
||||
page,
|
||||
homePage,
|
||||
cmdBar,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
@ -70,11 +64,7 @@ test.describe('Text-to-CAD tests', () => {
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
|
||||
await sendPromptFromCommandBarAndSetExistingProject(
|
||||
page,
|
||||
'a 2x6 lego',
|
||||
cmdBar
|
||||
)
|
||||
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x6 lego')
|
||||
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
@ -92,11 +82,7 @@ test.describe('Text-to-CAD tests', () => {
|
||||
await expect(successToastMessage).toBeVisible({ timeout: 15000 })
|
||||
|
||||
// Can send a new prompt from the command bar.
|
||||
await sendPromptFromCommandBarAndSetExistingProject(
|
||||
page,
|
||||
'a 2x4 lego',
|
||||
cmdBar
|
||||
)
|
||||
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
|
||||
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
@ -114,7 +100,6 @@ test.describe('Text-to-CAD tests', () => {
|
||||
test('you can reject text-to-cad output and it does nothing', async ({
|
||||
page,
|
||||
homePage,
|
||||
cmdBar,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
@ -123,11 +108,7 @@ test.describe('Text-to-CAD tests', () => {
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
|
||||
await sendPromptFromCommandBarAndSetExistingProject(
|
||||
page,
|
||||
'a 2x4 lego',
|
||||
cmdBar
|
||||
)
|
||||
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
|
||||
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
@ -160,7 +141,6 @@ test.describe('Text-to-CAD tests', () => {
|
||||
test('sending a bad prompt fails, can dismiss', async ({
|
||||
page,
|
||||
homePage,
|
||||
cmdBar,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
@ -170,11 +150,7 @@ test.describe('Text-to-CAD tests', () => {
|
||||
await u.waitForPageLoad()
|
||||
|
||||
const randomPrompt = `aslkdfja;` + Date.now() + `FFFFEIWJF`
|
||||
await sendPromptFromCommandBarAndSetExistingProject(
|
||||
page,
|
||||
randomPrompt,
|
||||
cmdBar
|
||||
)
|
||||
await sendPromptFromCommandBarTriggeredByButton(page, randomPrompt)
|
||||
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
@ -212,7 +188,6 @@ test.describe('Text-to-CAD tests', () => {
|
||||
test('sending a bad prompt fails, can start over from toast', async ({
|
||||
page,
|
||||
homePage,
|
||||
cmdBar,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
@ -222,7 +197,7 @@ test.describe('Text-to-CAD tests', () => {
|
||||
await u.waitForPageLoad()
|
||||
|
||||
const badPrompt = 'akjsndladf lajbhflauweyfaaaljhr472iouafyvsssssss'
|
||||
await sendPromptFromCommandBarAndSetExistingProject(page, badPrompt, cmdBar)
|
||||
await sendPromptFromCommandBarTriggeredByButton(page, badPrompt)
|
||||
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
@ -281,7 +256,6 @@ test.describe('Text-to-CAD tests', () => {
|
||||
test('sending a bad prompt fails, can ignore toast, can start over from command bar', async ({
|
||||
page,
|
||||
homePage,
|
||||
cmdBar,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
@ -291,7 +265,7 @@ test.describe('Text-to-CAD tests', () => {
|
||||
await u.waitForPageLoad()
|
||||
|
||||
const badPrompt = 'akjsndladflajbhflauweyf15;'
|
||||
await sendPromptFromCommandBarAndSetExistingProject(page, badPrompt, cmdBar)
|
||||
await sendPromptFromCommandBarTriggeredByButton(page, badPrompt)
|
||||
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
@ -318,11 +292,7 @@ test.describe('Text-to-CAD tests', () => {
|
||||
await expect(page.getByText(`Text-to-CAD failed`)).toBeVisible()
|
||||
|
||||
// They should be able to try again from the command bar.
|
||||
await sendPromptFromCommandBarAndSetExistingProject(
|
||||
page,
|
||||
'a 2x4 lego',
|
||||
cmdBar
|
||||
)
|
||||
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
|
||||
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
@ -340,40 +310,17 @@ test.describe('Text-to-CAD tests', () => {
|
||||
test('ensure you can shift+enter in the prompt box', async ({
|
||||
page,
|
||||
homePage,
|
||||
cmdBar,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
|
||||
const projectName = await homePage.goToModelingScene()
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
|
||||
const promptWithNewline = `a 2x4\nlego`
|
||||
|
||||
await test.step('Get to the prompt step to test', async () => {
|
||||
await cmdBar.openCmdBar()
|
||||
await cmdBar.selectOption({ name: 'Text-to-CAD Create' }).click()
|
||||
|
||||
await cmdBar.currentArgumentInput.fill('existing')
|
||||
await cmdBar.progressCmdBar()
|
||||
|
||||
await cmdBar.currentArgumentInput.fill(projectName)
|
||||
await cmdBar.progressCmdBar()
|
||||
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Text-to-CAD Create',
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'prompt',
|
||||
currentArgValue: '',
|
||||
highlightedHeaderArg: 'prompt',
|
||||
headerArguments: {
|
||||
Method: 'Existing project',
|
||||
ProjectName: projectName,
|
||||
Prompt: '',
|
||||
},
|
||||
})
|
||||
})
|
||||
await page.getByTestId('text-to-cad').click()
|
||||
|
||||
// Type the prompt.
|
||||
await page.keyboard.type('a 2x4')
|
||||
@ -407,7 +354,6 @@ test.describe('Text-to-CAD tests', () => {
|
||||
test('can do many at once and get many prompts back, and interact with many', async ({
|
||||
page,
|
||||
homePage,
|
||||
cmdBar,
|
||||
}) => {
|
||||
// Let this test run longer since we've seen it timeout.
|
||||
test.setTimeout(180_000)
|
||||
@ -419,23 +365,11 @@ test.describe('Text-to-CAD tests', () => {
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
|
||||
await sendPromptFromCommandBarAndSetExistingProject(
|
||||
page,
|
||||
'a 2x4 lego',
|
||||
cmdBar
|
||||
)
|
||||
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
|
||||
|
||||
await sendPromptFromCommandBarAndSetExistingProject(
|
||||
page,
|
||||
'a 2x8 lego',
|
||||
cmdBar
|
||||
)
|
||||
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x8 lego')
|
||||
|
||||
await sendPromptFromCommandBarAndSetExistingProject(
|
||||
page,
|
||||
'a 2x10 lego',
|
||||
cmdBar
|
||||
)
|
||||
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x10 lego')
|
||||
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
@ -506,7 +440,6 @@ test.describe('Text-to-CAD tests', () => {
|
||||
test('can do many at once with errors, clicking dismiss error does not dismiss all', async ({
|
||||
page,
|
||||
homePage,
|
||||
cmdBar,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
@ -515,16 +448,11 @@ test.describe('Text-to-CAD tests', () => {
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
|
||||
await sendPromptFromCommandBarAndSetExistingProject(
|
||||
page,
|
||||
'a 2x4 lego',
|
||||
cmdBar
|
||||
)
|
||||
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
|
||||
|
||||
await sendPromptFromCommandBarAndSetExistingProject(
|
||||
await sendPromptFromCommandBarTriggeredByButton(
|
||||
page,
|
||||
'alkjsdnlajshdbfjlhsbdf a;askjdnf',
|
||||
cmdBar
|
||||
'alkjsdnlajshdbfjlhsbdf a;askjdnf'
|
||||
)
|
||||
|
||||
// Find the toast.
|
||||
@ -598,9 +526,7 @@ async function _sendPromptFromCommandBar(page: Page, promptStr: string) {
|
||||
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||
await expect(cmdSearchBar).toBeVisible()
|
||||
|
||||
const textToCadCommand = page.getByRole('option', {
|
||||
name: 'Text-to-CAD Create',
|
||||
})
|
||||
const textToCadCommand = page.getByText('Use the Zoo Text-to-CAD API')
|
||||
await expect(textToCadCommand.first()).toBeVisible()
|
||||
// Click the Text-to-CAD command
|
||||
await textToCadCommand.first().scrollIntoViewIfNeeded()
|
||||
@ -618,67 +544,29 @@ async function _sendPromptFromCommandBar(page: Page, promptStr: string) {
|
||||
})
|
||||
}
|
||||
|
||||
async function sendPromptFromCommandBarAndSetExistingProject(
|
||||
async function sendPromptFromCommandBarTriggeredByButton(
|
||||
page: Page,
|
||||
promptStr: string,
|
||||
cmdBar: CmdBarFixture,
|
||||
projectName = 'testDefault'
|
||||
promptStr: string
|
||||
) {
|
||||
await page.waitForTimeout(1000)
|
||||
await test.step(`Send prompt from command bar: ${promptStr}`, async () => {
|
||||
await cmdBar.openCmdBar()
|
||||
await cmdBar.selectOption({ name: 'Text-to-CAD Create' }).click()
|
||||
await page.getByTestId('text-to-cad').click()
|
||||
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Text-to-CAD Create',
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'method',
|
||||
currentArgValue: '',
|
||||
highlightedHeaderArg: 'method',
|
||||
headerArguments: {
|
||||
Method: '',
|
||||
Prompt: '',
|
||||
},
|
||||
})
|
||||
await cmdBar.currentArgumentInput.fill('existing')
|
||||
await cmdBar.progressCmdBar()
|
||||
// Enter the prompt.
|
||||
const prompt = page.getByRole('textbox', { name: 'Prompt' })
|
||||
await expect(prompt.first()).toBeVisible()
|
||||
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Text-to-CAD Create',
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'projectName',
|
||||
currentArgValue: '',
|
||||
highlightedHeaderArg: 'projectName',
|
||||
headerArguments: {
|
||||
Method: 'Existing project',
|
||||
ProjectName: '',
|
||||
Prompt: '',
|
||||
},
|
||||
})
|
||||
await cmdBar.currentArgumentInput.fill(projectName)
|
||||
await cmdBar.progressCmdBar()
|
||||
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Text-to-CAD Create',
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'prompt',
|
||||
currentArgValue: '',
|
||||
highlightedHeaderArg: 'prompt',
|
||||
headerArguments: {
|
||||
Method: 'Existing project',
|
||||
ProjectName: projectName,
|
||||
Prompt: '',
|
||||
},
|
||||
})
|
||||
await cmdBar.currentArgumentInput.fill(promptStr)
|
||||
await cmdBar.progressCmdBar()
|
||||
// Type the prompt.
|
||||
await page.keyboard.type(promptStr)
|
||||
await page.waitForTimeout(200)
|
||||
await page.keyboard.press('Enter')
|
||||
})
|
||||
}
|
||||
|
||||
test(
|
||||
'Text-to-CAD functionality',
|
||||
{ tag: '@electron' },
|
||||
async ({ context, page, cmdBar }, testInfo) => {
|
||||
async ({ context, page }, testInfo) => {
|
||||
const projectName = 'project-000'
|
||||
const prompt = 'lego 2x4'
|
||||
const textToCadFileName = 'lego-2x4.kcl'
|
||||
@ -715,12 +603,7 @@ test(
|
||||
await openKclCodePanel()
|
||||
|
||||
await test.step(`Test file creation`, async () => {
|
||||
await sendPromptFromCommandBarAndSetExistingProject(
|
||||
page,
|
||||
prompt,
|
||||
cmdBar,
|
||||
projectName
|
||||
)
|
||||
await sendPromptFromCommandBarTriggeredByButton(page, prompt)
|
||||
// File is considered created if it shows up in the Project Files pane
|
||||
await expect(textToCadFileButton).toBeVisible({ timeout: 20_000 })
|
||||
expect(fileExists()).toBeTruthy()
|
||||
@ -890,12 +773,12 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
|
||||
)
|
||||
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
||||
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
||||
'main.kcl'
|
||||
'2x2x2-cube.kcl'
|
||||
)
|
||||
|
||||
await u.openFilePanel()
|
||||
await expect(
|
||||
page.getByTestId('file-tree-item').getByText('main.kcl')
|
||||
page.getByTestId('file-tree-item').getByText('2x2x2-cube.kcl')
|
||||
).toBeVisible()
|
||||
}
|
||||
)
|
||||
@ -1030,7 +913,7 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
|
||||
)
|
||||
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
||||
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
||||
'main.kcl'
|
||||
'2x2x2-cube.kcl'
|
||||
)
|
||||
|
||||
await page.getByRole('button', { name: 'Reject' }).click()
|
||||
@ -1078,7 +961,7 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
|
||||
)
|
||||
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
||||
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
||||
'main.kcl'
|
||||
'2x2x2-cube.kcl'
|
||||
)
|
||||
}
|
||||
)
|
||||
@ -1330,14 +1213,18 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
|
||||
)
|
||||
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
||||
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
||||
'main.kcl'
|
||||
'2x2x2-cube.kcl'
|
||||
)
|
||||
|
||||
// Check file is created
|
||||
await u.openFilePanel()
|
||||
await expect(
|
||||
page.getByTestId('file-tree-item').getByText('main.kcl')
|
||||
page.getByTestId('file-tree-item').getByText('2x2x2-cube.kcl')
|
||||
).toBeVisible()
|
||||
|
||||
await expect(
|
||||
page.getByTestId('file-tree-item').getByText('main.kcl')
|
||||
).not.toBeVisible()
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -573,6 +573,7 @@ profile001 = startProfile(sketch002, at = [-12.34, 12.34])
|
||||
await expect(page.getByTestId('command-bar')).toBeVisible()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.progressCmdBar()
|
||||
await expect(page.getByText('Confirm Extrude')).toBeVisible()
|
||||
await cmdBar.progressCmdBar()
|
||||
|
@ -37,8 +37,6 @@ When you submit a PR to add or modify KCL samples, images will be generated and
|
||||
[](bottle/main.kcl)
|
||||
#### [bracket](bracket/main.kcl) ([screenshot](screenshots/bracket.png))
|
||||
[](bracket/main.kcl)
|
||||
#### [brake-rotor](brake-rotor/main.kcl) ([screenshot](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/main.kcl)
|
||||
#### [cold-plate](cold-plate/main.kcl) ([screenshot](screenshots/cold-plate.png))
|
||||
@ -51,16 +49,12 @@ When you submit a PR to add or modify KCL samples, images will be generated and
|
||||
[](countersunk-plate/main.kcl)
|
||||
#### [cpu-cooler](cpu-cooler/main.kcl) ([screenshot](screenshots/cpu-cooler.png))
|
||||
[](cpu-cooler/main.kcl)
|
||||
#### [curtain-wall-anchor-plate](curtain-wall-anchor-plate/main.kcl) ([screenshot](screenshots/curtain-wall-anchor-plate.png))
|
||||
[](curtain-wall-anchor-plate/main.kcl)
|
||||
#### [cycloidal-gear](cycloidal-gear/main.kcl) ([screenshot](screenshots/cycloidal-gear.png))
|
||||
[](cycloidal-gear/main.kcl)
|
||||
#### [dodecahedron](dodecahedron/main.kcl) ([screenshot](screenshots/dodecahedron.png))
|
||||
[](dodecahedron/main.kcl)
|
||||
#### [enclosure](enclosure/main.kcl) ([screenshot](screenshots/enclosure.png))
|
||||
[](enclosure/main.kcl)
|
||||
#### [engine-valve](engine-valve/main.kcl) ([screenshot](screenshots/engine-valve.png))
|
||||
[](engine-valve/main.kcl)
|
||||
#### [exhaust-manifold](exhaust-manifold/main.kcl) ([screenshot](screenshots/exhaust-manifold.png))
|
||||
[](exhaust-manifold/main.kcl)
|
||||
#### [flange](flange/main.kcl) ([screenshot](screenshots/flange.png))
|
||||
@ -109,8 +103,6 @@ When you submit a PR to add or modify KCL samples, images will be generated and
|
||||
[](mounting-plate/main.kcl)
|
||||
#### [multi-axis-robot](multi-axis-robot/main.kcl) ([screenshot](screenshots/multi-axis-robot.png))
|
||||
[](multi-axis-robot/main.kcl)
|
||||
#### [pdu-faceplate](pdu-faceplate/main.kcl) ([screenshot](screenshots/pdu-faceplate.png))
|
||||
[](pdu-faceplate/main.kcl)
|
||||
#### [pillow-block-bearing](pillow-block-bearing/main.kcl) ([screenshot](screenshots/pillow-block-bearing.png))
|
||||
[](pillow-block-bearing/main.kcl)
|
||||
#### [pipe](pipe/main.kcl) ([screenshot](screenshots/pipe.png))
|
||||
@ -127,24 +119,16 @@ When you submit a PR to add or modify KCL samples, images will be generated and
|
||||
[](router-template-cross-bar/main.kcl)
|
||||
#### [router-template-slate](router-template-slate/main.kcl) ([screenshot](screenshots/router-template-slate.png))
|
||||
[](router-template-slate/main.kcl)
|
||||
#### [sash-window](sash-window/main.kcl) ([screenshot](screenshots/sash-window.png))
|
||||
[](sash-window/main.kcl)
|
||||
#### [sheet-metal-bracket](sheet-metal-bracket/main.kcl) ([screenshot](screenshots/sheet-metal-bracket.png))
|
||||
[](sheet-metal-bracket/main.kcl)
|
||||
#### [shepherds-hook-bolt](shepherds-hook-bolt/main.kcl) ([screenshot](screenshots/shepherds-hook-bolt.png))
|
||||
[](shepherds-hook-bolt/main.kcl)
|
||||
#### [socket-head-cap-screw](socket-head-cap-screw/main.kcl) ([screenshot](screenshots/socket-head-cap-screw.png))
|
||||
[](socket-head-cap-screw/main.kcl)
|
||||
#### [spinning-highrise-tower](spinning-highrise-tower/main.kcl) ([screenshot](screenshots/spinning-highrise-tower.png))
|
||||
[](spinning-highrise-tower/main.kcl)
|
||||
#### [spur-gear](spur-gear/main.kcl) ([screenshot](screenshots/spur-gear.png))
|
||||
[](spur-gear/main.kcl)
|
||||
#### [spur-reduction-gearset](spur-reduction-gearset/main.kcl) ([screenshot](screenshots/spur-reduction-gearset.png))
|
||||
[](spur-reduction-gearset/main.kcl)
|
||||
#### [surgical-drill-guide](surgical-drill-guide/main.kcl) ([screenshot](screenshots/surgical-drill-guide.png))
|
||||
[](surgical-drill-guide/main.kcl)
|
||||
#### [thermal-block-insert](thermal-block-insert/main.kcl) ([screenshot](screenshots/thermal-block-insert.png))
|
||||
[](thermal-block-insert/main.kcl)
|
||||
#### [tooling-nest-block](tooling-nest-block/main.kcl) ([screenshot](screenshots/tooling-nest-block.png))
|
||||
[](tooling-nest-block/main.kcl)
|
||||
#### [utility-sink](utility-sink/main.kcl) ([screenshot](screenshots/utility-sink.png))
|
||||
|
@ -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))
|
@ -1,155 +0,0 @@
|
||||
// Curtain Wall Anchor Plate
|
||||
// A structural steel L-plate used to anchor curtain wall systems to concrete slabs, with elongated holes for adjustability and bolts with nuts and base plates for secure fastening
|
||||
|
||||
// Set units in millimeters (mm)
|
||||
@settings(defaultLengthUnit = mm, kclVersion = 1.0)
|
||||
|
||||
// Define parameters
|
||||
slabPlateBaseLength = 300
|
||||
slabPlateHookLength = 80
|
||||
slabPlateWidth = 200
|
||||
slabPlateThickness = 8
|
||||
offsetSlabRail = 200
|
||||
|
||||
// Generate L-shaped anchor profile with base and hook flange
|
||||
// Includes fillets at internal and external corners for strength and safety
|
||||
fn lProfileFn(lengthBase, lengthHook, width, thickness) {
|
||||
profilePlane = startSketchOn(offsetPlane(XZ, offset = -width / 2))
|
||||
profileShape = startProfile(profilePlane, at = [0, 0])
|
||||
|> yLine(length = lengthHook, tag = $hookOutside)
|
||||
|> xLine(length = thickness)
|
||||
|> yLine(length = thickness - lengthHook, tag = $hookInside)
|
||||
|> xLine(length = lengthBase - thickness, tag = $baseInside)
|
||||
|> yLine(length = -thickness)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $baseOutside)
|
||||
|> close()
|
||||
profileBody = extrude(profileShape, length = width)
|
||||
|> fillet(
|
||||
radius = thickness,
|
||||
tags = [
|
||||
getCommonEdge(faces = [baseInside, hookInside])
|
||||
],
|
||||
)
|
||||
|> fillet(
|
||||
radius = thickness * 2,
|
||||
tags = [
|
||||
getCommonEdge(faces = [baseOutside, hookOutside])
|
||||
],
|
||||
)
|
||||
return profileBody
|
||||
}
|
||||
|
||||
// Create a hexagonal shape used for bolt and nut heads
|
||||
fn hexagonFn(plane, radius) {
|
||||
shape = startProfile(plane, at = [-radius, 0])
|
||||
|> angledLine(angle = 60, length = radius)
|
||||
|> xLine(length = radius)
|
||||
|> angledLine(angle = -60, length = radius)
|
||||
|> angledLine(angle = -120, length = radius)
|
||||
|> xLine(length = -radius)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
return shape
|
||||
}
|
||||
|
||||
// Build a bolt with a hexagonal head and cylindrical shaft
|
||||
fn boltFn(diameter, length) {
|
||||
boltHeadPlane = startSketchOn(XY)
|
||||
boltHeadShape = hexagonFn(plane = boltHeadPlane, radius = diameter)
|
||||
boltHeadBody = extrude(boltHeadShape, length = diameter * 0.7)
|
||||
boltPlane = startSketchOn(boltHeadBody, face = START)
|
||||
boltShape = circle(boltPlane, center = [0, 0], radius = diameter / 2)
|
||||
boltBody = extrude(boltShape, length = length)
|
||||
return boltBody
|
||||
}
|
||||
|
||||
// Construct a bolt assembly with base plate and hex nut
|
||||
// Assembles all parts for realistic anchor simulation
|
||||
fn boltWithPlateAndNutFn(diameter, length, gap) {
|
||||
plateSide = diameter * 3
|
||||
plateplane = startSketchOn(offsetPlane(XY, offset = -gap))
|
||||
plateShape = startProfile(plateplane, at = [-plateSide / 2, -plateSide / 2])
|
||||
|> yLine(length = plateSide)
|
||||
|> xLine(length = plateSide)
|
||||
|> yLine(length = -plateSide)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
plateBody = extrude(plateShape, length = -diameter * 0.3)
|
||||
nutPlane = startSketchOn(plateBody, face = START)
|
||||
boltHeadShape = hexagonFn(plane = nutPlane, radius = 12)
|
||||
boltHeadBody = extrude(boltHeadShape, length = diameter * 0.7)
|
||||
boltBody = boltFn(diameter = diameter, length = gap + diameter + 3)
|
||||
mergedBody = union([boltHeadBody, boltBody])
|
||||
return mergedBody
|
||||
}
|
||||
|
||||
// Generate the plate geometry with a vertical hook for slab attachment
|
||||
slabPlate = lProfileFn(
|
||||
lengthBase = slabPlateBaseLength,
|
||||
lengthHook = slabPlateHookLength,
|
||||
width = slabPlateWidth,
|
||||
thickness = slabPlateThickness,
|
||||
)
|
||||
|
||||
// Define oblong holes for bolts, allowing positional adjustment
|
||||
wideHoleWidth = 12
|
||||
wideHoleLength = 60
|
||||
wideHoleOffset = 30
|
||||
|
||||
// Two slots mirrored across the plate width
|
||||
wideHolePlane = startSketchOn(XY)
|
||||
wideHoleShape = startProfile(
|
||||
wideHolePlane,
|
||||
at = [
|
||||
-(wideHoleLength - wideHoleWidth) / 2,
|
||||
wideHoleWidth / 2
|
||||
],
|
||||
)
|
||||
|> xLine(length = wideHoleLength - wideHoleWidth)
|
||||
|> tangentialArc(endAbsolute = [
|
||||
(wideHoleLength - wideHoleWidth) / 2,
|
||||
-wideHoleWidth / 2
|
||||
])
|
||||
|> xLine(length = wideHoleWidth - wideHoleLength)
|
||||
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> translate(
|
||||
%,
|
||||
x = offsetSlabRail,
|
||||
y = wideHoleOffset - (slabPlateWidth / 2),
|
||||
z = -1,
|
||||
)
|
||||
wideHoleVoidLeft = extrude(wideHoleShape, length = slabPlateThickness + 2)
|
||||
wideHoleVoidRight = clone(wideHoleVoidLeft)
|
||||
|> translate(
|
||||
%,
|
||||
x = 0,
|
||||
y = slabPlateWidth - (wideHoleOffset * 2),
|
||||
z = 0,
|
||||
)
|
||||
|
||||
// Cut the holes into the anchor plate body
|
||||
slabPlatePunchOne = subtract([slabPlate], tools = [wideHoleVoidLeft])
|
||||
slabPlatePunchTwo = subtract([slabPlatePunchOne], tools = [wideHoleVoidRight])
|
||||
|
||||
// Add two bolt assemblies into the oblong slots
|
||||
// Properly rotated and spaced to match anchor hole layout
|
||||
slabPlateBolts = boltWithPlateAndNutFn(diameter = 10, length = 20, gap = slabPlateThickness + 5)
|
||||
|> rotate(
|
||||
%,
|
||||
roll = 180,
|
||||
pitch = 0,
|
||||
yaw = 0,
|
||||
)
|
||||
|> translate(
|
||||
%,
|
||||
x = offsetSlabRail,
|
||||
y = wideHoleOffset - (slabPlateWidth / 2),
|
||||
z = 5,
|
||||
)
|
||||
|> patternLinear3d(
|
||||
%,
|
||||
instances = 2,
|
||||
distance = slabPlateWidth - (wideHoleOffset * 2),
|
||||
axis = [0, -1, 0],
|
||||
)
|
@ -1,79 +0,0 @@
|
||||
// Engine Valve
|
||||
// A mechanical valve used in internal combustion engines to control intake or exhaust flow
|
||||
|
||||
|
||||
|
||||
|
||||
@settings(defaultLengthUnit = mm, kclVersion = 1.0)
|
||||
|
||||
// Define parameters
|
||||
valveDiameter = 30
|
||||
valveLength = 120
|
||||
valveHeadLength = valveDiameter * 1.0
|
||||
valveHeadThickness = 3
|
||||
stemDiameter = 6
|
||||
stemHeadLength = 9
|
||||
stemLength = valveLength - valveHeadLength - stemHeadLength
|
||||
|
||||
// Create the valve head
|
||||
valveRadius = valveDiameter / 2
|
||||
valveHeadPlane = startSketchOn(XZ)
|
||||
valveHeadShape = startProfile(valveHeadPlane, at = [-0.01, valveHeadLength])
|
||||
|> xLine(length = 0.01 - (stemDiameter / 2))
|
||||
|> line(endAbsolute = [0.01 - (stemDiameter / 2), valveRadius])
|
||||
|> tangentialArc(endAbsolute = [-0.8 * valveRadius, valveHeadThickness], tag = $seg01)
|
||||
|> tangentialArc(endAbsolute = [-valveRadius, 0])
|
||||
|> xLine(length = 0.3 * valveRadius)
|
||||
|> arc(
|
||||
interiorAbsolute = [
|
||||
-0.34 * valveRadius,
|
||||
0.08 * valveRadius
|
||||
],
|
||||
endAbsolute = [
|
||||
-0.02 * valveRadius,
|
||||
0.11 * valveRadius
|
||||
],
|
||||
)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
valveHead = revolve(valveHeadShape, angle = 360, axis = Y)
|
||||
|
||||
// Create the valve stem
|
||||
valveStemSketch = startSketchOn(offsetPlane(XY, offset = valveHeadLength))
|
||||
|> circle(center = [0, 0], radius = stemDiameter / 2)
|
||||
|> extrude(length = stemLength - valveHeadLength - stemHeadLength)
|
||||
|
||||
// Create the valve stem end
|
||||
stepLength = stemHeadLength / 10
|
||||
step1 = startSketchOn(valveStemSketch, face = END)
|
||||
|> circle(%, center = [0, 0], radius = stemDiameter / 2 * 0.9)
|
||||
|> extrude(%, length = stepLength * 2)
|
||||
step2 = startSketchOn(step1, face = END)
|
||||
|> circle(%, center = [0, 0], radius = stemDiameter / 2 * 0.8)
|
||||
|> extrude(%, length = stepLength)
|
||||
step3 = startSketchOn(step2, face = END)
|
||||
|> circle(%, center = [0, 0], radius = stemDiameter / 2 * 0.9)
|
||||
|> extrude(%, length = stepLength)
|
||||
step4 = startSketchOn(step3, face = END)
|
||||
|> circle(%, center = [0, 0], radius = stemDiameter / 2 * 0.8)
|
||||
|> extrude(%, length = stepLength)
|
||||
step5 = startSketchOn(step4, face = END)
|
||||
|> circle(%, center = [0, 0], radius = stemDiameter / 2 * 0.9)
|
||||
|> extrude(%, length = stepLength)
|
||||
step6 = startSketchOn(step5, face = END)
|
||||
|> circle(%, center = [0, 0], radius = stemDiameter / 2 * 0.8)
|
||||
|> extrude(%, length = stepLength)
|
||||
step7 = startSketchOn(step6, face = END)
|
||||
|> circle(
|
||||
%,
|
||||
center = [0, 0],
|
||||
radius = stemDiameter / 2 * 0.9,
|
||||
tag = $seg02,
|
||||
)
|
||||
|> extrude(%, length = stepLength * 3, tagEnd = $capEnd001)
|
||||
|> chamfer(
|
||||
length = 0.5,
|
||||
tags = [
|
||||
getCommonEdge(faces = [seg02, capEnd001])
|
||||
],
|
||||
)
|
@ -74,16 +74,6 @@
|
||||
"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",
|
||||
"pathFromProjectDirectoryToFirstFile": "car-wheel-assembly/main.kcl",
|
||||
@ -157,16 +147,6 @@
|
||||
"removable-sticker.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "curtain-wall-anchor-plate/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Curtain Wall Anchor Plate",
|
||||
"description": "A structural steel L-plate used to anchor curtain wall systems to concrete slabs, with elongated holes for adjustability and bolts with nuts and base plates for secure fastening",
|
||||
"files": [
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "cycloidal-gear/main.kcl",
|
||||
@ -197,16 +177,6 @@
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "engine-valve/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Engine Valve",
|
||||
"description": "A mechanical valve used in internal combustion engines to control intake or exhaust flow",
|
||||
"files": [
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "exhaust-manifold/main.kcl",
|
||||
@ -452,16 +422,6 @@
|
||||
"robot-rotating-base.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "pdu-faceplate/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Power Distribution Unit (PDU) faceplate with European plug sockets and switch",
|
||||
"description": "Designed for standard 19-inch rack systems with 1U height and 8 sockets",
|
||||
"files": [
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "pillow-block-bearing/main.kcl",
|
||||
@ -552,16 +512,6 @@
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "sash-window/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Sash Window",
|
||||
"description": "A traditional wooden sash window with two vertically sliding panels and a central locking mechanism",
|
||||
"files": [
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "sheet-metal-bracket/main.kcl",
|
||||
@ -572,16 +522,6 @@
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "shepherds-hook-bolt/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Shepherd’s Hook Bolt",
|
||||
"description": "A bent bolt with a curved hook, typically used for hanging or anchoring loads. The threaded end allows secure attachment to surfaces or materials, while the curved hook resists pull-out under tension.",
|
||||
"files": [
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "socket-head-cap-screw/main.kcl",
|
||||
@ -592,16 +532,6 @@
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "spinning-highrise-tower/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Spinning Highrise Tower",
|
||||
"description": "A conceptual high-rise tower with a central core and rotating floor slabs, demonstrating dynamic form through vertical repetition and transformation",
|
||||
"files": [
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "spur-gear/main.kcl",
|
||||
@ -632,16 +562,6 @@
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "thermal-block-insert/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Thermal Block Insert",
|
||||
"description": "Interlocking insulation insert for masonry walls, designed with a tongue-and-groove profile for modular alignment and thermal efficiency",
|
||||
"files": [
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "tooling-nest-block/main.kcl",
|
||||
|
@ -1,240 +0,0 @@
|
||||
// Power Distribution Unit (PDU) faceplate with European plug sockets and switch
|
||||
// Designed for standard 19-inch rack systems with 1U height and 8 sockets
|
||||
|
||||
// Set units in millimeters (mm)
|
||||
@settings(defaultLengthUnit = mm, kclVersion = 1.0)
|
||||
|
||||
// Define the dimensions
|
||||
// Width fits standard 19” rack, height is 1U, depth is variable
|
||||
faceplateWidth = 482.6 // this is standardized to fit 19-inch racks)
|
||||
faceplateHeight = 44.45 // usually 1U (44.45 mm), but can be 2U (88.9 mm) or more
|
||||
faceplateDepth = 100 // varies by manufacturer, but commonly between 100 mm and 300 mm
|
||||
|
||||
|
||||
// Define dimensions of side supports (width and thickness)
|
||||
supportWidth = 50
|
||||
supportThickness = 3
|
||||
|
||||
// Main body of the PDU faceplate with integrated rack mounting flanges
|
||||
faceplateShape = startSketchOn(offsetPlane(XY, offset = -faceplateHeight / 2))
|
||||
|> startProfile(%, at = [-faceplateWidth / 2 - supportWidth, 0])
|
||||
|> yLine(length = supportThickness)
|
||||
|> xLine(length = supportWidth)
|
||||
|> yLine(length = faceplateDepth - supportThickness)
|
||||
|> xLine(length = faceplateWidth)
|
||||
|> yLine(length = supportThickness - faceplateDepth)
|
||||
|> xLine(length = supportWidth)
|
||||
|> yLine(length = -supportThickness)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg01)
|
||||
|> close()
|
||||
faceplateBody = extrude(faceplateShape, length = faceplateHeight)
|
||||
faceplateFrontFace = startSketchOn(faceplateBody, face = seg01)
|
||||
|
||||
// Creates recessed volume within the faceplate for inserting modules
|
||||
nestWall = 2
|
||||
nestWidth = faceplateWidth - (nestWall * 2)
|
||||
nestHeight = faceplateHeight - (nestWall * 2)
|
||||
nestDepth = faceplateDepth - nestWall
|
||||
nestShape = startProfile(faceplateFrontFace, at = [-nestWidth / 2, nestHeight / 2])
|
||||
|> xLine(length = nestWidth)
|
||||
|> yLine(length = -nestHeight)
|
||||
|> xLine(length = -nestWidth)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
nestVoid = extrude(nestShape, length = -nestDepth)
|
||||
|
||||
// Spacer block on the left side, used to position components correctly
|
||||
moduleHeight = nestHeight
|
||||
moduleWidth = nestHeight
|
||||
moduleDepth = nestHeight
|
||||
|
||||
leftSpacerWidth = moduleWidth * 1.5
|
||||
leftSpacerPosition = leftSpacerWidth / 2 - (nestWidth / 2)
|
||||
|
||||
fn boxModuleFn(width) {
|
||||
shape = startSketchOn(XZ)
|
||||
|> startProfile(%, at = [-width / 2, moduleHeight / 2])
|
||||
|> xLine(length = width)
|
||||
|> yLine(length = -moduleHeight)
|
||||
|> xLine(length = -width)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
body = extrude(shape, length = -moduleDepth)
|
||||
return body
|
||||
}
|
||||
leftSpacerShape = boxModuleFn(width = leftSpacerWidth)
|
||||
|> translate(
|
||||
%,
|
||||
x = leftSpacerPosition,
|
||||
y = 0,
|
||||
z = 0,
|
||||
)
|
||||
|
||||
// Module for power switch including front plate and red rocker button
|
||||
switchPosition = leftSpacerPosition + leftSpacerWidth / 2 + moduleWidth / 2
|
||||
switchWidth = moduleWidth
|
||||
|
||||
// Switch Body
|
||||
switchBody = boxModuleFn(width = moduleWidth)
|
||||
|
||||
// Switch Plate
|
||||
switchPlateWidth = 20
|
||||
switchPlateHeight = 30
|
||||
switchPlateThickness = 3
|
||||
switchPlateShape = startSketchOn(switchBody, face = END)
|
||||
|> startProfile(
|
||||
%,
|
||||
at = [
|
||||
-switchPlateWidth / 2,
|
||||
-switchPlateHeight / 2
|
||||
],
|
||||
)
|
||||
|> yLine(length = switchPlateHeight)
|
||||
|> xLine(length = switchPlateWidth)
|
||||
|> yLine(length = -switchPlateHeight)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
switchPlateBody = extrude(switchPlateShape, length = switchPlateThickness)
|
||||
|> translate(
|
||||
%,
|
||||
x = switchPosition,
|
||||
y = 0,
|
||||
z = 0,
|
||||
)
|
||||
|
||||
// Switch Button
|
||||
switchButtonHeight = 26
|
||||
switchButtonWidth = 15
|
||||
switchButtonShape = startSketchOn(offsetPlane(-YZ, offset = -switchButtonWidth / 2))
|
||||
|> startProfile(
|
||||
%,
|
||||
at = [
|
||||
switchPlateThickness,
|
||||
switchButtonHeight / 2
|
||||
],
|
||||
)
|
||||
|> line(end = [3, -1])
|
||||
|> arc(interiorAbsolute = [6, 0], endAbsolute = [12, -9])
|
||||
|> line(endAbsolute = [
|
||||
switchPlateThickness,
|
||||
-switchButtonHeight / 2
|
||||
])
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
switchButtonBody = extrude(switchButtonShape, length = switchButtonWidth)
|
||||
|> translate(
|
||||
%,
|
||||
x = switchPosition,
|
||||
y = 0,
|
||||
z = 0,
|
||||
)
|
||||
|> appearance(%, color = "#ff0000")
|
||||
|
||||
// Spacer between switch and plug modules for layout alignment
|
||||
secondSpacerWidth = moduleWidth / 2
|
||||
secondSpacerPosition = switchPosition + switchWidth / 2 + secondSpacerWidth / 2
|
||||
secondSpacerBody = boxModuleFn(width = secondSpacerWidth)
|
||||
|> translate(
|
||||
%,
|
||||
x = secondSpacerPosition,
|
||||
y = 0,
|
||||
z = 0,
|
||||
)
|
||||
|
||||
// European power plug modules with circular sockets and two-pin holes
|
||||
// 8 identical sockets, each with grounding notch and dual-pin recesses
|
||||
powerPlugWidth = moduleWidth
|
||||
powerPlugCount = 8
|
||||
powerPlugOveralWidth = powerPlugWidth * powerPlugCount
|
||||
firstPowerPlugPosition = secondSpacerPosition + secondSpacerWidth / 2 + powerPlugWidth / 2
|
||||
lastPowerPlugPosition = firstPowerPlugPosition + powerPlugWidth * (powerPlugCount - 1)
|
||||
powerPlugBody = boxModuleFn(width = powerPlugWidth)
|
||||
|> translate(
|
||||
%,
|
||||
x = firstPowerPlugPosition,
|
||||
y = 0,
|
||||
z = 0,
|
||||
)
|
||||
plugShape = startSketchOn(powerPlugBody, face = END)
|
||||
|> circle(%, center = [0, 0], radius = 17)
|
||||
|> translate(
|
||||
%,
|
||||
x = firstPowerPlugPosition,
|
||||
y = 0,
|
||||
z = 0,
|
||||
)
|
||||
plugBody = extrude(plugShape, length = -20)
|
||||
plugHoleDistance = 20
|
||||
plugHoleShape = startSketchOn(plugBody, face = START)
|
||||
|> circle(%, center = [-plugHoleDistance / 2, 0], radius = 3)
|
||||
|> translate(
|
||||
%,
|
||||
x = firstPowerPlugPosition,
|
||||
y = 0,
|
||||
z = 0,
|
||||
)
|
||||
|> patternLinear2d(
|
||||
%,
|
||||
instances = 2,
|
||||
distance = plugHoleDistance,
|
||||
axis = [1, 0],
|
||||
)
|
||||
plugHoleBody = extrude(plugHoleShape, length = -5)
|
||||
|> patternLinear3d(
|
||||
%,
|
||||
instances = powerPlugCount,
|
||||
distance = powerPlugWidth,
|
||||
axis = [1, 0, 0],
|
||||
)
|
||||
|
||||
// Rightmost spacer to fill in remaining horizontal space
|
||||
rightSpacerWidth = nestWidth / 2 - lastPowerPlugPosition - (powerPlugWidth / 2)
|
||||
rightSpacerPosition = lastPowerPlugPosition + powerPlugWidth / 2 + rightSpacerWidth / 2
|
||||
rightSpacerBody = boxModuleFn(width = rightSpacerWidth)
|
||||
|> translate(
|
||||
%,
|
||||
x = rightSpacerPosition,
|
||||
y = 0,
|
||||
z = 0,
|
||||
)
|
||||
|
||||
// Rack mounting holes on flanges, elongated for alignment flexibility
|
||||
holeWidth = 25
|
||||
holeDiameter = 5
|
||||
holeStraightSegment = holeWidth - holeDiameter
|
||||
holeVerticalDistance = faceplateHeight * 0.3
|
||||
|
||||
holeShapes = startProfile(
|
||||
faceplateFrontFace,
|
||||
at = [
|
||||
-holeStraightSegment / 2,
|
||||
holeDiameter / 2
|
||||
],
|
||||
)
|
||||
|> xLine(length = holeStraightSegment)
|
||||
|> tangentialArc(endAbsolute = [
|
||||
holeStraightSegment / 2,
|
||||
-holeDiameter / 2
|
||||
])
|
||||
|> xLine(length = -holeStraightSegment)
|
||||
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> translate(
|
||||
%,
|
||||
x = -faceplateWidth / 2 - (supportWidth / 2),
|
||||
y = 0,
|
||||
z = -holeVerticalDistance,
|
||||
)
|
||||
|> patternLinear2d(
|
||||
%,
|
||||
instances = 3,
|
||||
distance = holeVerticalDistance,
|
||||
axis = [0, 1],
|
||||
)
|
||||
|> patternLinear2d(
|
||||
%,
|
||||
instances = 2,
|
||||
distance = faceplateWidth + supportWidth,
|
||||
axis = [1, 0],
|
||||
)
|
||||
holeVoid = extrude(holeShapes, length = -supportThickness)
|
@ -1,214 +0,0 @@
|
||||
// Sash Window
|
||||
// A traditional wooden sash window with two vertically sliding panels and a central locking mechanism
|
||||
|
||||
// Set units in millimeters (mm)
|
||||
@settings(defaultLengthUnit = mm, kclVersion = 1.0)
|
||||
|
||||
// Window state: 0 for closed, 1 for open
|
||||
windowState = 0
|
||||
|
||||
// Basic window dimensions
|
||||
windowWidth = 500
|
||||
windowHeight = 1000
|
||||
|
||||
// Frame thickness and depth
|
||||
frameWidth = 30
|
||||
frameDepth = 50
|
||||
|
||||
// Number of divisions per sash (horizontal and vertical)
|
||||
sashOpeningCountHorizontal = 2
|
||||
sashOpeningCountVertical = 1
|
||||
|
||||
// Derived dimensions
|
||||
sashWidth = windowWidth - (frameWidth * 2)
|
||||
sashHeight = (windowHeight - (frameWidth * 2)) / 2 + frameWidth / 2
|
||||
sashDepth = frameDepth / 2 - 2
|
||||
sashTravelDistance = sashHeight * windowState * 0.8
|
||||
|
||||
// Function to create panel with frame and openings
|
||||
fn panelFn(plane, offset, width, height, depth, perimeter, divisionThickness, openingCountHorizontal, openingCountVertical) {
|
||||
// Create panel base shape
|
||||
panelPlane = startSketchOn(offsetPlane(XZ, offset = offset))
|
||||
panelShape = startProfile(panelPlane, at = [-width / 2, -height / 2])
|
||||
|> yLine(length = height)
|
||||
|> xLine(length = width)
|
||||
|> yLine(length = -height)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
panelBody = extrude(panelShape, length = depth)
|
||||
|
||||
// Create opening grid within the panel
|
||||
voidAreaWidth = width - (perimeter * 2)
|
||||
voidAreaHeight = height - (perimeter * 2)
|
||||
|
||||
divisionTotalThicknessHorizontal = divisionThickness * openingCountHorizontal - divisionThickness
|
||||
divisionTotalThicknessVertical = divisionThickness * openingCountVertical - divisionThickness
|
||||
voidWidth = (voidAreaWidth - divisionTotalThicknessHorizontal) / openingCountHorizontal
|
||||
voidHeight = (voidAreaHeight - divisionTotalThicknessVertical) / openingCountVertical
|
||||
|
||||
voidStepHorizontal = voidWidth + divisionThickness
|
||||
voidStepVertical = voidHeight + divisionThickness
|
||||
voidPlane = startSketchOn(panelBody, face = END)
|
||||
voidShape = startProfile(
|
||||
voidPlane,
|
||||
at = [
|
||||
-voidAreaWidth / 2,
|
||||
-voidAreaHeight / 2
|
||||
],
|
||||
)
|
||||
|> yLine(length = voidHeight)
|
||||
|> xLine(length = voidWidth)
|
||||
|> yLine(length = -voidHeight)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> patternLinear2d(
|
||||
%,
|
||||
instances = openingCountHorizontal,
|
||||
distance = voidStepHorizontal,
|
||||
axis = [1, 0],
|
||||
)
|
||||
|> patternLinear2d(
|
||||
%,
|
||||
instances = openingCountVertical,
|
||||
distance = voidStepVertical,
|
||||
axis = [0, 1],
|
||||
)
|
||||
voidBody = extrude(voidShape, length = -depth)
|
||||
|> appearance(color = "#a55e2c")
|
||||
return panelBody
|
||||
}
|
||||
|
||||
// Create main window frame
|
||||
frame = panelFn(
|
||||
plane = XZ,
|
||||
offset = -frameDepth / 2,
|
||||
width = windowWidth,
|
||||
height = windowHeight,
|
||||
depth = frameDepth,
|
||||
perimeter = frameWidth,
|
||||
divisionThickness = 10,
|
||||
openingCountHorizontal = 1,
|
||||
openingCountVertical = 1,
|
||||
)
|
||||
|
||||
// Create bottom sliding sash
|
||||
bottomSash = panelFn(
|
||||
plane = XZ,
|
||||
offset = (frameDepth / 2 - sashDepth) / 2,
|
||||
width = sashWidth,
|
||||
height = sashHeight,
|
||||
depth = sashDepth,
|
||||
perimeter = frameWidth,
|
||||
divisionThickness = 10,
|
||||
openingCountHorizontal = sashOpeningCountHorizontal,
|
||||
openingCountVertical = sashOpeningCountVertical,
|
||||
)
|
||||
|> translate(
|
||||
%,
|
||||
x = 0,
|
||||
y = 0,
|
||||
z = frameWidth / 2 - (sashHeight / 2),
|
||||
)
|
||||
|> translate(
|
||||
%,
|
||||
x = 0,
|
||||
y = 0,
|
||||
z = sashTravelDistance,
|
||||
) // open / close
|
||||
|
||||
// Latch mechanism on bottom sash
|
||||
// Create latch plate
|
||||
latchPlateWidth = 13
|
||||
latchPlateLength = 30
|
||||
latchPlateThickness = 1
|
||||
|
||||
latchPlatePlane = startSketchOn(offsetPlane(XY, offset = frameWidth / 2))
|
||||
latchPlateShape = startProfile(
|
||||
latchPlatePlane,
|
||||
at = [
|
||||
-latchPlateLength / 2,
|
||||
-latchPlateWidth / 2
|
||||
],
|
||||
)
|
||||
|> yLine(length = latchPlateWidth)
|
||||
|> xLine(length = latchPlateLength)
|
||||
|> yLine(length = -latchPlateWidth)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
latchPlateBody = extrude(latchPlateShape, length = latchPlateThickness)
|
||||
|> translate(
|
||||
%,
|
||||
x = 0,
|
||||
y = -frameDepth / 4,
|
||||
z = 0,
|
||||
)
|
||||
|> translate(
|
||||
%,
|
||||
x = 0,
|
||||
y = 0,
|
||||
z = sashTravelDistance,
|
||||
) // open / close
|
||||
|
||||
// Create latch cylinder
|
||||
latchCylinderHeight = 5
|
||||
latchCylinderPlane = startSketchOn(offsetPlane(latchPlatePlane, offset = latchPlateThickness))
|
||||
latchCylinderShape = startProfile(latchCylinderPlane, at = [40, -1])
|
||||
|> xLine(length = -35)
|
||||
|> arc(interiorAbsolute = [-5, 0], endAbsolute = [5, 1])
|
||||
|> xLine(length = 35)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
latchCylinderBody = extrude(latchCylinderShape, length = latchCylinderHeight)
|
||||
|> translate(
|
||||
%,
|
||||
x = 0,
|
||||
y = -frameDepth / 4,
|
||||
z = 0,
|
||||
)
|
||||
|> translate(
|
||||
%,
|
||||
x = 0,
|
||||
y = 0,
|
||||
z = sashTravelDistance,
|
||||
) // open / close
|
||||
|> rotate(
|
||||
%,
|
||||
roll = 0,
|
||||
pitch = 0,
|
||||
yaw = -90 * windowState,
|
||||
)
|
||||
|
||||
// Create top fixed sash
|
||||
topSash = panelFn(
|
||||
plane = XZ,
|
||||
offset = -(frameDepth / 2 - sashDepth) / 2 - sashDepth,
|
||||
width = sashWidth,
|
||||
height = sashHeight,
|
||||
depth = sashDepth,
|
||||
perimeter = frameWidth,
|
||||
divisionThickness = 10,
|
||||
openingCountHorizontal = sashOpeningCountHorizontal,
|
||||
openingCountVertical = sashOpeningCountVertical,
|
||||
)
|
||||
|> translate(
|
||||
%,
|
||||
x = 0,
|
||||
y = 0,
|
||||
z = sashHeight / 2 - (frameWidth / 2),
|
||||
)
|
||||
|
||||
// Create latch nut on the top sash
|
||||
latchNutPlane = startSketchOn(XZ)
|
||||
latchNutShape = startProfile(
|
||||
latchNutPlane,
|
||||
at = [
|
||||
-latchPlateLength / 2,
|
||||
-latchPlateWidth / 2
|
||||
],
|
||||
)
|
||||
|> yLine(length = latchPlateWidth)
|
||||
|> xLine(length = latchPlateLength)
|
||||
|> yLine(length = -latchPlateWidth)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
latchNutPlateBody = extrude(latchNutShape, length = latchPlateThickness)
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 98 KiB |
Before Width: | Height: | Size: 82 KiB |
Before Width: | Height: | Size: 216 KiB After Width: | Height: | Size: 216 KiB |
Before Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 61 KiB |
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 95 KiB |
Before Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 100 KiB |
Before Width: | Height: | Size: 178 KiB |
Before Width: | Height: | Size: 78 KiB |
@ -1,89 +0,0 @@
|
||||
// Shepherd’s Hook Bolt
|
||||
// A bent bolt with a curved hook, typically used for hanging or anchoring loads. The threaded end allows secure attachment to surfaces or materials, while the curved hook resists pull-out under tension.
|
||||
|
||||
// Set units in millimeters (mm)
|
||||
@settings(defaultLengthUnit = mm, kclVersion = 1.0)
|
||||
|
||||
// Define bolt geometry parameters
|
||||
boltDiameter = 5
|
||||
hookRadius = 12
|
||||
shankLength = 5
|
||||
threadedEndLength = 30
|
||||
nutDistance = 20
|
||||
hookStartAngle = 290
|
||||
hookEndAngle = 150
|
||||
|
||||
approximatePitch = boltDiameter * 0.15
|
||||
threadDepth = 0.6134 * approximatePitch
|
||||
innerRadius = boltDiameter / 2 - threadDepth
|
||||
boltNumberOfRevolutions = threadedEndLength / approximatePitch
|
||||
|
||||
// Helper values for computing geometry transitions between straight shaft and hook arc
|
||||
hypotenuse = hookRadius / cos(hookStartAngle - 270)
|
||||
side = sqrt(pow(hypotenuse, exp = 2) - pow(hookRadius, exp = 2))
|
||||
shankOffset = hypotenuse + side
|
||||
|
||||
// Converts polar coordinates to cartesian points for drawing arcs
|
||||
fn polarToCartesian(radius, angle) {
|
||||
x = radius * cos(angle)
|
||||
y = radius * sin(angle)
|
||||
return [x, y]
|
||||
}
|
||||
|
||||
// Create the hook and shank profile path
|
||||
// Includes straight segment and two connected arcs forming the hook
|
||||
hookProfilePlane = startSketchOn(XZ)
|
||||
hookProfileShape = startProfile(hookProfilePlane, at = [0, -shankOffset - shankLength])
|
||||
|> line(endAbsolute = [0, -shankOffset])
|
||||
|> tangentialArc(endAbsolute = polarToCartesian(radius = hookRadius, angle = hookStartAngle))
|
||||
|> tangentialArc(endAbsolute = polarToCartesian(radius = hookRadius, angle = hookEndAngle), tag = $hook)
|
||||
|
||||
// Create the circular cross-section used for sweeping along the hook path
|
||||
hookSectionPlane = offsetPlane(XY, offset = -shankOffset - shankLength)
|
||||
hookSectionShape = circle(hookSectionPlane, center = [0, 0], radius = boltDiameter / 2)
|
||||
|
||||
// Sweep the section along the hook profile to form the main body of the hook bolt
|
||||
hookBody = sweep(hookSectionShape, path = hookProfileShape, sectional = true)
|
||||
|
||||
// Add a cylindrical tip at the hook end
|
||||
tipPlane = startSketchOn(hookBody, face = END)
|
||||
tipShape = circle(
|
||||
tipPlane,
|
||||
center = [hookRadius, 0],
|
||||
radius = boltDiameter / 2,
|
||||
tag = $seg01,
|
||||
)
|
||||
tipBody = extrude(
|
||||
tipShape,
|
||||
length = hookRadius * 0.5,
|
||||
tagStart = $startTag,
|
||||
tagEnd = $capEnd001,
|
||||
)
|
||||
|> fillet(
|
||||
radius = boltDiameter / 4,
|
||||
tags = [
|
||||
getCommonEdge(faces = [seg01, capEnd001])
|
||||
],
|
||||
)
|
||||
|
||||
// Create the threaded end of the bolt
|
||||
|
||||
// Construct the triangular profile for thread cutting
|
||||
boltThreadSectionPlane = startSketchOn(XZ)
|
||||
boltThreadSectionShapeForRevolve = startProfile(
|
||||
boltThreadSectionPlane,
|
||||
at = [
|
||||
innerRadius,
|
||||
-shankOffset - shankLength - threadedEndLength
|
||||
],
|
||||
)
|
||||
|> line(end = [threadDepth, approximatePitch / 2])
|
||||
|> line(end = [-threadDepth, approximatePitch / 2])
|
||||
|> patternLinear2d(axis = [0, 1], instances = boltNumberOfRevolutions, distance = approximatePitch)
|
||||
|> xLine(length = -innerRadius * 0.9)
|
||||
|> yLine(length = -threadedEndLength)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|
||||
// Create a revolved solid representing the thread geometry by repeating and revolving the profile around the shaft
|
||||
boltThreadRevolve = revolve(boltThreadSectionShapeForRevolve, angle = 360, axis = Y)
|
@ -1,93 +0,0 @@
|
||||
// Spinning Highrise Tower
|
||||
// A conceptual high-rise tower with a central core and rotating floor slabs, demonstrating dynamic form through vertical repetition and transformation
|
||||
|
||||
|
||||
|
||||
|
||||
@settings(defaultLengthUnit = m, kclVersion = 1.0)
|
||||
|
||||
// Define global parameters for floor geometry and building layout
|
||||
floorCount = 17
|
||||
floorHeight = 5
|
||||
slabWidth = 30
|
||||
slabThickness = 0.5
|
||||
rotationAngleStep = 5
|
||||
handrailHeight = 1.2
|
||||
handrailThickness = 0.3
|
||||
balconyDepth = 3
|
||||
|
||||
// Calculate facade and core geometry from parameters
|
||||
facadeWidth = slabWidth - (balconyDepth * 2)
|
||||
facadeHeight = floorHeight - slabThickness
|
||||
coreHeight = floorCount * floorHeight - slabThickness
|
||||
frameSide = 0.1
|
||||
windowTargetWidth = 6
|
||||
windowTargetCount = facadeWidth / windowTargetWidth
|
||||
windowCount = round(windowTargetCount)
|
||||
windowWidth = facadeWidth / windowCount
|
||||
|
||||
// Helper function: Creates a box from a center plane with given width and height
|
||||
fn boxFn(plane, width, height) {
|
||||
shape = startSketchOn(plane)
|
||||
|> startProfile(%, at = [-width / 2, -width / 2])
|
||||
|> line(%, end = [0, width])
|
||||
|> line(%, end = [width, 0])
|
||||
|> line(%, end = [0, -width])
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close(%)
|
||||
body = extrude(shape, length = height)
|
||||
return body
|
||||
}
|
||||
|
||||
// Helper function: Defines transformation (translation and rotation) for each floor
|
||||
fn transformFn(@i) {
|
||||
return {
|
||||
translate = [0, 0, i * floorHeight],
|
||||
rotation = { angle = rotationAngleStep * i }
|
||||
}
|
||||
}
|
||||
|
||||
// Create building base
|
||||
baseThickness = 0.2
|
||||
baseSlab = boxFn(plane = XY, width = slabWidth, height = -baseThickness)
|
||||
|> appearance(%, color = "#dbd7d2")
|
||||
|
||||
// Create ground platform beneath the base
|
||||
groundSize = 50
|
||||
groundBody = boxFn(plane = offsetPlane(XY, offset = -baseThickness), width = groundSize, height = -5)
|
||||
|> appearance(%, color = "#3a3631")
|
||||
|
||||
// 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)
|
||||
slabVoidStart = -slabWidth / 2 + handrailThickness
|
||||
slabVoidWidth = slabWidth - (handrailThickness * 2)
|
||||
slabVoidShape = startSketchOn(slabAndHandrailGeometry, face = END)
|
||||
|> startProfile(%, at = [slabVoidStart, slabVoidStart])
|
||||
|> line(%, end = [0, slabVoidWidth])
|
||||
|> line(%, end = [slabVoidWidth, 0])
|
||||
|> line(%, end = [0, -slabVoidWidth])
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close(%)
|
||||
|
||||
// Generate and pattern slabs with voids across all floors
|
||||
slabBody = extrude(slabVoidShape, length = -handrailHeight)
|
||||
|> patternTransform(instances = floorCount, transform = transformFn)
|
||||
|> appearance(%, color = "#dbd7d2")
|
||||
|
||||
// Create structural core of the tower
|
||||
coreLength = 10
|
||||
coreWidth = 8
|
||||
core = startSketchOn(XY)
|
||||
|> startProfile(%, at = [-coreLength / 2, -coreWidth / 2])
|
||||
|> line(%, end = [0, coreWidth])
|
||||
|> line(%, end = [coreLength, 0])
|
||||
|> line(%, end = [-0.22, -coreWidth])
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close(%)
|
||||
|> extrude(%, length = coreHeight)
|
||||
|
||||
// Create facade panels for each floor
|
||||
facadeStart = facadeWidth / 2
|
||||
facadeGeometry = boxFn(plane = XY, width = facadeWidth, height = facadeHeight)
|
||||
|> patternTransform(instances = floorCount, transform = transformFn)
|
||||
|> appearance(%, color = "#151819")
|
@ -1,61 +0,0 @@
|
||||
// Thermal Block Insert
|
||||
// Interlocking insulation insert for masonry walls, designed with a tongue-and-groove profile for modular alignment and thermal efficiency
|
||||
|
||||
// Set units in millimeters (mm)
|
||||
@settings(defaultLengthUnit = mm, kclVersion = 1.0)
|
||||
|
||||
// Define overall dimensions of the insert block
|
||||
insertLength = 400
|
||||
insertHeight = 200
|
||||
insertThickness = 50
|
||||
|
||||
// Define tongue-and-groove profile parameters for interlocking geometry
|
||||
setbackFactor = 0.25 // spacing between tongues
|
||||
tongueTargetCount = insertLength / 80
|
||||
tongueCount = round(tongueTargetCount)
|
||||
tongueLength = insertLength / (tongueCount * (1 + setbackFactor * 2) + 1)
|
||||
tongueGap = tongueLength * setbackFactor * 2
|
||||
tongueStep = tongueLength + tongueGap
|
||||
tongueDepth = tongueLength * 0.5
|
||||
tongueSetback = tongueLength * setbackFactor
|
||||
|
||||
// Function to create one side of the repeating tongue geometry along the block edge
|
||||
fn tongueBlockFn() {
|
||||
tongueSingleBlock = xLine(length = tongueLength)
|
||||
|> line(end = [-tongueSetback, tongueDepth])
|
||||
|> xLine(length = tongueLength)
|
||||
|> line(end = [-tongueSetback, -tongueDepth])
|
||||
|> patternLinear2d(
|
||||
%,
|
||||
instances = tongueCount,
|
||||
distance = tongueStep,
|
||||
axis = [1, 0],
|
||||
)
|
||||
|> xLine(length = tongueLength)
|
||||
return tongueSingleBlock
|
||||
}
|
||||
|
||||
// Create top-side profile with tongues
|
||||
tongueShape = startSketchOn(XY)
|
||||
|> startProfile(%, at = [-insertLength / 2, insertThickness / 2])
|
||||
|> tongueBlockFn()
|
||||
|> yLine(length = -insertThickness / 2)
|
||||
|> xLine(length = -insertLength)
|
||||
|> close(%)
|
||||
|
||||
// Create bottom-side profile with grooves (inverse of tongue)
|
||||
grooveShape = startSketchOn(XY)
|
||||
|> startProfile(
|
||||
%,
|
||||
at = [
|
||||
-insertLength / 2,
|
||||
-insertThickness / 2 - tongueDepth
|
||||
],
|
||||
)
|
||||
|> tongueBlockFn()
|
||||
|> yLine(length = insertThickness / 2 + tongueDepth)
|
||||
|> xLine(length = -insertLength)
|
||||
|> close(%)
|
||||
|
||||
// Extrude both tongue and groove profiles to form the final thermal insert block
|
||||
insertShape = extrude([tongueShape, grooveShape], length = insertHeight)
|
20
rust/Cargo.lock
generated
@ -1815,7 +1815,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-bumper"
|
||||
version = "0.1.75"
|
||||
version = "0.1.74"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@ -1826,7 +1826,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-derive-docs"
|
||||
version = "0.1.75"
|
||||
version = "0.1.74"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"anyhow",
|
||||
@ -1845,7 +1845,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-directory-test-macro"
|
||||
version = "0.1.75"
|
||||
version = "0.1.74"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"proc-macro2",
|
||||
@ -1855,7 +1855,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-language-server"
|
||||
version = "0.2.75"
|
||||
version = "0.2.74"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@ -1876,7 +1876,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-language-server-release"
|
||||
version = "0.1.75"
|
||||
version = "0.1.74"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@ -1896,7 +1896,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-lib"
|
||||
version = "0.2.75"
|
||||
version = "0.2.74"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"approx 0.5.1",
|
||||
@ -1973,7 +1973,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-python-bindings"
|
||||
version = "0.3.75"
|
||||
version = "0.3.74"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"kcl-lib",
|
||||
@ -1988,7 +1988,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-test-server"
|
||||
version = "0.1.75"
|
||||
version = "0.1.74"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"hyper 0.14.32",
|
||||
@ -2001,7 +2001,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-to-core"
|
||||
version = "0.1.75"
|
||||
version = "0.1.74"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -2015,7 +2015,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-wasm-lib"
|
||||
version = "0.1.75"
|
||||
version = "0.1.74"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bson",
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
[package]
|
||||
name = "kcl-bumper"
|
||||
version = "0.1.75"
|
||||
version = "0.1.74"
|
||||
edition = "2021"
|
||||
repository = "https://github.com/KittyCAD/modeling-api"
|
||||
rust-version = "1.76"
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "kcl-derive-docs"
|
||||
description = "A tool for generating documentation from Rust derive macros"
|
||||
version = "0.1.75"
|
||||
version = "0.1.74"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/KittyCAD/modeling-app"
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "kcl-directory-test-macro"
|
||||
description = "A tool for generating tests from a directory of kcl files"
|
||||
version = "0.1.75"
|
||||
version = "0.1.74"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/KittyCAD/modeling-app"
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "kcl-language-server-release"
|
||||
version = "0.1.75"
|
||||
version = "0.1.74"
|
||||
edition = "2021"
|
||||
authors = ["KittyCAD Inc <kcl@kittycad.io>"]
|
||||
publish = false
|
||||
|
@ -2,7 +2,7 @@
|
||||
name = "kcl-language-server"
|
||||
description = "A language server for KCL."
|
||||
authors = ["KittyCAD Inc <kcl@kittycad.io>"]
|
||||
version = "0.2.75"
|
||||
version = "0.2.74"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "kcl-lib"
|
||||
description = "KittyCAD Language implementation and tools"
|
||||
version = "0.2.75"
|
||||
version = "0.2.74"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/KittyCAD/modeling-app"
|
||||
|
@ -2,7 +2,7 @@ mod cache;
|
||||
|
||||
use kcl_lib::{
|
||||
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.
|
||||
@ -441,15 +441,10 @@ async fn kcl_test_import_file_doesnt_exist() {
|
||||
model = cube"#;
|
||||
|
||||
let result = execute_and_snapshot(code, None).await;
|
||||
let err = result.unwrap_err();
|
||||
let err = err.as_kcl_error().unwrap();
|
||||
assert_eq!(err.message(), "File `thing.obj` does not exist.");
|
||||
assert!(result.is_err());
|
||||
assert_eq!(
|
||||
err.backtrace(),
|
||||
vec![BacktraceItem {
|
||||
source_range: SourceRange::new(0, 18, ModuleId::default()),
|
||||
fn_name: None,
|
||||
}]
|
||||
result.err().unwrap().to_string(),
|
||||
r#"semantic: KclErrorDetails { source_ranges: [SourceRange([0, 18, 0])], message: "File `thing.obj` does not exist." }"#
|
||||
);
|
||||
}
|
||||
|
||||
@ -524,18 +519,10 @@ import 'e2e/executor/inputs/cube.gltf'
|
||||
model = cube"#;
|
||||
|
||||
let result = execute_and_snapshot(code, None).await;
|
||||
let err = result.unwrap_err();
|
||||
let err = err.as_kcl_error().unwrap();
|
||||
assert!(result.is_err());
|
||||
assert_eq!(
|
||||
err.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,
|
||||
}]
|
||||
result.err().unwrap().to_string(),
|
||||
r#"semantic: KclErrorDetails { source_ranges: [SourceRange([32, 70, 0])], message: "The given format does not match the file extension. Expected: `gltf`, Given: `obj`" }"#
|
||||
);
|
||||
}
|
||||
|
||||
@ -1679,15 +1666,10 @@ example = extrude(exampleSketch, length = 10)
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, None).await;
|
||||
let err = result.unwrap_err();
|
||||
let err = err.as_kcl_error().unwrap();
|
||||
assert_eq!(err.message(), "Cannot have an x constrained angle of 90 degrees");
|
||||
assert!(result.is_err());
|
||||
assert_eq!(
|
||||
err.backtrace(),
|
||||
vec![BacktraceItem {
|
||||
source_range: SourceRange::new(70, 111, ModuleId::default()),
|
||||
fn_name: Some("angledLine".to_owned())
|
||||
}]
|
||||
result.err().unwrap().to_string(),
|
||||
r#"type: KclErrorDetails { source_ranges: [SourceRange([70, 111, 0])], message: "Cannot have an x constrained angle of 90 degrees" }"#
|
||||
);
|
||||
}
|
||||
|
||||
@ -1704,15 +1686,10 @@ example = extrude(exampleSketch, length = 10)
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, None).await;
|
||||
let err = result.unwrap_err();
|
||||
let err = err.as_kcl_error().unwrap();
|
||||
assert_eq!(err.message(), "Cannot have an x constrained angle of 270 degrees");
|
||||
assert!(result.is_err());
|
||||
assert_eq!(
|
||||
err.backtrace(),
|
||||
vec![BacktraceItem {
|
||||
source_range: SourceRange::new(70, 112, ModuleId::default()),
|
||||
fn_name: Some("angledLine".to_owned())
|
||||
}]
|
||||
result.err().unwrap().to_string(),
|
||||
r#"type: KclErrorDetails { source_ranges: [SourceRange([70, 112, 0])], message: "Cannot have an x constrained angle of 270 degrees" }"#
|
||||
);
|
||||
}
|
||||
|
||||
@ -1729,15 +1706,10 @@ example = extrude(exampleSketch, length = 10)
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, None).await;
|
||||
let err = result.unwrap_err();
|
||||
let err = err.as_kcl_error().unwrap();
|
||||
assert_eq!(err.message(), "Cannot have a y constrained angle of 0 degrees");
|
||||
assert!(result.is_err());
|
||||
assert_eq!(
|
||||
err.backtrace(),
|
||||
vec![BacktraceItem {
|
||||
source_range: SourceRange::new(70, 110, ModuleId::default()),
|
||||
fn_name: Some("angledLine".to_owned())
|
||||
}]
|
||||
result.err().unwrap().to_string(),
|
||||
r#"type: KclErrorDetails { source_ranges: [SourceRange([70, 110, 0])], message: "Cannot have a y constrained angle of 0 degrees" }"#
|
||||
);
|
||||
}
|
||||
|
||||
@ -1754,15 +1726,10 @@ example = extrude(exampleSketch, length = 10)
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, None).await;
|
||||
let err = result.unwrap_err();
|
||||
let err = err.as_kcl_error().unwrap();
|
||||
assert_eq!(err.message(), "Cannot have a y constrained angle of 180 degrees");
|
||||
assert!(result.is_err());
|
||||
assert_eq!(
|
||||
err.backtrace(),
|
||||
vec![BacktraceItem {
|
||||
source_range: SourceRange::new(70, 112, ModuleId::default()),
|
||||
fn_name: Some("angledLine".to_owned())
|
||||
}]
|
||||
result.err().unwrap().to_string(),
|
||||
r#"type: KclErrorDetails { source_ranges: [SourceRange([70, 112, 0])], message: "Cannot have a y constrained angle of 180 degrees" }"#
|
||||
);
|
||||
}
|
||||
|
||||
@ -1779,15 +1746,10 @@ extrusion = extrude(sketch001, length = 10)
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, None).await;
|
||||
let err = result.unwrap_err();
|
||||
let err = err.as_kcl_error().unwrap();
|
||||
assert_eq!(err.message(), "Cannot have an x constrained angle of 90 degrees");
|
||||
assert!(result.is_err());
|
||||
assert_eq!(
|
||||
err.backtrace(),
|
||||
vec![BacktraceItem {
|
||||
source_range: SourceRange::new(66, 116, ModuleId::default()),
|
||||
fn_name: Some("angledLine".to_owned())
|
||||
}]
|
||||
result.err().unwrap().to_string(),
|
||||
r#"type: KclErrorDetails { source_ranges: [SourceRange([66, 116, 0])], message: "Cannot have an x constrained angle of 90 degrees" }"#
|
||||
);
|
||||
}
|
||||
|
||||
@ -1795,7 +1757,7 @@ extrusion = extrude(sketch001, length = 10)
|
||||
async fn kcl_test_angled_line_of_x_length_270() {
|
||||
let code = r#"sketch001 = startSketchOn(XZ)
|
||||
|> 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)
|
||||
|> line(end = [0, -5])
|
||||
|> close(tag = $edge3)
|
||||
@ -1804,15 +1766,10 @@ extrusion = extrude(sketch001, length = 10)
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, None).await;
|
||||
let err = result.unwrap_err();
|
||||
let err = err.as_kcl_error().unwrap();
|
||||
assert_eq!(err.message(), "Cannot have an x constrained angle of 270 degrees");
|
||||
assert!(result.is_err());
|
||||
assert_eq!(
|
||||
err.backtrace(),
|
||||
vec![BacktraceItem {
|
||||
source_range: SourceRange::new(66, 117, ModuleId::default()),
|
||||
fn_name: Some("angledLine".to_owned())
|
||||
}]
|
||||
result.err().unwrap().to_string(),
|
||||
r#"type: KclErrorDetails { source_ranges: [SourceRange([66, 116, 0])], message: "Cannot have an x constrained angle of 90 degrees" }"#
|
||||
);
|
||||
}
|
||||
|
||||
@ -1831,15 +1788,10 @@ example = extrude(exampleSketch, length = 10)
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, None).await;
|
||||
let err = result.unwrap_err();
|
||||
let err = err.as_kcl_error().unwrap();
|
||||
assert_eq!(err.message(), "Cannot have a y constrained angle of 0 degrees");
|
||||
assert!(result.is_err());
|
||||
assert_eq!(
|
||||
err.backtrace(),
|
||||
vec![BacktraceItem {
|
||||
source_range: SourceRange::new(95, 130, ModuleId::default()),
|
||||
fn_name: Some("angledLine".to_owned())
|
||||
}]
|
||||
result.err().unwrap().to_string(),
|
||||
r#"type: KclErrorDetails { source_ranges: [SourceRange([95, 130, 0])], message: "Cannot have a y constrained angle of 0 degrees" }"#
|
||||
);
|
||||
}
|
||||
|
||||
@ -1858,15 +1810,10 @@ example = extrude(exampleSketch, length = 10)
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, None).await;
|
||||
let err = result.unwrap_err();
|
||||
let err = err.as_kcl_error().unwrap();
|
||||
assert_eq!(err.message(), "Cannot have a y constrained angle of 180 degrees");
|
||||
assert!(result.is_err());
|
||||
assert_eq!(
|
||||
err.backtrace(),
|
||||
vec![BacktraceItem {
|
||||
source_range: SourceRange::new(95, 132, ModuleId::default()),
|
||||
fn_name: Some("angledLine".to_owned())
|
||||
}]
|
||||
result.err().unwrap().to_string(),
|
||||
r#"type: KclErrorDetails { source_ranges: [SourceRange([95, 132, 0])], message: "Cannot have a y constrained angle of 180 degrees" }"#
|
||||
);
|
||||
}
|
||||
|
||||
@ -1885,15 +1832,10 @@ example = extrude(exampleSketch, length = 10)
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, None).await;
|
||||
let err = result.unwrap_err();
|
||||
let err = err.as_kcl_error().unwrap();
|
||||
assert_eq!(err.message(), "Cannot have a y constrained angle of 180 degrees");
|
||||
assert!(result.is_err());
|
||||
assert_eq!(
|
||||
err.backtrace(),
|
||||
vec![BacktraceItem {
|
||||
source_range: SourceRange::new(95, 133, ModuleId::default()),
|
||||
fn_name: Some("angledLine".to_owned())
|
||||
}]
|
||||
result.err().unwrap().to_string(),
|
||||
r#"type: KclErrorDetails { source_ranges: [SourceRange([95, 133, 0])], message: "Cannot have a y constrained angle of 180 degrees" }"#
|
||||
);
|
||||
}
|
||||
|
||||
@ -1907,31 +1849,10 @@ someFunction('INVALID')
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, None).await;
|
||||
let err = result.unwrap_err();
|
||||
let err = err.as_kcl_error().unwrap();
|
||||
assert!(result.is_err());
|
||||
assert_eq!(
|
||||
err.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,
|
||||
},
|
||||
]
|
||||
result.err().unwrap().to_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" }"#
|
||||
);
|
||||
}
|
||||
|
||||
@ -1952,14 +1873,12 @@ async fn kcl_test_error_no_auth_websocket() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot_no_auth(code, None).await;
|
||||
let err = result.unwrap_err();
|
||||
let err = err.as_kcl_error().unwrap();
|
||||
assert!(
|
||||
err.message()
|
||||
.contains("Please send the following object over this websocket"),
|
||||
"actual: {}",
|
||||
err.message()
|
||||
);
|
||||
assert!(result.is_err());
|
||||
assert!(result
|
||||
.err()
|
||||
.unwrap()
|
||||
.to_string()
|
||||
.contains("Please send the following object over this websocket"));
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
@ -995,7 +995,7 @@ mod tests {
|
||||
let snippet = pattern_fn.to_autocomplete_snippet().unwrap();
|
||||
assert_eq!(
|
||||
snippet,
|
||||
r#"patternCircular3d(${0:%}, instances = ${1:10}, axis = [${2:3.14}, ${3:3.14}, ${4:3.14}], center = [${5:3.14}, ${6:3.14}, ${7:3.14}])"#
|
||||
r#"patternCircular3d(${0:%}, instances = ${1:10}, axis = [${2:3.14}, ${3:3.14}, ${4:3.14}], center = [${5:3.14}, ${6:3.14}, ${7:3.14}], arcDegrees = ${8:3.14}, rotateDuplicates = ${9:false})"#
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -439,7 +439,12 @@ impl EngineManager for EngineConnection {
|
||||
request_sent: tx,
|
||||
})
|
||||
.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;
|
||||
Ok(())
|
||||
@ -474,25 +479,25 @@ impl EngineManager for EngineConnection {
|
||||
})
|
||||
.await
|
||||
.map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
format!("Failed to send modeling command: {}", e),
|
||||
vec![source_range],
|
||||
))
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to send modeling command: {}", e),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
// Wait for the request to be sent.
|
||||
rx.await
|
||||
.map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
format!("could not send request to the engine actor: {e}"),
|
||||
vec![source_range],
|
||||
))
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("could not send request to the engine actor: {e}"),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?
|
||||
.map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
format!("could not send request to the engine: {e}"),
|
||||
vec![source_range],
|
||||
))
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("could not send request to the engine: {e}"),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
@ -516,15 +521,15 @@ impl EngineManager for EngineConnection {
|
||||
// Check if we have any pending errors.
|
||||
let pe = self.pending_errors.read().await;
|
||||
if !pe.is_empty() {
|
||||
return Err(KclError::Engine(KclErrorDetails::new(
|
||||
pe.join(", ").to_string(),
|
||||
vec![source_range],
|
||||
)));
|
||||
return Err(KclError::Engine(KclErrorDetails {
|
||||
message: pe.join(", ").to_string(),
|
||||
source_ranges: vec![source_range],
|
||||
}));
|
||||
} else {
|
||||
return Err(KclError::Engine(KclErrorDetails::new(
|
||||
"Modeling command failed: websocket closed early".to_string(),
|
||||
vec![source_range],
|
||||
)));
|
||||
return Err(KclError::Engine(KclErrorDetails {
|
||||
message: "Modeling command failed: websocket closed early".to_string(),
|
||||
source_ranges: vec![source_range],
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@ -543,10 +548,10 @@ impl EngineManager for EngineConnection {
|
||||
}
|
||||
}
|
||||
|
||||
Err(KclError::Engine(KclErrorDetails::new(
|
||||
format!("Modeling command timed out `{}`", id),
|
||||
vec![source_range],
|
||||
)))
|
||||
Err(KclError::Engine(KclErrorDetails {
|
||||
message: format!("Modeling command timed out `{}`", id),
|
||||
source_ranges: vec![source_range],
|
||||
}))
|
||||
}
|
||||
|
||||
async fn get_session_data(&self) -> Option<ModelingSessionData> {
|
||||
|
@ -147,27 +147,32 @@ impl EngineConnection {
|
||||
id_to_source_range: HashMap<uuid::Uuid, SourceRange>,
|
||||
) -> Result<(), KclError> {
|
||||
let source_range_str = serde_json::to_string(&source_range).map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
format!("Failed to serialize source range: {:?}", e),
|
||||
vec![source_range],
|
||||
))
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to serialize source range: {:?}", e),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
let cmd_str = serde_json::to_string(&cmd).map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
format!("Failed to serialize modeling command: {:?}", e),
|
||||
vec![source_range],
|
||||
))
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to serialize modeling command: {:?}", e),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
let id_to_source_range_str = serde_json::to_string(&id_to_source_range).map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
format!("Failed to serialize id to source range: {:?}", e),
|
||||
vec![source_range],
|
||||
))
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to serialize id to source range: {:?}", e),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
self.manager
|
||||
.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(())
|
||||
}
|
||||
@ -180,28 +185,33 @@ impl EngineConnection {
|
||||
id_to_source_range: HashMap<uuid::Uuid, SourceRange>,
|
||||
) -> Result<WebSocketResponse, KclError> {
|
||||
let source_range_str = serde_json::to_string(&source_range).map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
format!("Failed to serialize source range: {:?}", e),
|
||||
vec![source_range],
|
||||
))
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to serialize source range: {:?}", e),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
let cmd_str = serde_json::to_string(&cmd).map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
format!("Failed to serialize modeling command: {:?}", e),
|
||||
vec![source_range],
|
||||
))
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to serialize modeling command: {:?}", e),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
let id_to_source_range_str = serde_json::to_string(&id_to_source_range).map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
format!("Failed to serialize id to source range: {:?}", e),
|
||||
vec![source_range],
|
||||
))
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to serialize id to source range: {:?}", e),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
let promise = self
|
||||
.manager
|
||||
.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| {
|
||||
// Try to parse the error as an engine error.
|
||||
@ -209,52 +219,53 @@ impl EngineConnection {
|
||||
if let Ok(kittycad_modeling_cmds::websocket::FailureWebSocketResponse { errors, .. }) =
|
||||
serde_json::from_str(&err_str)
|
||||
{
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
errors.iter().map(|e| e.message.clone()).collect::<Vec<_>>().join("\n"),
|
||||
vec![source_range],
|
||||
))
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: errors.iter().map(|e| e.message.clone()).collect::<Vec<_>>().join("\n"),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
} else if let Ok(data) =
|
||||
serde_json::from_str::<Vec<kittycad_modeling_cmds::websocket::FailureWebSocketResponse>>(&err_str)
|
||||
{
|
||||
if let Some(data) = data.first() {
|
||||
// It could also be an array of responses.
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
data.errors
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: data
|
||||
.errors
|
||||
.iter()
|
||||
.map(|e| e.message.clone())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n"),
|
||||
vec![source_range],
|
||||
))
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
} else {
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
"Received empty response from engine".into(),
|
||||
vec![source_range],
|
||||
))
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: "Received empty response from engine".into(),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
}
|
||||
} else {
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
format!("Failed to wait for promise from send modeling command: {:?}", e),
|
||||
vec![source_range],
|
||||
))
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to wait for promise from send modeling command: {:?}", e),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
}
|
||||
})?;
|
||||
|
||||
if value.is_null() || value.is_undefined() {
|
||||
return Err(KclError::Engine(KclErrorDetails::new(
|
||||
"Received null or undefined response from engine".into(),
|
||||
vec![source_range],
|
||||
)));
|
||||
return Err(KclError::Engine(KclErrorDetails {
|
||||
message: "Received null or undefined response from engine".into(),
|
||||
source_ranges: vec![source_range],
|
||||
}));
|
||||
}
|
||||
|
||||
// Convert JsValue to a Uint8Array
|
||||
let data = js_sys::Uint8Array::from(value);
|
||||
|
||||
let ws_result: WebSocketResponse = bson::from_slice(&data.to_vec()).map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
format!("Failed to deserialize bson response from engine: {:?}", e),
|
||||
vec![source_range],
|
||||
))
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to deserialize bson response from engine: {:?}", e),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
Ok(ws_result)
|
||||
@ -305,16 +316,18 @@ impl crate::engine::EngineManager for EngineConnection {
|
||||
*self.default_planes.write().await = Some(new_planes);
|
||||
|
||||
// Start a new session.
|
||||
let promise = self
|
||||
.manager
|
||||
.start_new_session()
|
||||
.map_err(|e| KclError::Engine(KclErrorDetails::new(e.to_string().into(), vec![source_range])))?;
|
||||
let promise = self.manager.start_new_session().map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: e.to_string().into(),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
crate::wasm::JsFuture::from(promise).await.map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
format!("Failed to wait for promise from start new session: {:?}", e),
|
||||
vec![source_range],
|
||||
))
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to wait for promise from start new session: {:?}", e),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
|
@ -276,10 +276,10 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
{
|
||||
let duration = instant::Duration::from_millis(1);
|
||||
wasm_timer::Delay::new(duration).await.map_err(|err| {
|
||||
KclError::Internal(KclErrorDetails::new(
|
||||
format!("Failed to sleep: {:?}", err),
|
||||
vec![source_range],
|
||||
))
|
||||
KclError::Internal(KclErrorDetails {
|
||||
message: format!("Failed to sleep: {:?}", err),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@ -293,10 +293,10 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
Err(KclError::Engine(KclErrorDetails::new(
|
||||
"async command timed out".to_string(),
|
||||
vec![source_range],
|
||||
)))
|
||||
Err(KclError::Engine(KclErrorDetails {
|
||||
message: "async command timed out".to_string(),
|
||||
source_ranges: vec![source_range],
|
||||
}))
|
||||
}
|
||||
|
||||
/// 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);
|
||||
}
|
||||
_ => {
|
||||
return Err(KclError::Engine(KclErrorDetails::new(
|
||||
format!("The request is not a modeling command: {:?}", req),
|
||||
vec![*range],
|
||||
)));
|
||||
return Err(KclError::Engine(KclErrorDetails {
|
||||
message: format!("The request is not a modeling command: {:?}", req),
|
||||
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)
|
||||
} else {
|
||||
// We should never get here.
|
||||
Err(KclError::Engine(KclErrorDetails::new(
|
||||
format!("Failed to get batch response: {:?}", response),
|
||||
vec![source_range],
|
||||
)))
|
||||
Err(KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to get batch response: {:?}", response),
|
||||
source_ranges: vec![source_range],
|
||||
}))
|
||||
}
|
||||
}
|
||||
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
|
||||
// an error.
|
||||
let source_range = id_to_source_range.get(cmd_id.as_ref()).cloned().ok_or_else(|| {
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
format!("Failed to get source range for command ID: {:?}", cmd_id),
|
||||
vec![],
|
||||
))
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to get source range for command ID: {:?}", cmd_id),
|
||||
source_ranges: vec![],
|
||||
})
|
||||
})?;
|
||||
let ws_resp = self
|
||||
.inner_send_modeling_cmd(cmd_id.into(), source_range, final_req, id_to_source_range)
|
||||
.await?;
|
||||
self.parse_websocket_response(ws_resp, source_range)
|
||||
}
|
||||
_ => Err(KclError::Engine(KclErrorDetails::new(
|
||||
format!("The final request is not a modeling command: {:?}", final_req),
|
||||
vec![source_range],
|
||||
))),
|
||||
_ => Err(KclError::Engine(KclErrorDetails {
|
||||
message: format!("The final request is not a modeling command: {:?}", final_req),
|
||||
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 {
|
||||
let info = DEFAULT_PLANE_INFO.get(&name).ok_or_else(|| {
|
||||
// We should never get here.
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
format!("Failed to get default plane info for: {:?}", name),
|
||||
vec![source_range],
|
||||
))
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to get default plane info for: {:?}", name),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
planes.insert(
|
||||
name,
|
||||
@ -763,14 +763,15 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
WebSocketResponse::Success(success) => Ok(success.resp),
|
||||
WebSocketResponse::Failure(fail) => {
|
||||
let _request_id = fail.request_id;
|
||||
Err(KclError::Engine(KclErrorDetails::new(
|
||||
fail.errors
|
||||
Err(KclError::Engine(KclErrorDetails {
|
||||
message: fail
|
||||
.errors
|
||||
.iter()
|
||||
.map(|e| e.message.clone())
|
||||
.collect::<Vec<_>>()
|
||||
.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 } => {
|
||||
// Get the source range for the command.
|
||||
let source_range = id_to_source_range.get(cmd_id).cloned().ok_or_else(|| {
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
format!("Failed to get source range for command ID: {:?}", cmd_id),
|
||||
vec![],
|
||||
))
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to get source range for command ID: {:?}", cmd_id),
|
||||
source_ranges: vec![],
|
||||
})
|
||||
})?;
|
||||
return Err(KclError::Engine(KclErrorDetails::new(
|
||||
errors.iter().map(|e| e.message.clone()).collect::<Vec<_>>().join("\n"),
|
||||
vec![source_range],
|
||||
)));
|
||||
return Err(KclError::Engine(KclErrorDetails {
|
||||
message: errors.iter().map(|e| e.message.clone()).collect::<Vec<_>>().join("\n"),
|
||||
source_ranges: vec![source_range],
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return an error that we did not get an error or the response we wanted.
|
||||
// This should never happen but who knows.
|
||||
Err(KclError::Engine(KclErrorDetails::new(
|
||||
format!("Failed to find response for command ID: {:?}", id),
|
||||
vec![],
|
||||
)))
|
||||
Err(KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to find response for command ID: {:?}", id),
|
||||
source_ranges: vec![],
|
||||
}))
|
||||
}
|
||||
|
||||
async fn modify_grid(
|
||||
|
@ -380,39 +380,20 @@ impl miette::Diagnostic for Report {
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, ts_rs::TS, Clone, PartialEq, Eq, thiserror::Error, miette::Diagnostic)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[error("{message}")]
|
||||
#[ts(export)]
|
||||
pub struct KclErrorDetails {
|
||||
#[serde(rename = "sourceRanges")]
|
||||
#[label(collection, "Errors")]
|
||||
pub source_ranges: Vec<SourceRange>,
|
||||
pub backtrace: Vec<BacktraceItem>,
|
||||
#[serde(rename = "msg")]
|
||||
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 {
|
||||
pub fn internal(message: String) -> KclError {
|
||||
KclError::Internal(KclErrorDetails {
|
||||
source_ranges: Default::default(),
|
||||
backtrace: Default::default(),
|
||||
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 {
|
||||
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) => {
|
||||
e.backtrace = source_ranges
|
||||
.iter()
|
||||
.map(|s| BacktraceItem {
|
||||
source_range: *s,
|
||||
fn_name: None,
|
||||
})
|
||||
.collect();
|
||||
e.source_ranges = source_ranges;
|
||||
}
|
||||
KclError::Lexical(e) => e.source_ranges = source_ranges,
|
||||
KclError::Syntax(e) => e.source_ranges = source_ranges,
|
||||
KclError::Semantic(e) => e.source_ranges = source_ranges,
|
||||
KclError::ImportCycle(e) => e.source_ranges = source_ranges,
|
||||
KclError::Type(e) => e.source_ranges = source_ranges,
|
||||
KclError::Io(e) => e.source_ranges = source_ranges,
|
||||
KclError::Unexpected(e) => e.source_ranges = source_ranges,
|
||||
KclError::ValueAlreadyDefined(e) => e.source_ranges = source_ranges,
|
||||
KclError::UndefinedValue(e) => e.source_ranges = source_ranges,
|
||||
KclError::InvalidExpression(e) => e.source_ranges = source_ranges,
|
||||
KclError::Engine(e) => e.source_ranges = source_ranges,
|
||||
KclError::Internal(e) => e.source_ranges = source_ranges,
|
||||
}
|
||||
|
||||
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();
|
||||
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;
|
||||
}
|
||||
}
|
||||
KclError::Lexical(e) => e.source_ranges.extend(source_ranges),
|
||||
KclError::Syntax(e) => e.source_ranges.extend(source_ranges),
|
||||
KclError::Semantic(e) => e.source_ranges.extend(source_ranges),
|
||||
KclError::ImportCycle(e) => e.source_ranges.extend(source_ranges),
|
||||
KclError::Type(e) => e.source_ranges.extend(source_ranges),
|
||||
KclError::Io(e) => e.source_ranges.extend(source_ranges),
|
||||
KclError::Unexpected(e) => e.source_ranges.extend(source_ranges),
|
||||
KclError::ValueAlreadyDefined(e) => e.source_ranges.extend(source_ranges),
|
||||
KclError::UndefinedValue(e) => e.source_ranges.extend(source_ranges),
|
||||
KclError::InvalidExpression(e) => e.source_ranges.extend(source_ranges),
|
||||
KclError::Engine(e) => e.source_ranges.extend(source_ranges),
|
||||
KclError::Internal(e) => e.source_ranges.extend(source_ranges),
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -647,7 +551,6 @@ impl From<pyo3::PyErr> for KclError {
|
||||
fn from(error: pyo3::PyErr) -> Self {
|
||||
KclError::Internal(KclErrorDetails {
|
||||
source_ranges: vec![],
|
||||
backtrace: Default::default(),
|
||||
message: error.to_string(),
|
||||
})
|
||||
}
|
||||
@ -726,13 +629,8 @@ impl CompilationError {
|
||||
|
||||
impl From<CompilationError> for KclErrorDetails {
|
||||
fn from(err: CompilationError) -> Self {
|
||||
let backtrace = vec![BacktraceItem {
|
||||
source_range: err.source_range,
|
||||
fn_name: None,
|
||||
}];
|
||||
KclErrorDetails {
|
||||
source_ranges: vec![err.source_range],
|
||||
backtrace,
|
||||
message: err.message,
|
||||
}
|
||||
}
|
||||
|
@ -70,10 +70,10 @@ pub(super) fn expect_properties<'a>(
|
||||
) -> Result<&'a [Node<ObjectProperty>], KclError> {
|
||||
assert_eq!(annotation.name().unwrap(), for_key);
|
||||
Ok(&**annotation.properties.as_ref().ok_or_else(|| {
|
||||
KclError::Semantic(KclErrorDetails::new(
|
||||
format!("Empty `{for_key}` annotation"),
|
||||
vec![annotation.as_source_range()],
|
||||
))
|
||||
KclError::Semantic(KclErrorDetails {
|
||||
message: format!("Empty `{for_key}` annotation"),
|
||||
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(
|
||||
"Unexpected settings value, expected a simple name, e.g., `mm`".to_owned(),
|
||||
vec![expr.into()],
|
||||
)))
|
||||
Err(KclError::Semantic(KclErrorDetails {
|
||||
message: "Unexpected settings value, expected a simple name, e.g., `mm`".to_owned(),
|
||||
source_ranges: vec![expr.into()],
|
||||
}))
|
||||
}
|
||||
|
||||
// Returns the unparsed number literal.
|
||||
@ -98,10 +98,10 @@ pub(super) fn expect_number(expr: &Expr) -> Result<String, KclError> {
|
||||
}
|
||||
}
|
||||
|
||||
Err(KclError::Semantic(KclErrorDetails::new(
|
||||
"Unexpected settings value, expected a number, e.g., `1.0`".to_owned(),
|
||||
vec![expr.into()],
|
||||
)))
|
||||
Err(KclError::Semantic(KclErrorDetails {
|
||||
message: "Unexpected settings value, expected a number, e.g., `1.0`".to_owned(),
|
||||
source_ranges: vec![expr.into()],
|
||||
}))
|
||||
}
|
||||
|
||||
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 let Some(s) = p.value.ident_name() {
|
||||
return Impl::from_str(s).map(Some).map_err(|_| {
|
||||
KclError::Semantic(KclErrorDetails::new(
|
||||
format!(
|
||||
KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"Invalid value for {} attribute, expected one of: {}",
|
||||
IMPL,
|
||||
IMPL_VALUES.join(", ")
|
||||
),
|
||||
vec![source_range],
|
||||
))
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -139,12 +139,12 @@ impl UnitLen {
|
||||
"inch" | "in" => Ok(UnitLen::Inches),
|
||||
"ft" => Ok(UnitLen::Feet),
|
||||
"yd" => Ok(UnitLen::Yards),
|
||||
value => Err(KclError::Semantic(KclErrorDetails::new(
|
||||
format!(
|
||||
value => Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"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 {
|
||||
"deg" => Ok(UnitAngle::Degrees),
|
||||
"rad" => Ok(UnitAngle::Radians),
|
||||
value => Err(KclError::Semantic(KclErrorDetails::new(
|
||||
format!("Unexpected value for angle units: `{value}`; expected one of `deg`, `rad`"),
|
||||
vec![source_range],
|
||||
))),
|
||||
value => Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!("Unexpected value for angle units: `{value}`; expected one of `deg`, `rad`"),
|
||||
source_ranges: vec![source_range],
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,17 +2,18 @@ use fnv::FnvHashMap;
|
||||
use indexmap::IndexMap;
|
||||
use kittycad_modeling_cmds::{
|
||||
self as kcmc,
|
||||
id::ModelingCmdId,
|
||||
ok_response::OkModelingCmdResponse,
|
||||
shared::ExtrusionFaceCapType,
|
||||
websocket::{BatchResponse, OkWebSocketResponseData, WebSocketResponse},
|
||||
EnableSketchMode, ModelingCmd,
|
||||
};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{ser::SerializeSeq, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
errors::KclErrorDetails,
|
||||
execution::ArtifactId,
|
||||
parsing::ast::types::{Node, Program},
|
||||
KclError, NodePath, SourceRange,
|
||||
};
|
||||
@ -57,6 +58,52 @@ impl PartialOrd for ArtifactCommand {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, Ord, PartialOrd, Hash, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export_to = "Artifact.ts")]
|
||||
pub struct ArtifactId(Uuid);
|
||||
|
||||
impl ArtifactId {
|
||||
pub fn new(uuid: Uuid) -> Self {
|
||||
Self(uuid)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Uuid> for ArtifactId {
|
||||
fn from(uuid: Uuid) -> Self {
|
||||
Self::new(uuid)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Uuid> for ArtifactId {
|
||||
fn from(uuid: &Uuid) -> Self {
|
||||
Self::new(*uuid)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ArtifactId> for Uuid {
|
||||
fn from(id: ArtifactId) -> Self {
|
||||
id.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ArtifactId> for Uuid {
|
||||
fn from(id: &ArtifactId) -> Self {
|
||||
id.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ModelingCmdId> for ArtifactId {
|
||||
fn from(id: ModelingCmdId) -> Self {
|
||||
Self::new(*id.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ModelingCmdId> for ArtifactId {
|
||||
fn from(id: &ModelingCmdId) -> Self {
|
||||
Self::new(*id.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
pub type DummyPathToNode = Vec<()>;
|
||||
|
||||
fn serialize_dummy_path_to_node<S>(_path_to_node: &DummyPathToNode, serializer: S) -> Result<S::Ok, S::Error>
|
||||
@ -941,10 +988,12 @@ fn artifacts_to_update(
|
||||
ModelingCmd::StartPath(_) => {
|
||||
let mut return_arr = Vec::new();
|
||||
let current_plane_id = path_to_plane_id_map.get(&artifact_command.cmd_id).ok_or_else(|| {
|
||||
KclError::Internal(KclErrorDetails::new(
|
||||
format!("Expected a current plane ID when processing StartPath command, but we have none: {id:?}"),
|
||||
vec![range],
|
||||
))
|
||||
KclError::Internal(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a current plane ID when processing StartPath command, but we have none: {id:?}"
|
||||
),
|
||||
source_ranges: vec![range],
|
||||
})
|
||||
})?;
|
||||
return_arr.push(Artifact::Path(Path {
|
||||
id,
|
||||
@ -1063,10 +1112,10 @@ fn artifacts_to_update(
|
||||
// TODO: Using the first one. Make sure to revisit this
|
||||
// choice, don't think it matters for now.
|
||||
path_id: ArtifactId::new(*loft_cmd.section_ids.first().ok_or_else(|| {
|
||||
KclError::Internal(KclErrorDetails::new(
|
||||
format!("Expected at least one section ID in Loft command: {id:?}; cmd={cmd:?}"),
|
||||
vec![range],
|
||||
))
|
||||
KclError::Internal(KclErrorDetails {
|
||||
message: format!("Expected at least one section ID in Loft command: {id:?}; cmd={cmd:?}"),
|
||||
source_ranges: vec![range],
|
||||
})
|
||||
})?),
|
||||
surface_ids: Vec::new(),
|
||||
edge_ids: Vec::new(),
|
||||
@ -1106,12 +1155,12 @@ fn artifacts_to_update(
|
||||
};
|
||||
last_path = Some(path);
|
||||
let path_sweep_id = path.sweep_id.ok_or_else(|| {
|
||||
KclError::Internal(KclErrorDetails::new(
|
||||
format!(
|
||||
KclError::Internal(KclErrorDetails {
|
||||
message:format!(
|
||||
"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| {
|
||||
if let Artifact::StartSketchOnFace(s) = a {
|
||||
@ -1160,12 +1209,12 @@ fn artifacts_to_update(
|
||||
continue;
|
||||
};
|
||||
let path_sweep_id = path.sweep_id.ok_or_else(|| {
|
||||
KclError::Internal(KclErrorDetails::new(
|
||||
format!(
|
||||
KclError::Internal(KclErrorDetails {
|
||||
message:format!(
|
||||
"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| {
|
||||
if let Artifact::StartSketchOnFace(s) = a {
|
||||
|
@ -308,11 +308,6 @@ impl ArtifactGraph {
|
||||
// a child of the line above it.
|
||||
let label = label.unwrap_or("");
|
||||
if code_ref.node_path.is_empty() {
|
||||
if !code_ref.range.module_id().is_top_level() {
|
||||
// This is pointing to another module. We don't care about
|
||||
// these. It's okay that it's missing, for now.
|
||||
return Ok(());
|
||||
}
|
||||
return writeln!(output, "{prefix} %% {label}Missing NodePath");
|
||||
}
|
||||
writeln!(output, "{prefix} %% {label}{:?}", code_ref.node_path.steps)
|
||||
|
@ -3,7 +3,7 @@ use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
use super::{types::NumericType, ArtifactId, KclValue};
|
||||
use crate::{ModuleId, SourceRange};
|
||||
use crate::{docs::StdLibFn, ModuleId, SourceRange};
|
||||
|
||||
/// A CAD modeling operation for display in the feature tree, AKA operations
|
||||
/// timeline.
|
||||
@ -13,6 +13,21 @@ use crate::{ModuleId, SourceRange};
|
||||
pub enum Operation {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
StdLibCall {
|
||||
/// The standard library function being called.
|
||||
#[serde(flatten)]
|
||||
std_lib_fn: StdLibFnRef,
|
||||
/// The unlabeled argument to the function.
|
||||
unlabeled_arg: Option<OpArg>,
|
||||
/// The labeled keyword arguments to the function.
|
||||
labeled_args: IndexMap<String, OpArg>,
|
||||
/// The source range of the operation in the source code.
|
||||
source_range: SourceRange,
|
||||
/// True if the operation resulted in an error.
|
||||
#[serde(default, skip_serializing_if = "is_false")]
|
||||
is_error: bool,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
KclStdLibCall {
|
||||
name: String,
|
||||
/// The unlabeled argument to the function.
|
||||
unlabeled_arg: Option<OpArg>,
|
||||
@ -42,12 +57,19 @@ impl PartialOrd for Operation {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(match (self, other) {
|
||||
(Self::StdLibCall { source_range: a, .. }, Self::StdLibCall { source_range: b, .. }) => a.cmp(b),
|
||||
(Self::StdLibCall { source_range: a, .. }, Self::KclStdLibCall { source_range: b, .. }) => a.cmp(b),
|
||||
(Self::StdLibCall { source_range: a, .. }, Self::GroupBegin { source_range: b, .. }) => a.cmp(b),
|
||||
(Self::StdLibCall { .. }, Self::GroupEnd) => std::cmp::Ordering::Less,
|
||||
(Self::KclStdLibCall { source_range: a, .. }, Self::KclStdLibCall { source_range: b, .. }) => a.cmp(b),
|
||||
(Self::KclStdLibCall { source_range: a, .. }, Self::StdLibCall { source_range: b, .. }) => a.cmp(b),
|
||||
(Self::KclStdLibCall { source_range: a, .. }, Self::GroupBegin { source_range: b, .. }) => a.cmp(b),
|
||||
(Self::KclStdLibCall { .. }, Self::GroupEnd) => std::cmp::Ordering::Less,
|
||||
(Self::GroupBegin { source_range: a, .. }, Self::GroupBegin { source_range: b, .. }) => a.cmp(b),
|
||||
(Self::GroupBegin { source_range: a, .. }, Self::StdLibCall { source_range: b, .. }) => a.cmp(b),
|
||||
(Self::GroupBegin { source_range: a, .. }, Self::KclStdLibCall { source_range: b, .. }) => a.cmp(b),
|
||||
(Self::GroupBegin { .. }, Self::GroupEnd) => std::cmp::Ordering::Less,
|
||||
(Self::GroupEnd, Self::StdLibCall { .. }) => std::cmp::Ordering::Greater,
|
||||
(Self::GroupEnd, Self::KclStdLibCall { .. }) => std::cmp::Ordering::Greater,
|
||||
(Self::GroupEnd, Self::GroupBegin { .. }) => std::cmp::Ordering::Greater,
|
||||
(Self::GroupEnd, Self::GroupEnd) => std::cmp::Ordering::Equal,
|
||||
})
|
||||
@ -59,6 +81,7 @@ impl Operation {
|
||||
pub(crate) fn set_std_lib_call_is_error(&mut self, is_err: bool) {
|
||||
match self {
|
||||
Self::StdLibCall { ref mut is_error, .. } => *is_error = is_err,
|
||||
Self::KclStdLibCall { ref mut is_error, .. } => *is_error = is_err,
|
||||
Self::GroupBegin { .. } | Self::GroupEnd => {}
|
||||
}
|
||||
}
|
||||
@ -84,7 +107,6 @@ pub enum Group {
|
||||
labeled_args: IndexMap<String, OpArg>,
|
||||
},
|
||||
/// A whole-module import use.
|
||||
#[allow(dead_code)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
ModuleInstance {
|
||||
/// The name of the module being used.
|
||||
@ -113,6 +135,54 @@ impl OpArg {
|
||||
}
|
||||
}
|
||||
|
||||
/// A reference to a standard library function. This exists to implement
|
||||
/// `PartialEq` and `Eq` for `Operation`.
|
||||
#[derive(Debug, Clone, Serialize, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export_to = "Operation.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct StdLibFnRef {
|
||||
// The following doc comment gets inlined into Operation, overriding what's
|
||||
// there, in the generated TS. We serialize to its name. Renaming the
|
||||
// field to "name" allows it to match the other variant.
|
||||
/// The standard library function being called.
|
||||
#[serde(
|
||||
rename = "name",
|
||||
serialize_with = "std_lib_fn_name",
|
||||
deserialize_with = "std_lib_fn_from_name"
|
||||
)]
|
||||
#[ts(type = "string", rename = "name")]
|
||||
pub std_lib_fn: Box<dyn StdLibFn>,
|
||||
}
|
||||
|
||||
impl StdLibFnRef {
|
||||
pub(crate) fn new(std_lib_fn: Box<dyn StdLibFn>) -> Self {
|
||||
Self { std_lib_fn }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Box<dyn StdLibFn>> for StdLibFnRef {
|
||||
fn from(std_lib_fn: &Box<dyn StdLibFn>) -> Self {
|
||||
Self::new(std_lib_fn.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for StdLibFnRef {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.std_lib_fn.name() == other.std_lib_fn.name()
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for StdLibFnRef {}
|
||||
|
||||
#[expect(clippy::borrowed_box, reason = "Explicit Box is needed for serde")]
|
||||
fn std_lib_fn_name<S>(std_lib_fn: &Box<dyn StdLibFn>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let name = std_lib_fn.name();
|
||||
serializer.serialize_str(&name)
|
||||
}
|
||||
|
||||
fn is_false(b: &bool) -> bool {
|
||||
!*b
|
||||
}
|
||||
|