Compare commits
37 Commits
kurt-delet
...
kcl-0.2.35
Author | SHA1 | Date | |
---|---|---|---|
86ba586318 | |||
cc313afb89 | |||
d0c8311e41 | |||
28311d160a | |||
162856064b | |||
652f82e8c3 | |||
d4d9bf6c7f | |||
0e9d37c0a0 | |||
9c18060d73 | |||
e2be66b024 | |||
1bb372b642 | |||
627fbda671 | |||
74bdb72edc | |||
a9182bf357 | |||
5f14023404 | |||
9a3ac64603 | |||
67e60cb832 | |||
110037df79 | |||
30b1dae38a | |||
f20fc5b467 | |||
1bfc3a0a3c | |||
f6e975db84 | |||
b82eec85fd | |||
5dc4213295 | |||
61807e7629 | |||
357bbffce5 | |||
6ac9c49773 | |||
020497cde2 | |||
688852a5df | |||
6c635bd70d | |||
1c0a38a1e2 | |||
019cb815f9 | |||
c8653beae7 | |||
9ea3cb51ba | |||
e0de0493ab | |||
11cac0c30e | |||
4de50edf5a |
@ -1,5 +1,4 @@
|
|||||||
NODE_ENV=production
|
NODE_ENV=production
|
||||||
DEV=false
|
|
||||||
VITE_KC_API_WS_MODELING_URL=wss://api.zoo.dev/ws/modeling/commands
|
VITE_KC_API_WS_MODELING_URL=wss://api.zoo.dev/ws/modeling/commands
|
||||||
VITE_KC_API_BASE_URL=https://api.zoo.dev
|
VITE_KC_API_BASE_URL=https://api.zoo.dev
|
||||||
VITE_KC_SITE_BASE_URL=https://zoo.dev
|
VITE_KC_SITE_BASE_URL=https://zoo.dev
|
||||||
|
1
.github/workflows/e2e-tests.yml
vendored
@ -3,7 +3,6 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches: [ main ]
|
branches: [ main ]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ main ]
|
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||||
|
@ -9,7 +9,7 @@ Repeat a 2-dimensional sketch along some dimension, with a dynamic amount
|
|||||||
of distance between each repetition, some specified number of times.
|
of distance between each repetition, some specified number of times.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
patternLinear2d(data: LinearPattern2dData, sketch_set: SketchSet) -> [Sketch]
|
patternLinear2d(data: LinearPattern2dData, sketch_set: SketchSet, use_original?: bool) -> [Sketch]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@ -19,6 +19,7 @@ patternLinear2d(data: LinearPattern2dData, sketch_set: SketchSet) -> [Sketch]
|
|||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `data` | [`LinearPattern2dData`](/docs/kcl/types/LinearPattern2dData) | Data for a linear pattern on a 2D sketch. | Yes |
|
| `data` | [`LinearPattern2dData`](/docs/kcl/types/LinearPattern2dData) | Data for a linear pattern on a 2D sketch. | Yes |
|
||||||
| `sketch_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | A sketch or a group of sketches. | Yes |
|
| `sketch_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | A sketch or a group of sketches. | Yes |
|
||||||
|
| `use_original` | `bool` | | No |
|
||||||
|
|
||||||
### Returns
|
### Returns
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ Repeat a 3-dimensional solid along a linear path, with a dynamic amount
|
|||||||
of distance between each repetition, some specified number of times.
|
of distance between each repetition, some specified number of times.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
patternLinear3d(data: LinearPattern3dData, solid_set: SolidSet) -> [Solid]
|
patternLinear3d(data: LinearPattern3dData, solid_set: SolidSet, use_original?: bool) -> [Solid]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@ -19,6 +19,7 @@ patternLinear3d(data: LinearPattern3dData, solid_set: SolidSet) -> [Solid]
|
|||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `data` | [`LinearPattern3dData`](/docs/kcl/types/LinearPattern3dData) | Data for a linear pattern on a 3D model. | Yes |
|
| `data` | [`LinearPattern3dData`](/docs/kcl/types/LinearPattern3dData) | Data for a linear pattern on a 3D model. | Yes |
|
||||||
| `solid_set` | [`SolidSet`](/docs/kcl/types/SolidSet) | A solid or a group of solids. | Yes |
|
| `solid_set` | [`SolidSet`](/docs/kcl/types/SolidSet) | A solid or a group of solids. | Yes |
|
||||||
|
| `use_original` | `bool` | | No |
|
||||||
|
|
||||||
### Returns
|
### Returns
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ The transform function returns a transform object. All properties of the object
|
|||||||
- `rotation.origin` (either "local" i.e. rotate around its own center, "global" i.e. rotate around the scene's center, or a 3D point, defaults to "local")
|
- `rotation.origin` (either "local" i.e. rotate around its own center, "global" i.e. rotate around the scene's center, or a 3D point, defaults to "local")
|
||||||
|
|
||||||
```js
|
```js
|
||||||
patternTransform(total_instances: integer, transform_function: FunctionParam, solid_set: SolidSet) -> [Solid]
|
patternTransform(total_instances: integer, transform_function: FunctionParam, solid_set: SolidSet, use_original?: bool) -> [Solid]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@ -46,6 +46,7 @@ patternTransform(total_instances: integer, transform_function: FunctionParam, so
|
|||||||
| `total_instances` | `integer` | | Yes |
|
| `total_instances` | `integer` | | Yes |
|
||||||
| `transform_function` | `FunctionParam` | | Yes |
|
| `transform_function` | `FunctionParam` | | Yes |
|
||||||
| `solid_set` | [`SolidSet`](/docs/kcl/types/SolidSet) | A solid or a group of solids. | Yes |
|
| `solid_set` | [`SolidSet`](/docs/kcl/types/SolidSet) | A solid or a group of solids. | Yes |
|
||||||
|
| `use_original` | `bool` | | No |
|
||||||
|
|
||||||
### Returns
|
### Returns
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ Just like patternTransform, but works on 2D sketches not 3D solids.
|
|||||||
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
patternTransform2d(total_instances: integer, transform_function: FunctionParam, solid_set: SketchSet) -> [Sketch]
|
patternTransform2d(total_instances: integer, transform_function: FunctionParam, solid_set: SketchSet, use_original?: bool) -> [Sketch]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@ -20,6 +20,7 @@ patternTransform2d(total_instances: integer, transform_function: FunctionParam,
|
|||||||
| `total_instances` | `integer` | | Yes |
|
| `total_instances` | `integer` | | Yes |
|
||||||
| `transform_function` | `FunctionParam` | | Yes |
|
| `transform_function` | `FunctionParam` | | Yes |
|
||||||
| `solid_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | A sketch or a group of sketches. | Yes |
|
| `solid_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | A sketch or a group of sketches. | Yes |
|
||||||
|
| `use_original` | `bool` | | No |
|
||||||
|
|
||||||
### Returns
|
### Returns
|
||||||
|
|
||||||
|
22406
docs/kcl/std.json
@ -20,5 +20,6 @@ Data for a circular pattern on a 2D sketch.
|
|||||||
| `center` |`[number, number]`| The center about which to make the pattern. This is a 2D vector. | No |
|
| `center` |`[number, number]`| The center about which to make the pattern. This is a 2D vector. | No |
|
||||||
| `arcDegrees` |`number`| The arc angle (in degrees) to place the repetitions. Must be greater than 0. | No |
|
| `arcDegrees` |`number`| The arc angle (in degrees) to place the repetitions. Must be greater than 0. | No |
|
||||||
| `rotateDuplicates` |`boolean`| Whether or not to rotate the duplicates as they are copied. | No |
|
| `rotateDuplicates` |`boolean`| Whether or not to rotate the duplicates as they are copied. | No |
|
||||||
|
| `useOriginal` |`boolean`| If the target being patterned is itself a pattern, then, should you use the original solid, or the pattern? | No |
|
||||||
|
|
||||||
|
|
||||||
|
@ -21,5 +21,6 @@ Data for a circular pattern on a 3D model.
|
|||||||
| `center` |`[number, number, number]`| The center about which to make the pattern. This is a 3D vector. | No |
|
| `center` |`[number, number, number]`| The center about which to make the pattern. This is a 3D vector. | No |
|
||||||
| `arcDegrees` |`number`| The arc angle (in degrees) to place the repetitions. Must be greater than 0. | No |
|
| `arcDegrees` |`number`| The arc angle (in degrees) to place the repetitions. Must be greater than 0. | No |
|
||||||
| `rotateDuplicates` |`boolean`| Whether or not to rotate the duplicates as they are copied. | No |
|
| `rotateDuplicates` |`boolean`| Whether or not to rotate the duplicates as they are copied. | No |
|
||||||
|
| `useOriginal` |`boolean`| If the target being patterned is itself a pattern, then, should you use the original solid, or the pattern? | No |
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ A sketch is a collection of paths.
|
|||||||
| `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No |
|
| `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No |
|
||||||
| `tags` |`object`| Tag identifiers that have been declared in this sketch. | No |
|
| `tags` |`object`| Tag identifiers that have been declared in this sketch. | No |
|
||||||
| `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The original id of the sketch. This stays the same even if the sketch is is sketched on face etc. | No |
|
| `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The original id of the sketch. This stays the same even if the sketch is is sketched on face etc. | No |
|
||||||
|
| `originalId` |`string`| | No |
|
||||||
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A sketch is a collection of paths. | No |
|
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A sketch is a collection of paths. | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |
|
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@ A sketch is a collection of paths.
|
|||||||
| `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No |
|
| `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No |
|
||||||
| `tags` |`object`| Tag identifiers that have been declared in this sketch. | No |
|
| `tags` |`object`| Tag identifiers that have been declared in this sketch. | No |
|
||||||
| `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The original id of the sketch. This stays the same even if the sketch is is sketched on face etc. | No |
|
| `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The original id of the sketch. This stays the same even if the sketch is is sketched on face etc. | No |
|
||||||
|
| `originalId` |`string`| | No |
|
||||||
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A sketch or a group of sketches. | No |
|
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A sketch or a group of sketches. | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |
|
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |
|
||||||
|
|
||||||
|
@ -19,6 +19,8 @@ test.describe(
|
|||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
// FIXME: Cannot use scene.waitForExecutionDone() since there is no KCL code
|
||||||
|
await page.waitForTimeout(10000)
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
|
|
||||||
const coord =
|
const coord =
|
||||||
|
@ -10,6 +10,7 @@ test.describe('Code pane and errors', { tag: ['@skipWin'] }, () => {
|
|||||||
test('Typing KCL errors induces a badge on the code pane button', async ({
|
test('Typing KCL errors induces a badge on the code pane button', async ({
|
||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
|
scene,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
@ -30,11 +31,7 @@ test.describe('Code pane and errors', { tag: ['@skipWin'] }, () => {
|
|||||||
|
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
await scene.waitForExecutionDone()
|
||||||
// wait for execution done
|
|
||||||
await u.openDebugPanel()
|
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
|
||||||
await u.closeDebugPanel()
|
|
||||||
|
|
||||||
// Ensure no badge is present
|
// Ensure no badge is present
|
||||||
const codePaneButtonHolder = page.locator('#code-button-holder')
|
const codePaneButtonHolder = page.locator('#code-button-holder')
|
||||||
@ -175,7 +172,9 @@ test.describe('Code pane and errors', { tag: ['@skipWin'] }, () => {
|
|||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
await page.waitForTimeout(1000)
|
// FIXME: await scene.waitForExecutionDone() does not work. It still fails.
|
||||||
|
// I needed to increase this timeout to get this to pass.
|
||||||
|
await page.waitForTimeout(10000)
|
||||||
|
|
||||||
// Ensure badge is present
|
// Ensure badge is present
|
||||||
const codePaneButtonHolder = page.locator('#code-button-holder')
|
const codePaneButtonHolder = page.locator('#code-button-holder')
|
||||||
@ -187,7 +186,7 @@ test.describe('Code pane and errors', { tag: ['@skipWin'] }, () => {
|
|||||||
// click in the editor to focus it
|
// click in the editor to focus it
|
||||||
await page.locator('.cm-content').click()
|
await page.locator('.cm-content').click()
|
||||||
|
|
||||||
await page.waitForTimeout(500)
|
await page.waitForTimeout(2000)
|
||||||
|
|
||||||
// go to the start of the editor and enter more text which will trigger
|
// go to the start of the editor and enter more text which will trigger
|
||||||
// a lint error.
|
// a lint error.
|
||||||
@ -204,8 +203,9 @@ test.describe('Code pane and errors', { tag: ['@skipWin'] }, () => {
|
|||||||
await page.keyboard.press('ArrowUp')
|
await page.keyboard.press('ArrowUp')
|
||||||
await page.keyboard.press('Home')
|
await page.keyboard.press('Home')
|
||||||
await page.keyboard.type('foo_bar = 1')
|
await page.keyboard.type('foo_bar = 1')
|
||||||
await page.waitForTimeout(500)
|
await page.waitForTimeout(2000)
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
|
await page.waitForTimeout(2000)
|
||||||
|
|
||||||
// ensure we have a lint error
|
// ensure we have a lint error
|
||||||
await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible()
|
await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible()
|
||||||
|
@ -174,6 +174,9 @@ test.describe('Command bar tests', { tag: ['@skipWin'] }, () => {
|
|||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
|
// FIXME: No KCL code, unable to wait for engine execution
|
||||||
|
await page.waitForTimeout(10000)
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
).not.toBeDisabled()
|
).not.toBeDisabled()
|
||||||
|
@ -9,8 +9,8 @@ import fsp from 'fs/promises'
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'export works on the first try',
|
'export works on the first try',
|
||||||
{ tag: '@electron' },
|
{ tag: ['@electron', '@skipLocalEngine'] },
|
||||||
async ({ page, context }, testInfo) => {
|
async ({ page, context, scene }, testInfo) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const bracketDir = path.join(dir, 'bracket')
|
const bracketDir = path.join(dir, 'bracket')
|
||||||
await Promise.all([fsp.mkdir(bracketDir, { recursive: true })])
|
await Promise.all([fsp.mkdir(bracketDir, { recursive: true })])
|
||||||
@ -118,8 +118,9 @@ test(
|
|||||||
// Close the file pane
|
// Close the file pane
|
||||||
await u.closeFilePanel()
|
await u.closeFilePanel()
|
||||||
|
|
||||||
// wait for it to finish executing (todo: make this more robust)
|
// FIXME: await scene.waitForExecutionDone() does not work. The modeling indicator stays in -receive-reliable and not execution done
|
||||||
await page.waitForTimeout(1000)
|
await page.waitForTimeout(10000)
|
||||||
|
|
||||||
// expect zero errors in guter
|
// expect zero errors in guter
|
||||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
|
|
||||||
|
@ -490,6 +490,11 @@ test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
|
|||||||
await page.keyboard.press('ArrowLeft')
|
await page.keyboard.press('ArrowLeft')
|
||||||
await page.keyboard.press('ArrowRight')
|
await page.keyboard.press('ArrowRight')
|
||||||
|
|
||||||
|
// FIXME: lsp errors do not propagate to the frontend until engine is connected and code is executed
|
||||||
|
// This timeout is to wait for engine connection. LSP and code execution errors should be handled differently
|
||||||
|
// LSP can emit errors as fast as it waits and show them in the editor
|
||||||
|
await page.waitForTimeout(10000)
|
||||||
|
|
||||||
// error in guter
|
// error in guter
|
||||||
await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible()
|
await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible()
|
||||||
|
|
||||||
@ -641,7 +646,7 @@ test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
|
|||||||
width = 0.500
|
width = 0.500
|
||||||
height = 0.500
|
height = 0.500
|
||||||
dia = 4
|
dia = 4
|
||||||
|
|
||||||
fn squareHole = (l, w) => {
|
fn squareHole = (l, w) => {
|
||||||
squareHoleSketch = startSketchOn('XY')
|
squareHoleSketch = startSketchOn('XY')
|
||||||
|> startProfileAt([-width / 2, -length / 2], %)
|
|> startProfileAt([-width / 2, -length / 2], %)
|
||||||
@ -714,7 +719,7 @@ test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
|
|||||||
|> line(end = [0, -10], tag = $revolveAxis)
|
|> line(end = [0, -10], tag = $revolveAxis)
|
||||||
|> close()
|
|> close()
|
||||||
|> extrude(length = 10)
|
|> extrude(length = 10)
|
||||||
|
|
||||||
sketch001 = startSketchOn(box, revolveAxis)
|
sketch001 = startSketchOn(box, revolveAxis)
|
||||||
|> startProfileAt([5, 10], %)
|
|> startProfileAt([5, 10], %)
|
||||||
|> line(end = [0, -10])
|
|> line(end = [0, -10])
|
||||||
|
@ -112,6 +112,9 @@ export class CmdBarFixture {
|
|||||||
* and assumes we are past the `pickCommand` step.
|
* and assumes we are past the `pickCommand` step.
|
||||||
*/
|
*/
|
||||||
progressCmdBar = async (shouldFuzzProgressMethod = true) => {
|
progressCmdBar = async (shouldFuzzProgressMethod = true) => {
|
||||||
|
// FIXME: Progressing the command bar is a race condition. We have an async useEffect that reports the final state via useCalculateKclExpression. If this does not run quickly enough, it will not "fail" the continue because you can press continue if the state is not ready. E2E tests do not know this.
|
||||||
|
// Wait 1250ms to assume the await executeAst of the KCL input field is finished
|
||||||
|
await this.page.waitForTimeout(1250)
|
||||||
if (shouldFuzzProgressMethod || Math.random() > 0.5) {
|
if (shouldFuzzProgressMethod || Math.random() > 0.5) {
|
||||||
const arrowButton = this.page.getByRole('button', {
|
const arrowButton = this.page.getByRole('button', {
|
||||||
name: 'arrow right Continue',
|
name: 'arrow right Continue',
|
||||||
@ -128,6 +131,23 @@ export class CmdBarFixture {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Added data-testid to the command bar buttons
|
||||||
|
// command-bar-continue are the buttons to go to the next step
|
||||||
|
// does not include the submit which is the final button press
|
||||||
|
// aka the right arrow button
|
||||||
|
continue = async () => {
|
||||||
|
const continueButton = this.page.getByTestId('command-bar-continue')
|
||||||
|
await continueButton.click()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Added data-testid to the command bar buttons
|
||||||
|
// command-bar-submit is the button for the final step to submit
|
||||||
|
// the command bar flow aka the checkmark button.
|
||||||
|
submit = async () => {
|
||||||
|
const submitButton = this.page.getByTestId('command-bar-submit')
|
||||||
|
await submitButton.click()
|
||||||
|
}
|
||||||
|
|
||||||
openCmdBar = async (selectCmd?: 'promptToEdit') => {
|
openCmdBar = async (selectCmd?: 'promptToEdit') => {
|
||||||
// TODO why does this button not work in electron tests?
|
// TODO why does this button not work in electron tests?
|
||||||
// await this.cmdBarOpenBtn.click()
|
// await this.cmdBarOpenBtn.click()
|
||||||
|
@ -20,6 +20,7 @@ export class ToolbarFixture {
|
|||||||
shellButton!: Locator
|
shellButton!: Locator
|
||||||
revolveButton!: Locator
|
revolveButton!: Locator
|
||||||
offsetPlaneButton!: Locator
|
offsetPlaneButton!: Locator
|
||||||
|
helixButton!: Locator
|
||||||
startSketchBtn!: Locator
|
startSketchBtn!: Locator
|
||||||
lineBtn!: Locator
|
lineBtn!: Locator
|
||||||
rectangleBtn!: Locator
|
rectangleBtn!: Locator
|
||||||
@ -49,6 +50,7 @@ export class ToolbarFixture {
|
|||||||
this.shellButton = page.getByTestId('shell')
|
this.shellButton = page.getByTestId('shell')
|
||||||
this.revolveButton = page.getByTestId('revolve')
|
this.revolveButton = page.getByTestId('revolve')
|
||||||
this.offsetPlaneButton = page.getByTestId('plane-offset')
|
this.offsetPlaneButton = page.getByTestId('plane-offset')
|
||||||
|
this.helixButton = page.getByTestId('helix')
|
||||||
this.startSketchBtn = page.getByTestId('sketch')
|
this.startSketchBtn = page.getByTestId('sketch')
|
||||||
this.lineBtn = page.getByTestId('line')
|
this.lineBtn = page.getByTestId('line')
|
||||||
this.rectangleBtn = page.getByTestId('corner-rectangle')
|
this.rectangleBtn = page.getByTestId('corner-rectangle')
|
||||||
|
@ -27,7 +27,7 @@ test.describe('Onboarding tests', () => {
|
|||||||
},
|
},
|
||||||
cleanProjectDir: true,
|
cleanProjectDir: true,
|
||||||
},
|
},
|
||||||
async ({ context, page, homePage }) => {
|
async ({ page, homePage }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
@ -68,7 +68,7 @@ test.describe('Onboarding tests', () => {
|
|||||||
},
|
},
|
||||||
cleanProjectDir: true,
|
cleanProjectDir: true,
|
||||||
},
|
},
|
||||||
async ({ page, homePage }, testInfo) => {
|
async ({ page }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
const viewportSize = { width: 1200, height: 500 }
|
const viewportSize = { width: 1200, height: 500 }
|
||||||
@ -154,7 +154,7 @@ test.describe('Onboarding tests', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'Click through each onboarding step',
|
'Click through each onboarding step and back',
|
||||||
{
|
{
|
||||||
appSettings: {
|
appSettings: {
|
||||||
app: {
|
app: {
|
||||||
@ -187,15 +187,21 @@ test.describe('Onboarding tests', () => {
|
|||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
|
|
||||||
const nextButton = page.getByTestId('onboarding-next')
|
const nextButton = page.getByTestId('onboarding-next')
|
||||||
|
const prevButton = page.getByTestId('onboarding-prev')
|
||||||
|
|
||||||
while ((await nextButton.innerText()) !== 'Finish') {
|
while ((await nextButton.innerText()) !== 'Finish') {
|
||||||
await nextButton.hover()
|
await nextButton.hover()
|
||||||
await nextButton.click()
|
await nextButton.click()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finish the onboarding
|
while ((await prevButton.innerText()) !== 'Dismiss') {
|
||||||
await nextButton.hover()
|
await prevButton.hover()
|
||||||
await nextButton.click()
|
await prevButton.click()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dismiss the onboarding
|
||||||
|
await prevButton.hover()
|
||||||
|
await prevButton.click()
|
||||||
|
|
||||||
// Test that the onboarding pane is gone
|
// Test that the onboarding pane is gone
|
||||||
await expect(page.getByTestId('onboarding-content')).not.toBeVisible()
|
await expect(page.getByTestId('onboarding-content')).not.toBeVisible()
|
||||||
@ -269,7 +275,7 @@ test.describe('Onboarding tests', () => {
|
|||||||
cleanProjectDir: true,
|
cleanProjectDir: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
async ({ context, page, homePage }) => {
|
async ({ page, homePage }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
const badCode = `// This is bad code we shouldn't see`
|
const badCode = `// This is bad code we shouldn't see`
|
||||||
|
|
||||||
@ -336,10 +342,10 @@ test.describe('Onboarding tests', () => {
|
|||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
// Test that the text in this step is correct
|
// Test that the text in this step is correct
|
||||||
const avatarLocator = await page
|
const avatarLocator = page
|
||||||
.getByTestId('user-sidebar-toggle')
|
.getByTestId('user-sidebar-toggle')
|
||||||
.locator('img')
|
.locator('img')
|
||||||
const onboardingOverlayLocator = await page
|
const onboardingOverlayLocator = page
|
||||||
.getByTestId('onboarding-content')
|
.getByTestId('onboarding-content')
|
||||||
.locator('div')
|
.locator('div')
|
||||||
.nth(1)
|
.nth(1)
|
||||||
@ -447,7 +453,7 @@ test.fixme(
|
|||||||
},
|
},
|
||||||
cleanProjectDir: true,
|
cleanProjectDir: true,
|
||||||
},
|
},
|
||||||
async ({ context, page, homePage }, testInfo) => {
|
async ({ context, page }) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const routerTemplateDir = join(dir, 'router-template-slate')
|
const routerTemplateDir = join(dir, 'router-template-slate')
|
||||||
await fsp.mkdir(routerTemplateDir, { recursive: true })
|
await fsp.mkdir(routerTemplateDir, { recursive: true })
|
||||||
@ -486,10 +492,6 @@ test.fixme(
|
|||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Navigate into project', async () => {
|
await test.step('Navigate into project', async () => {
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
|
||||||
|
|
||||||
page.on('console', console.log)
|
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('heading', { name: 'Your Projects' })
|
page.getByRole('heading', { name: 'Your Projects' })
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
|
@ -29,11 +29,13 @@ test.describe('Point-and-click tests', { tag: ['@skipWin'] }, () => {
|
|||||||
localStorage.setItem('persistCode', file)
|
localStorage.setItem('persistCode', file)
|
||||||
}, file)
|
}, file)
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
await scene.waitForExecutionDone()
|
||||||
|
|
||||||
const [clickCircle, moveToCircle] = scene.makeMouseHelpers(582, 217)
|
const [clickCircle, moveToCircle] = scene.makeMouseHelpers(582, 217)
|
||||||
|
|
||||||
await test.step('because there is sweepable geometry, verify extrude is enable when nothing is selected', async () => {
|
await test.step('because there is sweepable geometry, verify extrude is enable when nothing is selected', async () => {
|
||||||
await scene.clickNoWhere()
|
// FIXME: Do not click, clicking removes the activeLines in future checks
|
||||||
|
// await scene.clickNoWhere()
|
||||||
await expect(toolbar.extrudeButton).toBeEnabled()
|
await expect(toolbar.extrudeButton).toBeEnabled()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -199,6 +201,7 @@ test.describe('Point-and-click tests', { tag: ['@skipWin'] }, () => {
|
|||||||
}, file)
|
}, file)
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
await scene.waitForExecutionDone()
|
||||||
|
|
||||||
const sketchOnAChamfer = _sketchOnAChamfer(page, editor, toolbar, scene)
|
const sketchOnAChamfer = _sketchOnAChamfer(page, editor, toolbar, scene)
|
||||||
|
|
||||||
@ -422,6 +425,7 @@ test.describe('Point-and-click tests', { tag: ['@skipWin'] }, () => {
|
|||||||
}, file)
|
}, file)
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
await scene.waitForExecutionDone()
|
||||||
|
|
||||||
const sketchOnAChamfer = _sketchOnAChamfer(page, editor, toolbar, scene)
|
const sketchOnAChamfer = _sketchOnAChamfer(page, editor, toolbar, scene)
|
||||||
|
|
||||||
@ -712,6 +716,330 @@ openSketch = startSketchOn('XY')
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test(`Shift-click to select and deselect edges and faces`, async ({
|
||||||
|
context,
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
|
scene,
|
||||||
|
}) => {
|
||||||
|
// Code samples
|
||||||
|
const initialCode = `sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-12, -6], %)
|
||||||
|
|> line(end = [0, 12])
|
||||||
|
|> line(end = [24, 0])
|
||||||
|
|> line(end = [0, -12])
|
||||||
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||||
|
|> close()
|
||||||
|
|> extrude(%, length = -12)`
|
||||||
|
|
||||||
|
// Locators
|
||||||
|
const upperEdgeLocation = { x: 600, y: 192 }
|
||||||
|
const lowerEdgeLocation = { x: 600, y: 383 }
|
||||||
|
const faceLocation = { x: 630, y: 290 }
|
||||||
|
|
||||||
|
// Click helpers
|
||||||
|
const [clickOnUpperEdge] = scene.makeMouseHelpers(
|
||||||
|
upperEdgeLocation.x,
|
||||||
|
upperEdgeLocation.y
|
||||||
|
)
|
||||||
|
const [clickOnLowerEdge] = scene.makeMouseHelpers(
|
||||||
|
lowerEdgeLocation.x,
|
||||||
|
lowerEdgeLocation.y
|
||||||
|
)
|
||||||
|
const [clickOnFace] = scene.makeMouseHelpers(faceLocation.x, faceLocation.y)
|
||||||
|
|
||||||
|
// Colors
|
||||||
|
const edgeColorWhite: [number, number, number] = [220, 220, 220] // varies from 192 to 255
|
||||||
|
const edgeColorYellow: [number, number, number] = [251, 251, 40] // vaies from 12 to 67
|
||||||
|
const faceColorGray: [number, number, number] = [168, 168, 168]
|
||||||
|
const faceColorYellow: [number, number, number] = [155, 155, 155]
|
||||||
|
const tolerance = 40
|
||||||
|
const timeout = 150
|
||||||
|
|
||||||
|
// Setup
|
||||||
|
await test.step(`Initial test setup`, async () => {
|
||||||
|
await context.addInitScript((initialCode) => {
|
||||||
|
localStorage.setItem('persistCode', initialCode)
|
||||||
|
}, initialCode)
|
||||||
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
|
// Wait for the scene and stream to load
|
||||||
|
await scene.expectPixelColor(faceColorGray, faceLocation, tolerance)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Select and deselect a single edge', async () => {
|
||||||
|
await test.step('Click the edge', async () => {
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorWhite,
|
||||||
|
upperEdgeLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
await clickOnUpperEdge()
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorYellow,
|
||||||
|
upperEdgeLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
})
|
||||||
|
await test.step('Shift-click the same edge to deselect', async () => {
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await clickOnUpperEdge()
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorWhite,
|
||||||
|
upperEdgeLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Select and deselect multiple objects', async () => {
|
||||||
|
await test.step('Select both edges and the face', async () => {
|
||||||
|
await test.step('Select the upper edge', async () => {
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorWhite,
|
||||||
|
upperEdgeLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
await clickOnUpperEdge()
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorYellow,
|
||||||
|
upperEdgeLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
})
|
||||||
|
await test.step('Select the lower edge (Shift-click)', async () => {
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorWhite,
|
||||||
|
lowerEdgeLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await clickOnLowerEdge()
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorYellow,
|
||||||
|
lowerEdgeLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
})
|
||||||
|
await test.step('Select the face (Shift-click)', async () => {
|
||||||
|
await scene.expectPixelColor(faceColorGray, faceLocation, tolerance)
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await clickOnFace()
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
await scene.expectPixelColor(faceColorYellow, faceLocation, tolerance)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
await test.step('Deselect them one by one', async () => {
|
||||||
|
await test.step('Deselect the face (Shift-click)', async () => {
|
||||||
|
await scene.expectPixelColor(faceColorYellow, faceLocation, tolerance)
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await clickOnFace()
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
await scene.expectPixelColor(faceColorGray, faceLocation, tolerance)
|
||||||
|
})
|
||||||
|
await test.step('Deselect the lower edge (Shift-click)', async () => {
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorYellow,
|
||||||
|
lowerEdgeLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await clickOnLowerEdge()
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorWhite,
|
||||||
|
lowerEdgeLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
})
|
||||||
|
await test.step('Deselect the upper edge (Shift-click)', async () => {
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorYellow,
|
||||||
|
upperEdgeLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await clickOnUpperEdge()
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorWhite,
|
||||||
|
upperEdgeLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test(`Shift-click to select and deselect sketch segments`, async ({
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
|
scene,
|
||||||
|
editor,
|
||||||
|
}) => {
|
||||||
|
// Locators
|
||||||
|
const firstPointLocation = { x: 200, y: 100 }
|
||||||
|
const secondPointLocation = { x: 800, y: 100 }
|
||||||
|
const thirdPointLocation = { x: 800, y: 400 }
|
||||||
|
const fristSegmentLocation = { x: 750, y: 100 }
|
||||||
|
const secondSegmentLocation = { x: 800, y: 150 }
|
||||||
|
const planeLocation = { x: 700, y: 200 }
|
||||||
|
|
||||||
|
// Click helpers
|
||||||
|
const [clickFirstPoint] = scene.makeMouseHelpers(
|
||||||
|
firstPointLocation.x,
|
||||||
|
firstPointLocation.y
|
||||||
|
)
|
||||||
|
const [clickSecondPoint] = scene.makeMouseHelpers(
|
||||||
|
secondPointLocation.x,
|
||||||
|
secondPointLocation.y
|
||||||
|
)
|
||||||
|
const [clickThirdPoint] = scene.makeMouseHelpers(
|
||||||
|
thirdPointLocation.x,
|
||||||
|
thirdPointLocation.y
|
||||||
|
)
|
||||||
|
const [clickFirstSegment] = scene.makeMouseHelpers(
|
||||||
|
fristSegmentLocation.x,
|
||||||
|
fristSegmentLocation.y
|
||||||
|
)
|
||||||
|
const [clickSecondSegment] = scene.makeMouseHelpers(
|
||||||
|
secondSegmentLocation.x,
|
||||||
|
secondSegmentLocation.y
|
||||||
|
)
|
||||||
|
const [clickPlane] = scene.makeMouseHelpers(
|
||||||
|
planeLocation.x,
|
||||||
|
planeLocation.y
|
||||||
|
)
|
||||||
|
|
||||||
|
// Colors
|
||||||
|
const edgeColorWhite: [number, number, number] = [220, 220, 220]
|
||||||
|
const edgeColorBlue: [number, number, number] = [20, 20, 200]
|
||||||
|
const backgroundColor: [number, number, number] = [30, 30, 30]
|
||||||
|
const tolerance = 40
|
||||||
|
const timeout = 150
|
||||||
|
|
||||||
|
// Setup
|
||||||
|
await test.step(`Initial test setup`, async () => {
|
||||||
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
|
// Wait for the scene and stream to load
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
backgroundColor,
|
||||||
|
secondPointLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Select and deselect a single sketch segment', async () => {
|
||||||
|
await test.step('Get into sketch mode', async () => {
|
||||||
|
await editor.closePane()
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await clickPlane()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
})
|
||||||
|
await test.step('Draw sketch', async () => {
|
||||||
|
await clickFirstPoint()
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await clickSecondPoint()
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await clickThirdPoint()
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
})
|
||||||
|
await test.step('Deselect line tool', async () => {
|
||||||
|
const btnLine = page.getByTestId('line')
|
||||||
|
const btnLineAriaPressed = await btnLine.getAttribute('aria-pressed')
|
||||||
|
if (btnLineAriaPressed === 'true') {
|
||||||
|
await btnLine.click()
|
||||||
|
}
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
})
|
||||||
|
await test.step('Select the first segment', async () => {
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await clickFirstSegment()
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorBlue,
|
||||||
|
fristSegmentLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorWhite,
|
||||||
|
secondSegmentLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
})
|
||||||
|
await test.step('Select the second segment (Shift-click)', async () => {
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await clickSecondSegment()
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorBlue,
|
||||||
|
fristSegmentLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorBlue,
|
||||||
|
secondSegmentLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
})
|
||||||
|
await test.step('Deselect the first segment', async () => {
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await clickFirstSegment()
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorWhite,
|
||||||
|
fristSegmentLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorBlue,
|
||||||
|
secondSegmentLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
})
|
||||||
|
await test.step('Deselect the second segment', async () => {
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await clickSecondSegment()
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorWhite,
|
||||||
|
fristSegmentLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorWhite,
|
||||||
|
secondSegmentLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test(`Offset plane point-and-click`, async ({
|
test(`Offset plane point-and-click`, async ({
|
||||||
context,
|
context,
|
||||||
page,
|
page,
|
||||||
@ -727,6 +1055,9 @@ openSketch = startSketchOn('XY')
|
|||||||
const expectedOutput = `plane001 = offsetPlane('XZ', 5)`
|
const expectedOutput = `plane001 = offsetPlane('XZ', 5)`
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
// FIXME: Since there is no KCL code loaded. We need to wait for the scene to load before we continue.
|
||||||
|
// The engine may not be connected
|
||||||
|
await page.waitForTimeout(15000)
|
||||||
|
|
||||||
await test.step(`Look for the blue of the XZ plane`, async () => {
|
await test.step(`Look for the blue of the XZ plane`, async () => {
|
||||||
await scene.expectPixelColor([50, 51, 96], testPoint, 15)
|
await scene.expectPixelColor([50, 51, 96], testPoint, 15)
|
||||||
@ -775,6 +1106,71 @@ openSketch = startSketchOn('XY')
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('Helix point-and-click', async ({
|
||||||
|
context,
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
|
scene,
|
||||||
|
editor,
|
||||||
|
toolbar,
|
||||||
|
cmdBar,
|
||||||
|
}) => {
|
||||||
|
// One dumb hardcoded screen pixel value
|
||||||
|
const testPoint = { x: 620, y: 257 }
|
||||||
|
const expectedOutput = `helix001 = helix(revolutions = 1, angleStart = 360, counterClockWise = false, radius = 5, axis = 'X', length = 5)`
|
||||||
|
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
|
await test.step(`Look for the red of the default plane`, async () => {
|
||||||
|
await scene.expectPixelColor([96, 52, 52], testPoint, 15)
|
||||||
|
})
|
||||||
|
await test.step(`Go through the command bar flow`, async () => {
|
||||||
|
await toolbar.helixButton.click()
|
||||||
|
await cmdBar.expectState({
|
||||||
|
stage: 'arguments',
|
||||||
|
currentArgKey: 'revolutions',
|
||||||
|
currentArgValue: '1',
|
||||||
|
headerArguments: {
|
||||||
|
AngleStart: '',
|
||||||
|
Axis: '',
|
||||||
|
CounterClockWise: '',
|
||||||
|
Length: '',
|
||||||
|
Radius: '',
|
||||||
|
Revolutions: '',
|
||||||
|
},
|
||||||
|
highlightedHeaderArg: 'revolutions',
|
||||||
|
commandName: 'Helix',
|
||||||
|
})
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
||||||
|
await editor.expectEditor.toContain(expectedOutput)
|
||||||
|
await editor.expectState({
|
||||||
|
diagnostics: [],
|
||||||
|
activeLines: [expectedOutput],
|
||||||
|
highlightedCode: '',
|
||||||
|
})
|
||||||
|
// Red plane is now gone, white helix is there
|
||||||
|
await scene.expectPixelColor([250, 250, 250], testPoint, 15)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Delete offset plane via feature tree selection', async () => {
|
||||||
|
await editor.closePane()
|
||||||
|
const operationButton = await toolbar.getFeatureTreeOperation('Helix', 0)
|
||||||
|
await operationButton.click({ button: 'left' })
|
||||||
|
await page.keyboard.press('Backspace')
|
||||||
|
// Red plane is back
|
||||||
|
await scene.expectPixelColor([96, 52, 52], testPoint, 15)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
const loftPointAndClickCases = [
|
const loftPointAndClickCases = [
|
||||||
{ shouldPreselect: true },
|
{ shouldPreselect: true },
|
||||||
{ shouldPreselect: false },
|
{ shouldPreselect: false },
|
||||||
@ -887,6 +1283,7 @@ loft001 = loft([sketch001, sketch002])
|
|||||||
}, initialCode)
|
}, initialCode)
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
await scene.waitForExecutionDone()
|
||||||
|
|
||||||
// One dumb hardcoded screen pixel value
|
// One dumb hardcoded screen pixel value
|
||||||
const testPoint = { x: 575, y: 200 }
|
const testPoint = { x: 575, y: 200 }
|
||||||
@ -964,7 +1361,7 @@ sketch002 = startSketchOn('XZ')
|
|||||||
testPoint.x - 50,
|
testPoint.x - 50,
|
||||||
testPoint.y
|
testPoint.y
|
||||||
)
|
)
|
||||||
const sweepDeclaration = 'sweep001 = sweep({ path = sketch002 }, sketch001)'
|
const sweepDeclaration = 'sweep001 = sweep(sketch001, path = sketch002)'
|
||||||
|
|
||||||
await test.step(`Look for sketch001`, async () => {
|
await test.step(`Look for sketch001`, async () => {
|
||||||
await toolbar.closePane('code')
|
await toolbar.closePane('code')
|
||||||
@ -1529,16 +1926,7 @@ extrude001 = extrude(sketch001, length = -12)
|
|||||||
}, initialCode)
|
}, initialCode)
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
await scene.waitForExecutionDone()
|
||||||
// verify modeling scene is loaded
|
|
||||||
await scene.expectPixelColor(
|
|
||||||
backgroundColor,
|
|
||||||
secondEdgeLocation,
|
|
||||||
lowTolerance
|
|
||||||
)
|
|
||||||
|
|
||||||
// wait for stream to load
|
|
||||||
await scene.expectPixelColor(bodyColor, bodyLocation, highTolerance)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Test 1: Command bar flow with preselected edges
|
// Test 1: Command bar flow with preselected edges
|
||||||
@ -1763,6 +2151,7 @@ chamfer04 = chamfer({ length = 5, tags = [getOppositeEdge(seg02)]}, extrude001
|
|||||||
}, initialCode)
|
}, initialCode)
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
await scene.waitForExecutionDone()
|
||||||
|
|
||||||
// verify modeling scene is loaded
|
// verify modeling scene is loaded
|
||||||
await scene.expectPixelColor(
|
await scene.expectPixelColor(
|
||||||
@ -1885,12 +2274,13 @@ chamfer04 = chamfer({ length = 5, tags = [getOppositeEdge(seg02)]}, extrude001
|
|||||||
}, initialCode)
|
}, initialCode)
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
await scene.waitForExecutionDone()
|
||||||
|
|
||||||
// One dumb hardcoded screen pixel value
|
// One dumb hardcoded screen pixel value
|
||||||
const testPoint = { x: 575, y: 200 }
|
const testPoint = { x: 575, y: 200 }
|
||||||
const [clickOnCap] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
const [clickOnCap] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
||||||
const shellDeclaration =
|
const shellDeclaration =
|
||||||
"shell001 = shell({ faces = ['end'], thickness = 5 }, extrude001)"
|
"shell001 = shell(extrude001, faces = ['end'], thickness = 5)"
|
||||||
|
|
||||||
await test.step(`Look for the grey of the shape`, async () => {
|
await test.step(`Look for the grey of the shape`, async () => {
|
||||||
await scene.expectPixelColor([127, 127, 127], testPoint, 15)
|
await scene.expectPixelColor([127, 127, 127], testPoint, 15)
|
||||||
@ -1983,6 +2373,7 @@ extrude001 = extrude(sketch001, length = 40)
|
|||||||
}, initialCode)
|
}, initialCode)
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
await scene.waitForExecutionDone()
|
||||||
|
|
||||||
// One dumb hardcoded screen pixel value
|
// One dumb hardcoded screen pixel value
|
||||||
const testPoint = { x: 580, y: 180 }
|
const testPoint = { x: 580, y: 180 }
|
||||||
@ -1990,8 +2381,7 @@ extrude001 = extrude(sketch001, length = 40)
|
|||||||
const [clickOnWall] = scene.makeMouseHelpers(testPoint.x, testPoint.y + 70)
|
const [clickOnWall] = scene.makeMouseHelpers(testPoint.x, testPoint.y + 70)
|
||||||
const mutatedCode = 'xLine(-40, %, $seg01)'
|
const mutatedCode = 'xLine(-40, %, $seg01)'
|
||||||
const shellDeclaration =
|
const shellDeclaration =
|
||||||
"shell001 = shell({ faces = ['end', seg01], thickness = 5}, extrude001)"
|
"shell001 = shell(extrude001, faces = ['end', seg01], thickness = 5)"
|
||||||
const formattedOutLastLine = '}, extrude001)'
|
|
||||||
|
|
||||||
await test.step(`Look for the grey of the shape`, async () => {
|
await test.step(`Look for the grey of the shape`, async () => {
|
||||||
await scene.expectPixelColor([99, 99, 99], testPoint, 15)
|
await scene.expectPixelColor([99, 99, 99], testPoint, 15)
|
||||||
@ -2034,7 +2424,7 @@ extrude001 = extrude(sketch001, length = 40)
|
|||||||
await editor.expectEditor.toContain(shellDeclaration)
|
await editor.expectEditor.toContain(shellDeclaration)
|
||||||
await editor.expectState({
|
await editor.expectState({
|
||||||
diagnostics: [],
|
diagnostics: [],
|
||||||
activeLines: [formattedOutLastLine],
|
activeLines: [shellDeclaration],
|
||||||
highlightedCode: '',
|
highlightedCode: '',
|
||||||
})
|
})
|
||||||
await scene.expectPixelColor([49, 49, 49], testPoint, 15)
|
await scene.expectPixelColor([49, 49, 49], testPoint, 15)
|
||||||
@ -2088,9 +2478,8 @@ extrude002 = extrude(sketch002, length = 50)
|
|||||||
// One dumb hardcoded screen pixel value
|
// One dumb hardcoded screen pixel value
|
||||||
const testPoint = { x: 550, y: 295 }
|
const testPoint = { x: 550, y: 295 }
|
||||||
const [clickOnCap] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
const [clickOnCap] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
||||||
const shellDeclaration = `shell001 = shell({ faces = ['end'], thickness = 5 }, ${
|
const shellTarget = hasExtrudesInPipe ? 'sketch002' : 'extrude002'
|
||||||
hasExtrudesInPipe ? 'sketch002' : 'extrude002'
|
const shellDeclaration = `shell001 = shell(${shellTarget}, faces = ['end'], thickness = 5)`
|
||||||
})`
|
|
||||||
|
|
||||||
await test.step(`Look for the grey of the shape`, async () => {
|
await test.step(`Look for the grey of the shape`, async () => {
|
||||||
await toolbar.closePane('code')
|
await toolbar.closePane('code')
|
||||||
@ -2158,7 +2547,7 @@ extrude002 = extrude(sketch002, length = 50)
|
|||||||
sketch002 = startSketchOn('XZ')
|
sketch002 = startSketchOn('XZ')
|
||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> xLine(-2000, %)
|
|> xLine(-2000, %)
|
||||||
sweep001 = sweep({ path = sketch002 }, sketch001)
|
sweep001 = sweep(sketch001, path = sketch002)
|
||||||
`
|
`
|
||||||
await context.addInitScript((initialCode) => {
|
await context.addInitScript((initialCode) => {
|
||||||
localStorage.setItem('persistCode', initialCode)
|
localStorage.setItem('persistCode', initialCode)
|
||||||
|
@ -455,7 +455,7 @@ test.describe('Can export from electron app', () => {
|
|||||||
for (const method of exportMethods) {
|
for (const method of exportMethods) {
|
||||||
test(
|
test(
|
||||||
`Can export using ${method}`,
|
`Can export using ${method}`,
|
||||||
{ tag: '@electron' },
|
{ tag: ['@electron', '@skipLocalEngine'] },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const bracketDir = path.join(dir, 'bracket')
|
const bracketDir = path.join(dir, 'bracket')
|
||||||
|
@ -35,106 +35,113 @@ sketch003 = startSketchOn('XY')
|
|||||||
extrude003 = extrude(sketch003, length = 20)
|
extrude003 = extrude(sketch003, length = 20)
|
||||||
`
|
`
|
||||||
|
|
||||||
test.describe('Check the happy path, for basic changing color', () => {
|
test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
|
||||||
const cases = [
|
test.fixme('Check the happy path, for basic changing color', () => {
|
||||||
{
|
const cases = [
|
||||||
desc: 'User accepts change',
|
{
|
||||||
shouldReject: false,
|
desc: 'User accepts change',
|
||||||
},
|
shouldReject: false,
|
||||||
{
|
},
|
||||||
desc: 'User rejects change',
|
{
|
||||||
shouldReject: true,
|
desc: 'User rejects change',
|
||||||
},
|
shouldReject: true,
|
||||||
] as const
|
},
|
||||||
for (const { desc, shouldReject } of cases) {
|
] as const
|
||||||
test(`${desc}`, async ({
|
for (const { desc, shouldReject } of cases) {
|
||||||
context,
|
test(`${desc}`, async ({
|
||||||
homePage,
|
context,
|
||||||
cmdBar,
|
homePage,
|
||||||
editor,
|
cmdBar,
|
||||||
page,
|
editor,
|
||||||
scene,
|
page,
|
||||||
}) => {
|
scene,
|
||||||
await context.addInitScript((file) => {
|
}) => {
|
||||||
localStorage.setItem('persistCode', file)
|
await context.addInitScript((file) => {
|
||||||
}, file)
|
localStorage.setItem('persistCode', file)
|
||||||
await homePage.goToModelingScene()
|
}, file)
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
await scene.waitForExecutionDone()
|
||||||
|
|
||||||
const body1CapCoords = { x: 571, y: 351 }
|
const body1CapCoords = { x: 571, y: 351 }
|
||||||
const greenCheckCoords = { x: 565, y: 345 }
|
const greenCheckCoords = { x: 565, y: 345 }
|
||||||
const body2WallCoords = { x: 609, y: 153 }
|
const body2WallCoords = { x: 609, y: 153 }
|
||||||
const [clickBody1Cap] = scene.makeMouseHelpers(
|
const [clickBody1Cap] = scene.makeMouseHelpers(
|
||||||
body1CapCoords.x,
|
body1CapCoords.x,
|
||||||
body1CapCoords.y
|
body1CapCoords.y
|
||||||
)
|
)
|
||||||
const yellow: [number, number, number] = [179, 179, 131]
|
const yellow: [number, number, number] = [179, 179, 131]
|
||||||
const green: [number, number, number] = [108, 152, 75]
|
const green: [number, number, number] = [108, 152, 75]
|
||||||
const notGreen: [number, number, number] = [132, 132, 132]
|
const notGreen: [number, number, number] = [132, 132, 132]
|
||||||
const body2NotGreen: [number, number, number] = [88, 88, 88]
|
const body2NotGreen: [number, number, number] = [88, 88, 88]
|
||||||
const submittingToast = page.getByText('Submitting to Text-to-CAD API...')
|
const submittingToast = page.getByText(
|
||||||
const successToast = page.getByText('Prompt to edit successful')
|
'Submitting to Text-to-CAD API...'
|
||||||
const acceptBtn = page.getByRole('button', { name: 'checkmark Accept' })
|
)
|
||||||
const rejectBtn = page.getByRole('button', { name: 'close Reject' })
|
const successToast = page.getByText('Prompt to edit successful')
|
||||||
|
const acceptBtn = page.getByRole('button', {
|
||||||
await test.step('wait for scene to load select body and check selection came through', async () => {
|
name: 'checkmark Accept',
|
||||||
await scene.expectPixelColor([134, 134, 134], body1CapCoords, 15)
|
|
||||||
await clickBody1Cap()
|
|
||||||
await scene.expectPixelColor(yellow, body1CapCoords, 20)
|
|
||||||
await editor.expectState({
|
|
||||||
highlightedCode: '',
|
|
||||||
activeLines: ['|>startProfileAt([-73.64,-42.89],%)'],
|
|
||||||
diagnostics: [],
|
|
||||||
})
|
})
|
||||||
})
|
const rejectBtn = page.getByRole('button', { name: 'close Reject' })
|
||||||
|
|
||||||
await test.step('fire off edit prompt', async () => {
|
await test.step('wait for scene to load select body and check selection came through', async () => {
|
||||||
await cmdBar.openCmdBar('promptToEdit')
|
await scene.expectPixelColor([134, 134, 134], body1CapCoords, 15)
|
||||||
// being specific about the color with a hex means asserting pixel color is more stable
|
await clickBody1Cap()
|
||||||
await page
|
await scene.expectPixelColor(yellow, body1CapCoords, 20)
|
||||||
.getByTestId('cmd-bar-arg-value')
|
await editor.expectState({
|
||||||
.fill('make this neon green please, use #39FF14')
|
highlightedCode: '',
|
||||||
await page.waitForTimeout(100)
|
activeLines: ['|>startProfileAt([-73.64,-42.89],%)'],
|
||||||
await cmdBar.progressCmdBar()
|
diagnostics: [],
|
||||||
await expect(submittingToast).toBeVisible()
|
})
|
||||||
await expect(submittingToast).not.toBeVisible({ timeout: 2 * 60_000 }) // can take a while
|
})
|
||||||
await expect(successToast).toBeVisible()
|
|
||||||
})
|
|
||||||
|
|
||||||
await test.step('verify initial change', async () => {
|
await test.step('fire off edit prompt', async () => {
|
||||||
await scene.expectPixelColor(green, greenCheckCoords, 15)
|
await cmdBar.openCmdBar('promptToEdit')
|
||||||
await scene.expectPixelColor(body2NotGreen, body2WallCoords, 15)
|
// being specific about the color with a hex means asserting pixel color is more stable
|
||||||
await editor.expectEditor.toContain('appearance({')
|
await page
|
||||||
})
|
.getByTestId('cmd-bar-arg-value')
|
||||||
|
.fill('make this neon green please, use #39FF14')
|
||||||
if (!shouldReject) {
|
await page.waitForTimeout(100)
|
||||||
await test.step('check accept works and can be "undo"ed', async () => {
|
await cmdBar.progressCmdBar()
|
||||||
await acceptBtn.click()
|
await expect(submittingToast).toBeVisible()
|
||||||
await expect(successToast).not.toBeVisible()
|
await expect(submittingToast).not.toBeVisible({
|
||||||
|
timeout: 2 * 60_000,
|
||||||
|
}) // can take a while
|
||||||
|
await expect(successToast).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('verify initial change', async () => {
|
||||||
await scene.expectPixelColor(green, greenCheckCoords, 15)
|
await scene.expectPixelColor(green, greenCheckCoords, 15)
|
||||||
await editor.expectEditor.toContain('appearance({')
|
await scene.expectPixelColor(body2NotGreen, body2WallCoords, 15)
|
||||||
|
await editor.expectEditor.toContain('appearance(')
|
||||||
// ctrl-z works after accepting
|
|
||||||
await page.keyboard.down('ControlOrMeta')
|
|
||||||
await page.keyboard.press('KeyZ')
|
|
||||||
await page.keyboard.up('ControlOrMeta')
|
|
||||||
await editor.expectEditor.not.toContain('appearance({')
|
|
||||||
await scene.expectPixelColor(notGreen, greenCheckCoords, 15)
|
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
await test.step('check reject works', async () => {
|
|
||||||
await rejectBtn.click()
|
|
||||||
await expect(successToast).not.toBeVisible()
|
|
||||||
|
|
||||||
await scene.expectPixelColor(notGreen, greenCheckCoords, 15)
|
if (!shouldReject) {
|
||||||
await editor.expectEditor.not.toContain('appearance({')
|
await test.step('check accept works and can be "undo"ed', async () => {
|
||||||
})
|
await acceptBtn.click()
|
||||||
}
|
await expect(successToast).not.toBeVisible()
|
||||||
})
|
|
||||||
}
|
await scene.expectPixelColor(green, greenCheckCoords, 15)
|
||||||
})
|
await editor.expectEditor.toContain('appearance(')
|
||||||
|
|
||||||
|
// ctrl-z works after accepting
|
||||||
|
await page.keyboard.down('ControlOrMeta')
|
||||||
|
await page.keyboard.press('KeyZ')
|
||||||
|
await page.keyboard.up('ControlOrMeta')
|
||||||
|
await editor.expectEditor.not.toContain('appearance(')
|
||||||
|
await scene.expectPixelColor(notGreen, greenCheckCoords, 15)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
await test.step('check reject works', async () => {
|
||||||
|
await rejectBtn.click()
|
||||||
|
await expect(successToast).not.toBeVisible()
|
||||||
|
|
||||||
|
await scene.expectPixelColor(notGreen, greenCheckCoords, 15)
|
||||||
|
await editor.expectEditor.not.toContain('appearance(')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
test.describe('bad path', { tag: ['@skipWin'] }, () => {
|
|
||||||
test(`bad edit prompt`, async ({
|
test(`bad edit prompt`, async ({
|
||||||
context,
|
context,
|
||||||
homePage,
|
homePage,
|
||||||
@ -148,6 +155,7 @@ test.describe('bad path', { tag: ['@skipWin'] }, () => {
|
|||||||
localStorage.setItem('persistCode', file)
|
localStorage.setItem('persistCode', file)
|
||||||
}, file)
|
}, file)
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
await scene.waitForExecutionDone()
|
||||||
|
|
||||||
const body1CapCoords = { x: 571, y: 351 }
|
const body1CapCoords = { x: 571, y: 351 }
|
||||||
const [clickBody1Cap] = scene.makeMouseHelpers(
|
const [clickBody1Cap] = scene.makeMouseHelpers(
|
||||||
|
@ -251,9 +251,9 @@ extrude001 = extrude(sketch001, length = 50)
|
|||||||
|> yLineTo(0, %)
|
|> yLineTo(0, %)
|
||||||
|> close()
|
|> close()
|
||||||
|>
|
|>
|
||||||
|
|
||||||
example = extrude(exampleSketch, length = 5)
|
example = extrude(exampleSketch, length = 5)
|
||||||
shell({ faces: ['end'], thickness: 0.25 }, exampleSketch)`
|
shell(exampleSketch, faces = ['end'], thickness = 0.25)`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -306,115 +306,113 @@ extrude001 = extrude(sketch001, length = 50)
|
|||||||
|> angledLine({ angle: 50, length: 45 }, %)
|
|> angledLine({ angle: 50, length: 45 }, %)
|
||||||
|> yLineTo(0, %)
|
|> yLineTo(0, %)
|
||||||
|> close()
|
|> close()
|
||||||
|
|
||||||
thing: "blah"`)
|
thing: "blah"`)
|
||||||
|
|
||||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
test('when engine fails export we handle the failure and alert the user', async ({
|
test(
|
||||||
page,
|
'when engine fails export we handle the failure and alert the user',
|
||||||
homePage,
|
{ tag: '@skipLocalEngine' },
|
||||||
}) => {
|
async ({ scene, page, homePage }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.addInitScript(
|
await page.addInitScript(
|
||||||
async ({ code }) => {
|
async ({ code }) => {
|
||||||
localStorage.setItem('persistCode', code)
|
localStorage.setItem('persistCode', code)
|
||||||
;(window as any).playwrightSkipFilePicker = true
|
;(window as any).playwrightSkipFilePicker = true
|
||||||
},
|
},
|
||||||
{ code: TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR }
|
{ code: TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR }
|
||||||
)
|
)
|
||||||
|
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
// wait for execution done
|
// wait for execution done
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
// expect zero errors in guter
|
// expect zero errors in guter
|
||||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
|
|
||||||
// export the model
|
// export the model
|
||||||
const exportButton = page.getByTestId('export-pane-button')
|
const exportButton = page.getByTestId('export-pane-button')
|
||||||
await expect(exportButton).toBeVisible()
|
await expect(exportButton).toBeVisible()
|
||||||
|
|
||||||
// Click the export button
|
// Click the export button
|
||||||
await exportButton.click()
|
await exportButton.click()
|
||||||
|
|
||||||
// Click the stl.
|
// Click the stl.
|
||||||
const stlOption = page.getByText('glTF')
|
const stlOption = page.getByText('glTF')
|
||||||
await expect(stlOption).toBeVisible()
|
await expect(stlOption).toBeVisible()
|
||||||
|
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
// Click the checkbox
|
// Click the checkbox
|
||||||
const submitButton = page.getByText('Confirm Export')
|
const submitButton = page.getByText('Confirm Export')
|
||||||
await expect(submitButton).toBeVisible()
|
await expect(submitButton).toBeVisible()
|
||||||
|
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
// Find the toast.
|
// Find the toast.
|
||||||
// Look out for the toast message
|
// Look out for the toast message
|
||||||
const exportingToastMessage = page.getByText(`Exporting...`)
|
const exportingToastMessage = page.getByText(`Exporting...`)
|
||||||
const errorToastMessage = page.getByText(`Error while exporting`)
|
const errorToastMessage = page.getByText(`Error while exporting`)
|
||||||
|
|
||||||
const engineErrorToastMessage = page.getByText(`Nothing to export`)
|
const engineErrorToastMessage = page.getByText(`Nothing to export`)
|
||||||
await expect(engineErrorToastMessage).toBeVisible()
|
await expect(engineErrorToastMessage).toBeVisible()
|
||||||
|
|
||||||
// Make sure the exporting toast is gone
|
// Make sure the exporting toast is gone
|
||||||
await expect(exportingToastMessage).not.toBeVisible()
|
await expect(exportingToastMessage).not.toBeVisible()
|
||||||
|
|
||||||
// Click the code editor
|
// Click the code editor
|
||||||
await page.locator('.cm-content').click()
|
await page.locator('.cm-content').click()
|
||||||
|
|
||||||
await page.waitForTimeout(2000)
|
await page.waitForTimeout(2000)
|
||||||
|
|
||||||
// Expect the toast to be gone
|
// Expect the toast to be gone
|
||||||
await expect(errorToastMessage).not.toBeVisible()
|
await expect(errorToastMessage).not.toBeVisible()
|
||||||
await expect(engineErrorToastMessage).not.toBeVisible()
|
await expect(engineErrorToastMessage).not.toBeVisible()
|
||||||
|
|
||||||
// Now add in code that works.
|
// Now add in code that works.
|
||||||
await page.locator('.cm-content').fill(bracket)
|
await page.locator('.cm-content').fill(bracket)
|
||||||
await page.keyboard.press('End')
|
await page.keyboard.press('End')
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
// wait for execution done
|
await scene.waitForExecutionDone()
|
||||||
await u.openDebugPanel()
|
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
|
||||||
await u.closeDebugPanel()
|
|
||||||
|
|
||||||
// Now try exporting
|
// Now try exporting
|
||||||
|
|
||||||
// Click the export button
|
// Click the export button
|
||||||
await exportButton.click()
|
await exportButton.click()
|
||||||
|
|
||||||
// Click the stl.
|
// Click the stl.
|
||||||
await expect(stlOption).toBeVisible()
|
await expect(stlOption).toBeVisible()
|
||||||
|
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
// Click the checkbox
|
// Click the checkbox
|
||||||
await expect(submitButton).toBeVisible()
|
await expect(submitButton).toBeVisible()
|
||||||
|
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
// Find the toast.
|
// Find the toast.
|
||||||
// Look out for the toast message
|
// Look out for the toast message
|
||||||
await expect(exportingToastMessage).toBeVisible()
|
await expect(exportingToastMessage).toBeVisible()
|
||||||
|
|
||||||
// Expect it to succeed.
|
// Expect it to succeed.
|
||||||
await expect(exportingToastMessage).not.toBeVisible({ timeout: 15_000 })
|
await expect(exportingToastMessage).not.toBeVisible({ timeout: 15_000 })
|
||||||
await expect(errorToastMessage).not.toBeVisible()
|
await expect(errorToastMessage).not.toBeVisible()
|
||||||
await expect(engineErrorToastMessage).not.toBeVisible()
|
await expect(engineErrorToastMessage).not.toBeVisible()
|
||||||
|
|
||||||
const successToastMessage = page.getByText(`Exported successfully`)
|
const successToastMessage = page.getByText(`Exported successfully`)
|
||||||
await expect(successToastMessage).toBeVisible()
|
await expect(successToastMessage).toBeVisible()
|
||||||
})
|
}
|
||||||
|
)
|
||||||
test(
|
test(
|
||||||
'ensure you can not export while an export is already going',
|
'ensure you can not export while an export is already going',
|
||||||
{ tag: ['@skipLinux', '@skipWin'] },
|
{ tag: ['@skipLinux', '@skipWin'] },
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
PERSIST_MODELING_CONTEXT,
|
PERSIST_MODELING_CONTEXT,
|
||||||
} from './test-utils'
|
} from './test-utils'
|
||||||
import { uuidv4, roundOff } from 'lib/utils'
|
import { uuidv4, roundOff } from 'lib/utils'
|
||||||
|
import { SceneFixture } from './fixtures/sceneFixture'
|
||||||
|
|
||||||
test.describe('Sketch tests', { tag: ['@skipWin'] }, () => {
|
test.describe('Sketch tests', { tag: ['@skipWin'] }, () => {
|
||||||
test('multi-sketch file shows multiple Edit Sketch buttons', async ({
|
test('multi-sketch file shows multiple Edit Sketch buttons', async ({
|
||||||
@ -184,7 +185,8 @@ test.describe('Sketch tests', { tag: ['@skipWin'] }, () => {
|
|||||||
const doEditSegmentsByDraggingHandle = async (
|
const doEditSegmentsByDraggingHandle = async (
|
||||||
page: Page,
|
page: Page,
|
||||||
homePage: HomePageFixture,
|
homePage: HomePageFixture,
|
||||||
openPanes: string[]
|
openPanes: string[],
|
||||||
|
scene: SceneFixture
|
||||||
) => {
|
) => {
|
||||||
// Load the app with the code panes
|
// Load the app with the code panes
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
@ -200,6 +202,7 @@ test.describe('Sketch tests', { tag: ['@skipWin'] }, () => {
|
|||||||
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
await scene.waitForExecutionDone()
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
@ -317,7 +320,7 @@ test.describe('Sketch tests', { tag: ['@skipWin'] }, () => {
|
|||||||
test(
|
test(
|
||||||
'code pane open at start-handles',
|
'code pane open at start-handles',
|
||||||
{ tag: ['@skipWin'] },
|
{ tag: ['@skipWin'] },
|
||||||
async ({ page, homePage }) => {
|
async ({ page, homePage, scene }) => {
|
||||||
// Load the app with the code panes
|
// Load the app with the code panes
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
@ -330,14 +333,14 @@ test.describe('Sketch tests', { tag: ['@skipWin'] }, () => {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
await doEditSegmentsByDraggingHandle(page, homePage, ['code'])
|
await doEditSegmentsByDraggingHandle(page, homePage, ['code'], scene)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'code pane closed at start-handles',
|
'code pane closed at start-handles',
|
||||||
{ tag: ['@skipWin'] },
|
{ tag: ['@skipWin'] },
|
||||||
async ({ page, homePage }) => {
|
async ({ page, homePage, scene }) => {
|
||||||
// Load the app with the code panes
|
// Load the app with the code panes
|
||||||
await page.addInitScript(async (persistModelingContext) => {
|
await page.addInitScript(async (persistModelingContext) => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
@ -345,7 +348,7 @@ test.describe('Sketch tests', { tag: ['@skipWin'] }, () => {
|
|||||||
JSON.stringify({ openPanes: [] })
|
JSON.stringify({ openPanes: [] })
|
||||||
)
|
)
|
||||||
}, PERSIST_MODELING_CONTEXT)
|
}, PERSIST_MODELING_CONTEXT)
|
||||||
await doEditSegmentsByDraggingHandle(page, homePage, [])
|
await doEditSegmentsByDraggingHandle(page, homePage, [], scene)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -547,6 +550,7 @@ test.describe('Sketch tests', { tag: ['@skipWin'] }, () => {
|
|||||||
test('Can edit a sketch that has been revolved in the same pipe', async ({
|
test('Can edit a sketch that has been revolved in the same pipe', async ({
|
||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
|
scene,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
@ -562,6 +566,7 @@ test.describe('Sketch tests', { tag: ['@skipWin'] }, () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
await scene.waitForExecutionDone()
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
|
@ -1187,14 +1187,12 @@ sweepSketch = startSketchOn('XY')
|
|||||||
angleStart = 0,
|
angleStart = 0,
|
||||||
radius = 2
|
radius = 2
|
||||||
}, %)
|
}, %)
|
||||||
|> sweep({
|
|> sweep(path = sweepPath)
|
||||||
path = sweepPath,
|
|> appearance(
|
||||||
}, %)
|
|
||||||
|> appearance({
|
|
||||||
color = "#bb00ff",
|
color = "#bb00ff",
|
||||||
metalness = 90,
|
metalness = 90,
|
||||||
roughness = 90
|
roughness = 90
|
||||||
}, %)
|
)
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -1235,14 +1233,12 @@ sweepSketch = startSketchOn('XY')
|
|||||||
angleStart = 0,
|
angleStart = 0,
|
||||||
radius = 2
|
radius = 2
|
||||||
}, %)
|
}, %)
|
||||||
|> sweep({
|
|> sweep(path = sweepPath)
|
||||||
path = sweepPath,
|
|> appearance(
|
||||||
}, %)
|
|
||||||
|> appearance({
|
|
||||||
color = "#bb00ff",
|
color = "#bb00ff",
|
||||||
metalness = 90,
|
metalness = 90,
|
||||||
roughness = 90
|
roughness = 90
|
||||||
}, %)
|
)
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 145 KiB After Width: | Height: | Size: 143 KiB |
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 127 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
@ -3,199 +3,200 @@ import { test, expect } from './zoo-test'
|
|||||||
import { commonPoints, getUtils } from './test-utils'
|
import { commonPoints, getUtils } from './test-utils'
|
||||||
|
|
||||||
test.describe('Test network and connection issues', () => {
|
test.describe('Test network and connection issues', () => {
|
||||||
test('simulate network down and network little widget', async ({
|
test(
|
||||||
page,
|
'simulate network down and network little widget',
|
||||||
homePage,
|
{ tag: '@skipLocalEngine' },
|
||||||
}) => {
|
async ({ page, homePage }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
const networkToggle = page.getByTestId('network-toggle')
|
const networkToggle = page.getByTestId('network-toggle')
|
||||||
|
|
||||||
// This is how we wait until the stream is online
|
// This is how we wait until the stream is online
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
).not.toBeDisabled({ timeout: 15000 })
|
).not.toBeDisabled({ timeout: 15000 })
|
||||||
|
|
||||||
const networkWidget = page.locator('[data-testid="network-toggle"]')
|
const networkWidget = page.locator('[data-testid="network-toggle"]')
|
||||||
await expect(networkWidget).toBeVisible()
|
await expect(networkWidget).toBeVisible()
|
||||||
await networkWidget.hover()
|
await networkWidget.hover()
|
||||||
|
|
||||||
const networkPopover = page.locator('[data-testid="network-popover"]')
|
const networkPopover = page.locator('[data-testid="network-popover"]')
|
||||||
await expect(networkPopover).not.toBeVisible()
|
await expect(networkPopover).not.toBeVisible()
|
||||||
|
|
||||||
// (First check) Expect the network to be up
|
// (First check) Expect the network to be up
|
||||||
await expect(networkToggle).toContainText('Connected')
|
await expect(networkToggle).toContainText('Connected')
|
||||||
|
|
||||||
// Click the network widget
|
// Click the network widget
|
||||||
await networkWidget.click()
|
await networkWidget.click()
|
||||||
|
|
||||||
// Check the modal opened.
|
// Check the modal opened.
|
||||||
await expect(networkPopover).toBeVisible()
|
await expect(networkPopover).toBeVisible()
|
||||||
|
|
||||||
// Click off the modal.
|
// Click off the modal.
|
||||||
await page.mouse.click(100, 100)
|
await page.mouse.click(100, 100)
|
||||||
await expect(networkPopover).not.toBeVisible()
|
await expect(networkPopover).not.toBeVisible()
|
||||||
|
|
||||||
// Turn off the network
|
// Turn off the network
|
||||||
await u.emulateNetworkConditions({
|
await u.emulateNetworkConditions({
|
||||||
offline: true,
|
offline: true,
|
||||||
// values of 0 remove any active throttling. crbug.com/456324#c9
|
// values of 0 remove any active throttling. crbug.com/456324#c9
|
||||||
latency: 0,
|
latency: 0,
|
||||||
downloadThroughput: -1,
|
downloadThroughput: -1,
|
||||||
uploadThroughput: -1,
|
uploadThroughput: -1,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Expect the network to be down
|
// Expect the network to be down
|
||||||
await expect(networkToggle).toContainText('Problem')
|
await expect(networkToggle).toContainText('Problem')
|
||||||
|
|
||||||
// Click the network widget
|
// Click the network widget
|
||||||
await networkWidget.click()
|
await networkWidget.click()
|
||||||
|
|
||||||
// Check the modal opened.
|
// Check the modal opened.
|
||||||
await expect(networkPopover).toBeVisible()
|
await expect(networkPopover).toBeVisible()
|
||||||
|
|
||||||
// Click off the modal.
|
// Click off the modal.
|
||||||
await page.mouse.click(0, 0)
|
await page.mouse.click(0, 0)
|
||||||
await expect(networkPopover).not.toBeVisible()
|
await expect(networkPopover).not.toBeVisible()
|
||||||
|
|
||||||
// Turn back on the network
|
// Turn back on the network
|
||||||
await u.emulateNetworkConditions({
|
await u.emulateNetworkConditions({
|
||||||
offline: false,
|
offline: false,
|
||||||
// values of 0 remove any active throttling. crbug.com/456324#c9
|
// values of 0 remove any active throttling. crbug.com/456324#c9
|
||||||
latency: 0,
|
latency: 0,
|
||||||
downloadThroughput: -1,
|
downloadThroughput: -1,
|
||||||
uploadThroughput: -1,
|
uploadThroughput: -1,
|
||||||
})
|
})
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
).not.toBeDisabled({ timeout: 15000 })
|
).not.toBeDisabled({ timeout: 15000 })
|
||||||
|
|
||||||
// (Second check) expect the network to be up
|
// (Second check) expect the network to be up
|
||||||
await expect(networkToggle).toContainText('Connected')
|
await expect(networkToggle).toContainText('Connected')
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
test('Engine disconnect & reconnect in sketch mode', async ({
|
test(
|
||||||
page,
|
'Engine disconnect & reconnect in sketch mode',
|
||||||
homePage,
|
{ tag: '@skipLocalEngine' },
|
||||||
}) => {
|
async ({ page, homePage }) => {
|
||||||
// TODO: Don't skip Mac for these. After `window.tearDown` is working in Safari, these should work on webkit
|
// TODO: Don't skip Mac for these. After `window.tearDown` is working in Safari, these should work on webkit
|
||||||
const networkToggle = page.getByTestId('network-toggle')
|
const networkToggle = page.getByTestId('network-toggle')
|
||||||
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
// click on "Start Sketch" button
|
// click on "Start Sketch" button
|
||||||
await u.clearCommandLogs()
|
await u.clearCommandLogs()
|
||||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
// select a plane
|
// select a plane
|
||||||
await page.mouse.click(700, 200)
|
await page.mouse.click(700, 200)
|
||||||
|
|
||||||
await expect(page.locator('.cm-content')).toHaveText(
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
`sketch001 = startSketchOn('XZ')`
|
`sketch001 = startSketchOn('XZ')`
|
||||||
)
|
)
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
||||||
|
|
||||||
const startXPx = 600
|
const startXPx = 600
|
||||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)`)
|
|> startProfileAt(${commonPoints.startAt}, %)`)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> xLine(${commonPoints.num1}, %)`)
|
|> xLine(${commonPoints.num1}, %)`)
|
||||||
|
|
||||||
// Expect the network to be up
|
// Expect the network to be up
|
||||||
await expect(networkToggle).toContainText('Connected')
|
await expect(networkToggle).toContainText('Connected')
|
||||||
|
|
||||||
// simulate network down
|
// simulate network down
|
||||||
await u.emulateNetworkConditions({
|
await u.emulateNetworkConditions({
|
||||||
offline: true,
|
offline: true,
|
||||||
// values of 0 remove any active throttling. crbug.com/456324#c9
|
// values of 0 remove any active throttling. crbug.com/456324#c9
|
||||||
latency: 0,
|
latency: 0,
|
||||||
downloadThroughput: -1,
|
downloadThroughput: -1,
|
||||||
uploadThroughput: -1,
|
uploadThroughput: -1,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Expect the network to be down
|
// Expect the network to be down
|
||||||
await expect(networkToggle).toContainText('Problem')
|
await expect(networkToggle).toContainText('Problem')
|
||||||
|
|
||||||
// Ensure we are not in sketch mode
|
// Ensure we are not in sketch mode
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Exit Sketch' })
|
page.getByRole('button', { name: 'Exit Sketch' })
|
||||||
).not.toBeVisible()
|
).not.toBeVisible()
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
|
|
||||||
// simulate network up
|
// simulate network up
|
||||||
await u.emulateNetworkConditions({
|
await u.emulateNetworkConditions({
|
||||||
offline: false,
|
offline: false,
|
||||||
// values of 0 remove any active throttling. crbug.com/456324#c9
|
// values of 0 remove any active throttling. crbug.com/456324#c9
|
||||||
latency: 0,
|
latency: 0,
|
||||||
downloadThroughput: -1,
|
downloadThroughput: -1,
|
||||||
uploadThroughput: -1,
|
uploadThroughput: -1,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Wait for the app to be ready for use
|
// Wait for the app to be ready for use
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
).not.toBeDisabled({ timeout: 15000 })
|
).not.toBeDisabled({ timeout: 15000 })
|
||||||
|
|
||||||
// Expect the network to be up
|
// Expect the network to be up
|
||||||
await expect(networkToggle).toContainText('Connected')
|
await expect(networkToggle).toContainText('Connected')
|
||||||
await expect(page.getByTestId('loading-stream')).not.toBeAttached()
|
await expect(page.getByTestId('loading-stream')).not.toBeAttached()
|
||||||
|
|
||||||
// Click off the code pane.
|
// Click off the code pane.
|
||||||
await page.mouse.click(100, 100)
|
await page.mouse.click(100, 100)
|
||||||
|
|
||||||
// select a line
|
// select a line
|
||||||
await page.getByText(`startProfileAt(${commonPoints.startAt}, %)`).click()
|
await page.getByText(`startProfileAt(${commonPoints.startAt}, %)`).click()
|
||||||
|
|
||||||
// enter sketch again
|
// enter sketch again
|
||||||
await u.doAndWaitForCmd(
|
await u.doAndWaitForCmd(
|
||||||
() => page.getByRole('button', { name: 'Edit Sketch' }).click(),
|
() => page.getByRole('button', { name: 'Edit Sketch' }).click(),
|
||||||
'default_camera_get_settings'
|
'default_camera_get_settings'
|
||||||
)
|
)
|
||||||
await page.waitForTimeout(150)
|
await page.waitForTimeout(150)
|
||||||
|
|
||||||
// Click the line tool
|
// Click the line tool
|
||||||
await page.getByRole('button', { name: 'line Line', exact: true }).click()
|
await page.getByRole('button', { name: 'line Line', exact: true }).click()
|
||||||
|
|
||||||
await page.waitForTimeout(150)
|
await page.waitForTimeout(150)
|
||||||
|
|
||||||
// Ensure we can continue sketching
|
// Ensure we can continue sketching
|
||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
||||||
await expect.poll(u.normalisedEditorCode)
|
await expect.poll(u.normalisedEditorCode)
|
||||||
.toBe(`sketch001 = startSketchOn('XZ')
|
.toBe(`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([12.34, -12.34], %)
|
|> startProfileAt([12.34, -12.34], %)
|
||||||
|> xLine(12.34, %)
|
|> xLine(12.34, %)
|
||||||
|> line(end = [-12.34, 12.34])
|
|> line(end = [-12.34, 12.34])
|
||||||
|
|
||||||
`)
|
`)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await page.mouse.click(startXPx, 500 - PUR * 20)
|
await page.mouse.click(startXPx, 500 - PUR * 20)
|
||||||
|
|
||||||
await expect.poll(u.normalisedEditorCode)
|
await expect.poll(u.normalisedEditorCode)
|
||||||
.toBe(`sketch001 = startSketchOn('XZ')
|
.toBe(`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([12.34, -12.34], %)
|
|> startProfileAt([12.34, -12.34], %)
|
||||||
|> xLine(12.34, %)
|
|> xLine(12.34, %)
|
||||||
|> line(end = [-12.34, 12.34])
|
|> line(end = [-12.34, 12.34])
|
||||||
@ -203,20 +204,21 @@ test.describe('Test network and connection issues', () => {
|
|||||||
|
|
||||||
`)
|
`)
|
||||||
|
|
||||||
// Unequip line tool
|
// Unequip line tool
|
||||||
await page.keyboard.press('Escape')
|
await page.keyboard.press('Escape')
|
||||||
// Make sure we didn't pop out of sketch mode.
|
// Make sure we didn't pop out of sketch mode.
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Exit Sketch' })
|
page.getByRole('button', { name: 'Exit Sketch' })
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'line Line', exact: true })
|
page.getByRole('button', { name: 'line Line', exact: true })
|
||||||
).not.toHaveAttribute('aria-pressed', 'true')
|
).not.toHaveAttribute('aria-pressed', 'true')
|
||||||
|
|
||||||
// Exit sketch
|
// Exit sketch
|
||||||
await page.keyboard.press('Escape')
|
await page.keyboard.press('Escape')
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Exit Sketch' })
|
page.getByRole('button', { name: 'Exit Sketch' })
|
||||||
).not.toBeVisible()
|
).not.toBeVisible()
|
||||||
})
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
@ -109,7 +109,8 @@ test.describe('Testing Camera Movement', { tag: ['@skipWin'] }, () => {
|
|||||||
await page.keyboard.down('Shift')
|
await page.keyboard.down('Shift')
|
||||||
await page.mouse.move(600, 200)
|
await page.mouse.move(600, 200)
|
||||||
await page.mouse.down({ button: 'right' })
|
await page.mouse.down({ button: 'right' })
|
||||||
await page.mouse.move(700, 200, { steps: 2 })
|
// Gotcha: remove steps:2 from this 700,200 mouse move. This bricked the test on local host engine.
|
||||||
|
await page.mouse.move(700, 200)
|
||||||
await page.mouse.up({ button: 'right' })
|
await page.mouse.up({ button: 'right' })
|
||||||
await page.keyboard.up('Shift')
|
await page.keyboard.up('Shift')
|
||||||
}, [-19, -85, -85])
|
}, [-19, -85, -85])
|
||||||
|
@ -248,7 +248,11 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Solids should be select and deletable', async ({ page, homePage }) => {
|
test('Solids should be select and deletable', async ({
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
|
scene,
|
||||||
|
}) => {
|
||||||
test.setTimeout(90_000)
|
test.setTimeout(90_000)
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
@ -320,10 +324,7 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
|||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
await scene.waitForExecutionDone()
|
||||||
await u.openDebugPanel()
|
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
|
||||||
await u.closeDebugPanel()
|
|
||||||
|
|
||||||
await u.openAndClearDebugPanel()
|
await u.openAndClearDebugPanel()
|
||||||
await u.sendCustomCmd({
|
await u.sendCustomCmd({
|
||||||
@ -902,6 +903,7 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
|||||||
test('Testing selections (and hovers) work on sketches when NOT in sketch mode', async ({
|
test('Testing selections (and hovers) work on sketches when NOT in sketch mode', async ({
|
||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
|
scene,
|
||||||
}) => {
|
}) => {
|
||||||
const cases = [
|
const cases = [
|
||||||
{
|
{
|
||||||
@ -937,6 +939,7 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
|||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
await scene.waitForExecutionDone()
|
||||||
await u.openAndClearDebugPanel()
|
await u.openAndClearDebugPanel()
|
||||||
|
|
||||||
await u.sendCustomCmd({
|
await u.sendCustomCmd({
|
||||||
@ -970,6 +973,7 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
|||||||
test("Hovering and selection of extruded faces works, and is not overridden shortly after user's click", async ({
|
test("Hovering and selection of extruded faces works, and is not overridden shortly after user's click", async ({
|
||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
|
scene,
|
||||||
}) => {
|
}) => {
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
@ -988,6 +992,7 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
|||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
await scene.waitForExecutionDone()
|
||||||
await u.openAndClearDebugPanel()
|
await u.openAndClearDebugPanel()
|
||||||
|
|
||||||
await u.sendCustomCmd({
|
await u.sendCustomCmd({
|
||||||
@ -1021,19 +1026,19 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
|||||||
.poll(() => u.getGreatestPixDiff(extrudeWall, noHoverColor))
|
.poll(() => u.getGreatestPixDiff(extrudeWall, noHoverColor))
|
||||||
.toBeLessThan(15)
|
.toBeLessThan(15)
|
||||||
await page.mouse.move(nothing.x, nothing.y)
|
await page.mouse.move(nothing.x, nothing.y)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(1000)
|
||||||
await page.mouse.move(extrudeWall.x, extrudeWall.y)
|
await page.mouse.move(extrudeWall.x, extrudeWall.y)
|
||||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible()
|
await expect(page.getByTestId('hover-highlight').first()).toBeVisible()
|
||||||
await expect(page.getByTestId('hover-highlight').first()).toContainText(
|
await expect(page.getByTestId('hover-highlight').first()).toContainText(
|
||||||
removeAfterFirstParenthesis(extrudeText)
|
removeAfterFirstParenthesis(extrudeText)
|
||||||
)
|
)
|
||||||
await page.waitForTimeout(200)
|
await page.waitForTimeout(1000)
|
||||||
await expect(
|
await expect(
|
||||||
await u.getGreatestPixDiff(extrudeWall, hoverColor)
|
await u.getGreatestPixDiff(extrudeWall, hoverColor)
|
||||||
).toBeLessThan(15)
|
).toBeLessThan(15)
|
||||||
await page.mouse.click(extrudeWall.x, extrudeWall.y)
|
await page.mouse.click(extrudeWall.x, extrudeWall.y)
|
||||||
await expect(page.locator('.cm-activeLine')).toHaveText(`|> ${extrudeText}`)
|
await expect(page.locator('.cm-activeLine')).toHaveText(`|> ${extrudeText}`)
|
||||||
await page.waitForTimeout(200)
|
await page.waitForTimeout(1000)
|
||||||
await expect(
|
await expect(
|
||||||
await u.getGreatestPixDiff(extrudeWall, selectColor)
|
await u.getGreatestPixDiff(extrudeWall, selectColor)
|
||||||
).toBeLessThan(15)
|
).toBeLessThan(15)
|
||||||
@ -1044,7 +1049,7 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
|||||||
).toBeLessThan(15)
|
).toBeLessThan(15)
|
||||||
|
|
||||||
await page.mouse.move(nothing.x, nothing.y)
|
await page.mouse.move(nothing.x, nothing.y)
|
||||||
await page.waitForTimeout(300)
|
await page.waitForTimeout(1000)
|
||||||
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
||||||
|
|
||||||
// because of shading, color is not exact everywhere on the face
|
// because of shading, color is not exact everywhere on the face
|
||||||
@ -1058,11 +1063,11 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
|||||||
await expect(page.getByTestId('hover-highlight').first()).toContainText(
|
await expect(page.getByTestId('hover-highlight').first()).toContainText(
|
||||||
removeAfterFirstParenthesis(capText)
|
removeAfterFirstParenthesis(capText)
|
||||||
)
|
)
|
||||||
await page.waitForTimeout(200)
|
await page.waitForTimeout(1000)
|
||||||
await expect(await u.getGreatestPixDiff(cap, hoverColor)).toBeLessThan(15)
|
await expect(await u.getGreatestPixDiff(cap, hoverColor)).toBeLessThan(15)
|
||||||
await page.mouse.click(cap.x, cap.y)
|
await page.mouse.click(cap.x, cap.y)
|
||||||
await expect(page.locator('.cm-activeLine')).toHaveText(`|> ${capText}`)
|
await expect(page.locator('.cm-activeLine')).toHaveText(`|> ${capText}`)
|
||||||
await page.waitForTimeout(200)
|
await page.waitForTimeout(1000)
|
||||||
await expect(await u.getGreatestPixDiff(cap, selectColor)).toBeLessThan(15)
|
await expect(await u.getGreatestPixDiff(cap, selectColor)).toBeLessThan(15)
|
||||||
await page.waitForTimeout(1000)
|
await page.waitForTimeout(1000)
|
||||||
// check color stays there, i.e. not overridden (this was a bug previously)
|
// check color stays there, i.e. not overridden (this was a bug previously)
|
||||||
@ -1109,7 +1114,7 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
|||||||
|> line(end = [4.95, -8])
|
|> line(end = [4.95, -8])
|
||||||
|> line(end = [-20.38, -10.12])
|
|> line(end = [-20.38, -10.12])
|
||||||
|> line(end = [-15.79, 17.08])
|
|> line(end = [-15.79, 17.08])
|
||||||
|
|
||||||
fn yohey = (pos) => {
|
fn yohey = (pos) => {
|
||||||
sketch004 = startSketchOn('XZ')
|
sketch004 = startSketchOn('XZ')
|
||||||
${extrudeAndEditBlockedInFunction}
|
${extrudeAndEditBlockedInFunction}
|
||||||
@ -1119,7 +1124,7 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
|||||||
|> line(end = [-15.79, 17.08])
|
|> line(end = [-15.79, 17.08])
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
yohey([15.79, -34.6])
|
yohey([15.79, -34.6])
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
@ -896,4 +896,53 @@ test.describe('Testing settings', () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
test(`Change inline units setting`, async ({
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
|
context,
|
||||||
|
editor,
|
||||||
|
}) => {
|
||||||
|
const initialInlineUnits = 'yd'
|
||||||
|
const editedInlineUnits = { short: 'mm', long: 'Millimeters' }
|
||||||
|
const inlineSettingsString = (s: string) =>
|
||||||
|
`@settings(defaultLengthUnit = ${s})`
|
||||||
|
const unitsIndicator = page.getByRole('button', {
|
||||||
|
name: 'Current units are:',
|
||||||
|
})
|
||||||
|
const unitsChangeButton = (name: string) =>
|
||||||
|
page.getByRole('button', { name, exact: true })
|
||||||
|
|
||||||
|
await context.folderSetupFn(async (dir) => {
|
||||||
|
const bracketDir = join(dir, 'project-000')
|
||||||
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
|
await fsp.copyFile(
|
||||||
|
executorInputPath('cube.kcl'),
|
||||||
|
join(bracketDir, 'main.kcl')
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Initial units from settings`, async () => {
|
||||||
|
await homePage.openProject('project-000')
|
||||||
|
await expect(unitsIndicator).toHaveText('Current units are: in')
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Manually write inline settings`, async () => {
|
||||||
|
await editor.openPane()
|
||||||
|
await editor.replaceCode(
|
||||||
|
`fn cube`,
|
||||||
|
`${inlineSettingsString(initialInlineUnits)}
|
||||||
|
fn cube`
|
||||||
|
)
|
||||||
|
await expect(unitsIndicator).toContainText(initialInlineUnits)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Change units setting via lower-right control`, async () => {
|
||||||
|
await unitsIndicator.click()
|
||||||
|
await unitsChangeButton(editedInlineUnits.long).click()
|
||||||
|
await expect(
|
||||||
|
page.getByText(`Updated per-file units to ${editedInlineUnits.short}`)
|
||||||
|
).toBeVisible()
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -3,7 +3,7 @@ import { getUtils, createProject } from './test-utils'
|
|||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
|
|
||||||
test.describe('Text-to-CAD tests', () => {
|
test.describe('Text-to-CAD tests', { tag: ['@skipWin'] }, () => {
|
||||||
test('basic lego happy case', async ({ page, homePage }) => {
|
test('basic lego happy case', async ({ page, homePage }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
|
@ -32,15 +32,18 @@ test.fixme('Units menu', async ({ page, homePage }) => {
|
|||||||
await expect(unitsMenuButton).toContainText('mm')
|
await expect(unitsMenuButton).toContainText('mm')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Successful export shows a success toast', async ({ page, homePage }) => {
|
test(
|
||||||
// FYI this test doesn't work with only engine running locally
|
'Successful export shows a success toast',
|
||||||
// And you will need to have the KittyCAD CLI installed
|
{ tag: '@skipLocalEngine' },
|
||||||
const u = await getUtils(page)
|
async ({ page, homePage }) => {
|
||||||
await page.addInitScript(async () => {
|
// FYI this test doesn't work with only engine running locally
|
||||||
;(window as any).playwrightSkipFilePicker = true
|
// And you will need to have the KittyCAD CLI installed
|
||||||
localStorage.setItem(
|
const u = await getUtils(page)
|
||||||
'persistCode',
|
await page.addInitScript(async () => {
|
||||||
`topAng = 25
|
;(window as any).playwrightSkipFilePicker = true
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`topAng = 25
|
||||||
bottomAng = 35
|
bottomAng = 35
|
||||||
baseLen = 3.5
|
baseLen = 3.5
|
||||||
baseHeight = 1
|
baseHeight = 1
|
||||||
@ -78,26 +81,27 @@ part001 = startSketchOn('-XZ')
|
|||||||
|> xLineTo(ZERO, %)
|
|> xLineTo(ZERO, %)
|
||||||
|> close()
|
|> close()
|
||||||
|> extrude(length = 4)`
|
|> extrude(length = 4)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.waitForCmdReceive('extrude')
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
await u.clearAndCloseDebugPanel()
|
||||||
|
|
||||||
|
await doExport(
|
||||||
|
{
|
||||||
|
type: 'gltf',
|
||||||
|
storage: 'embedded',
|
||||||
|
presentation: 'pretty',
|
||||||
|
},
|
||||||
|
page
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
)
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
|
||||||
await u.openDebugPanel()
|
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
|
||||||
await u.waitForCmdReceive('extrude')
|
|
||||||
await page.waitForTimeout(1000)
|
|
||||||
await u.clearAndCloseDebugPanel()
|
|
||||||
|
|
||||||
await doExport(
|
|
||||||
{
|
|
||||||
type: 'gltf',
|
|
||||||
storage: 'embedded',
|
|
||||||
presentation: 'pretty',
|
|
||||||
},
|
|
||||||
page
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Paste should not work unless an input is focused', async ({
|
test('Paste should not work unless an input is focused', async ({
|
||||||
page,
|
page,
|
||||||
@ -444,7 +448,7 @@ test('Delete key does not navigate back', async ({ page, homePage }) => {
|
|||||||
await expect.poll(() => page.url()).not.toContain('/settings')
|
await expect.poll(() => page.url()).not.toContain('/settings')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Sketch on face', async ({ page, homePage }) => {
|
test('Sketch on face', async ({ page, homePage, scene, cmdBar }) => {
|
||||||
test.setTimeout(90_000)
|
test.setTimeout(90_000)
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
@ -470,11 +474,7 @@ extrude001 = extrude(sketch001, length = 5 + 7)`
|
|||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
await scene.waitForExecutionDone()
|
||||||
// wait for execution done
|
|
||||||
await u.openDebugPanel()
|
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
|
||||||
await u.closeDebugPanel()
|
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
@ -579,10 +579,9 @@ extrude001 = extrude(sketch001, length = 5 + 7)`
|
|||||||
await expect(page.getByTestId('command-bar')).toBeVisible()
|
await expect(page.getByTestId('command-bar')).toBeVisible()
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'arrow right Continue' }).click()
|
await cmdBar.progressCmdBar()
|
||||||
await page.waitForTimeout(100)
|
|
||||||
await expect(page.getByText('Confirm Extrude')).toBeVisible()
|
await expect(page.getByText('Confirm Extrude')).toBeVisible()
|
||||||
await page.getByRole('button', { name: 'checkmark Submit command' }).click()
|
await cmdBar.progressCmdBar()
|
||||||
|
|
||||||
const result2 = result.genNext`
|
const result2 = result.genNext`
|
||||||
const sketch002 = extrude(sketch002, length = ${[5, 5]} + 7)`
|
const sketch002 = extrude(sketch002, length = ${[5, 5]} + 7)`
|
||||||
|
@ -32,10 +32,6 @@ win:
|
|||||||
arch:
|
arch:
|
||||||
- x64
|
- x64
|
||||||
- arm64
|
- arm64
|
||||||
# - target: msi
|
|
||||||
# arch:
|
|
||||||
# - x64
|
|
||||||
# - arm64
|
|
||||||
signingHashAlgorithms:
|
signingHashAlgorithms:
|
||||||
- sha256
|
- sha256
|
||||||
sign: "./scripts/sign-win.js"
|
sign: "./scripts/sign-win.js"
|
||||||
@ -47,15 +43,12 @@ win:
|
|||||||
mimeType: text/vnd.zoo.kcl
|
mimeType: text/vnd.zoo.kcl
|
||||||
description: Zoo KCL File
|
description: Zoo KCL File
|
||||||
role: Editor
|
role: Editor
|
||||||
# msi:
|
|
||||||
# oneClick: false
|
|
||||||
# perMachine: true
|
|
||||||
nsis:
|
nsis:
|
||||||
oneClick: false
|
oneClick: false
|
||||||
perMachine: true
|
perMachine: true
|
||||||
allowElevation: true
|
allowElevation: true
|
||||||
installerIcon: "assets/icon.ico"
|
installerIcon: "assets/icon.ico"
|
||||||
include: "./installer.nsh"
|
include: "./scripts/installer.nsh"
|
||||||
linux:
|
linux:
|
||||||
artifactName: "${productName}-${version}-${arch}-${os}.${ext}"
|
artifactName: "${productName}-${version}-${arch}-${os}.${ext}"
|
||||||
target:
|
target:
|
||||||
|
@ -85,7 +85,7 @@
|
|||||||
"fmt": "prettier --write ./src *.ts *.json *.js ./e2e ./packages",
|
"fmt": "prettier --write ./src *.ts *.json *.js ./e2e ./packages",
|
||||||
"fmt-check": "prettier --check ./src *.ts *.json *.js ./e2e ./packages",
|
"fmt-check": "prettier --check ./src *.ts *.json *.js ./e2e ./packages",
|
||||||
"fetch:wasm": "./get-latest-wasm-bundle.sh",
|
"fetch:wasm": "./get-latest-wasm-bundle.sh",
|
||||||
"fetch:samples": "echo \"Fetching latest KCL samples...\" && curl -o public/kcl-samples-manifest-fallback.json https://raw.githubusercontent.com/KittyCAD/kcl-samples/main/manifest.json",
|
"fetch:samples": "echo \"Fetching latest KCL samples...\" && curl -o public/kcl-samples-manifest-fallback.json https://raw.githubusercontent.com/KittyCAD/kcl-samples/achalmers/kw-appearance/manifest.json",
|
||||||
"isomorphic-copy-wasm": "(copy src/wasm-lib/pkg/wasm_lib_bg.wasm public || cp src/wasm-lib/pkg/wasm_lib_bg.wasm public)",
|
"isomorphic-copy-wasm": "(copy src/wasm-lib/pkg/wasm_lib_bg.wasm public || cp src/wasm-lib/pkg/wasm_lib_bg.wasm public)",
|
||||||
"build:wasm-dev": "yarn wasm-prep && (cd src/wasm-lib && wasm-pack build --dev --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && yarn isomorphic-copy-wasm && yarn fmt",
|
"build:wasm-dev": "yarn wasm-prep && (cd src/wasm-lib && wasm-pack build --dev --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && yarn isomorphic-copy-wasm && yarn fmt",
|
||||||
"build:wasm": "yarn wasm-prep && cd src/wasm-lib && wasm-pack build --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings && cd ../.. && yarn isomorphic-copy-wasm && yarn fmt",
|
"build:wasm": "yarn wasm-prep && cd src/wasm-lib && wasm-pack build --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings && cd ../.. && yarn isomorphic-copy-wasm && yarn fmt",
|
||||||
@ -120,6 +120,7 @@
|
|||||||
"test:playwright:electron:windows:local": "yarn tronb:vite:dev && set NODE_ENV='development' && playwright test --config=playwright.electron.config.ts --grep-invert=\"@skipWin|@snapshot\"",
|
"test:playwright:electron:windows:local": "yarn tronb:vite:dev && set NODE_ENV='development' && playwright test --config=playwright.electron.config.ts --grep-invert=\"@skipWin|@snapshot\"",
|
||||||
"test:playwright:electron:macos:local": "yarn tronb:vite:dev && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipMacos|@snapshot'",
|
"test:playwright:electron:macos:local": "yarn tronb:vite:dev && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipMacos|@snapshot'",
|
||||||
"test:playwright:electron:ubuntu:local": "yarn tronb:vite:dev && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipLinux|@snapshot'",
|
"test:playwright:electron:ubuntu:local": "yarn tronb:vite:dev && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipLinux|@snapshot'",
|
||||||
|
"test:playwright:electron:ubuntu:engine:local": "yarn tronb:vite:dev && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipLinux|@snapshot|@skipLocalEngine'",
|
||||||
"test:unit:local": "yarn simpleserver:bg && yarn test:unit; kill-port 3000",
|
"test:unit:local": "yarn simpleserver:bg && yarn test:unit; kill-port 3000",
|
||||||
"test:unit:kcl-samples:local": "yarn simpleserver:bg && yarn test:unit:kcl-samples; kill-port 3000"
|
"test:unit:kcl-samples:local": "yarn simpleserver:bg && yarn test:unit:kcl-samples; kill-port 3000"
|
||||||
},
|
},
|
||||||
@ -158,8 +159,8 @@
|
|||||||
"@types/electron": "^1.6.10",
|
"@types/electron": "^1.6.10",
|
||||||
"@types/isomorphic-fetch": "^0.0.39",
|
"@types/isomorphic-fetch": "^0.0.39",
|
||||||
"@types/minimist": "^1.2.5",
|
"@types/minimist": "^1.2.5",
|
||||||
"@types/mocha": "^10.0.6",
|
"@types/mocha": "^10.0.10",
|
||||||
"@types/node": "^22.7.8",
|
"@types/node": "^22.13.1",
|
||||||
"@types/pixelmatch": "^5.2.6",
|
"@types/pixelmatch": "^5.2.6",
|
||||||
"@types/pngjs": "^6.0.4",
|
"@types/pngjs": "^6.0.4",
|
||||||
"@types/react": "^18.3.4",
|
"@types/react": "^18.3.4",
|
||||||
@ -200,7 +201,7 @@
|
|||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.4.1",
|
||||||
"ts-node": "^10.0.0",
|
"ts-node": "^10.0.0",
|
||||||
"typescript": "^5.7.3",
|
"typescript": "^5.7.3",
|
||||||
"typescript-eslint": "^8.19.1",
|
"typescript-eslint": "^8.23.0",
|
||||||
"vite": "^5.4.12",
|
"vite": "^5.4.12",
|
||||||
"vite-plugin-package-version": "^1.1.0",
|
"vite-plugin-package-version": "^1.1.0",
|
||||||
"vite-tsconfig-paths": "^4.3.2",
|
"vite-tsconfig-paths": "^4.3.2",
|
||||||
|
@ -59,7 +59,9 @@ UnaryOp { AddOp | BangOp }
|
|||||||
|
|
||||||
ObjectProperty { PropertyName (":" | Equals) expression }
|
ObjectProperty { PropertyName (":" | Equals) expression }
|
||||||
|
|
||||||
ArgumentList { "(" commaSep<expression> ")" }
|
LabeledArgument { ArgumentLabel Equals expression }
|
||||||
|
|
||||||
|
ArgumentList { "(" commaSep<LabeledArgument | expression> ")" }
|
||||||
|
|
||||||
type[@isGroup=Type] {
|
type[@isGroup=Type] {
|
||||||
@specialize[@name=PrimitiveType]<
|
@specialize[@name=PrimitiveType]<
|
||||||
@ -74,6 +76,8 @@ VariableDefinition { identifier }
|
|||||||
|
|
||||||
VariableName { identifier }
|
VariableName { identifier }
|
||||||
|
|
||||||
|
ArgumentLabel { identifier }
|
||||||
|
|
||||||
@skip { whitespace | LineComment | BlockComment }
|
@skip { whitespace | LineComment | BlockComment }
|
||||||
|
|
||||||
kw<term> { @specialize[@name={term}]<identifier, term> }
|
kw<term> { @specialize[@name={term}]<identifier, term> }
|
||||||
|
85
packages/codemirror-lang-kcl/test/call.txt
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
# empty
|
||||||
|
|
||||||
|
f()
|
||||||
|
|
||||||
|
==>
|
||||||
|
Program(ExpressionStatement(CallExpression(VariableName,
|
||||||
|
ArgumentList)))
|
||||||
|
|
||||||
|
# single anon arg
|
||||||
|
|
||||||
|
f(1)
|
||||||
|
|
||||||
|
==>
|
||||||
|
Program(ExpressionStatement(CallExpression(VariableName,
|
||||||
|
ArgumentList(Number))))
|
||||||
|
|
||||||
|
# deprecated multiple anon args
|
||||||
|
|
||||||
|
f(1, 2)
|
||||||
|
|
||||||
|
==>
|
||||||
|
Program(ExpressionStatement(CallExpression(VariableName,
|
||||||
|
ArgumentList(Number,
|
||||||
|
Number))))
|
||||||
|
|
||||||
|
# deprecated trailing %
|
||||||
|
|
||||||
|
startSketchOn('XY')
|
||||||
|
|> line([thickness, 0], %)
|
||||||
|
|
||||||
|
==>
|
||||||
|
Program(ExpressionStatement(PipeExpression(CallExpression(VariableName,
|
||||||
|
ArgumentList(String)),
|
||||||
|
PipeOperator,
|
||||||
|
CallExpression(VariableName,
|
||||||
|
ArgumentList(ArrayExpression(VariableName,
|
||||||
|
Number),
|
||||||
|
PipeSubstitution)))))
|
||||||
|
|
||||||
|
# % and named arg
|
||||||
|
|
||||||
|
startSketchOn('XY')
|
||||||
|
|> line(%, end = [thickness, 0])
|
||||||
|
|
||||||
|
==>
|
||||||
|
Program(ExpressionStatement(PipeExpression(CallExpression(VariableName,
|
||||||
|
ArgumentList(String)),
|
||||||
|
PipeOperator,
|
||||||
|
CallExpression(VariableName,
|
||||||
|
ArgumentList(PipeSubstitution,
|
||||||
|
LabeledArgument(ArgumentLabel,
|
||||||
|
Equals,
|
||||||
|
ArrayExpression(VariableName,
|
||||||
|
Number)))))))
|
||||||
|
|
||||||
|
# implied % and named arg
|
||||||
|
|
||||||
|
startSketchOn('XY')
|
||||||
|
|> line(end = [thickness, 0])
|
||||||
|
|
||||||
|
==>
|
||||||
|
Program(ExpressionStatement(PipeExpression(CallExpression(VariableName,
|
||||||
|
ArgumentList(String)),
|
||||||
|
PipeOperator,
|
||||||
|
CallExpression(VariableName,
|
||||||
|
ArgumentList(LabeledArgument(ArgumentLabel,
|
||||||
|
Equals,
|
||||||
|
ArrayExpression(VariableName,
|
||||||
|
Number)))))))
|
||||||
|
|
||||||
|
# multiple named arg
|
||||||
|
|
||||||
|
ngon(plane = "XY", numSides = 5, radius = pentR)
|
||||||
|
|
||||||
|
==>
|
||||||
|
Program(ExpressionStatement(CallExpression(VariableName,
|
||||||
|
ArgumentList(LabeledArgument(ArgumentLabel,
|
||||||
|
Equals,
|
||||||
|
String),
|
||||||
|
LabeledArgument(ArgumentLabel,
|
||||||
|
Equals,
|
||||||
|
Number),
|
||||||
|
LabeledArgument(ArgumentLabel,
|
||||||
|
Equals,
|
||||||
|
VariableName)))))
|
@ -29,7 +29,7 @@
|
|||||||
"vscode-uri": "^3.0.8"
|
"vscode-uri": "^3.0.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.10.6",
|
"@types/node": "^22.13.1",
|
||||||
"ts-node": "^10.9.2"
|
"ts-node": "^10.9.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,10 +109,10 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9"
|
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9"
|
||||||
integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==
|
integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==
|
||||||
|
|
||||||
"@types/node@^22.10.6":
|
"@types/node@^22.13.1":
|
||||||
version "22.10.6"
|
version "22.13.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.6.tgz#5c6795e71635876039f853cbccd59f523d9e4239"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.1.tgz#a2a3fefbdeb7ba6b89f40371842162fac0934f33"
|
||||||
integrity sha512-qNiuwC4ZDAUNcY47xgaSuS92cjf8JbSUoaKS77bmLG1rU7MlATVSiw/IlrjtIyyskXBZ8KkNfjK/P5na7rgXbQ==
|
integrity sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew==
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types "~6.20.0"
|
undici-types "~6.20.0"
|
||||||
|
|
||||||
|
@ -1 +1,212 @@
|
|||||||
404: Not Found
|
[
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "80-20-rail/main.kcl",
|
||||||
|
"multipleFiles": false,
|
||||||
|
"title": "80/20 Rail",
|
||||||
|
"description": "An 80/20 extruded aluminum linear rail. T-slot profile adjustable by profile height, rail length, and origin position"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "a-parametric-bearing-pillow-block/main.kcl",
|
||||||
|
"multipleFiles": false,
|
||||||
|
"title": "A Parametric Bearing Pillow Block",
|
||||||
|
"description": "A bearing pillow block, also known as a plummer block or pillow block bearing, is a pedestal used to provide support for a rotating shaft with the help of compatible bearings and various accessories. Housing a bearing, the pillow block provides a secure and stable foundation that allows the shaft to rotate smoothly within its machinery setup. These components are essential in a wide range of mechanical systems and machinery, playing a key role in reducing friction and supporting radial and axial loads."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "ball-bearing/main.kcl",
|
||||||
|
"multipleFiles": false,
|
||||||
|
"title": "Ball Bearing",
|
||||||
|
"description": "A ball bearing is a type of rolling-element bearing that uses balls to maintain the separation between the bearing races. The primary purpose of a ball bearing is to reduce rotational friction and support radial and axial loads."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "bracket/main.kcl",
|
||||||
|
"multipleFiles": false,
|
||||||
|
"title": "Shelf Bracket",
|
||||||
|
"description": "This is a bracket that holds a shelf. It is made of aluminum and is designed to hold a force of 300 lbs. The bracket is 6 inches wide and the force is applied at the end of the shelf, 12 inches from the wall. The bracket has a factor of safety of 1.2. The legs of the bracket are 5 inches and 2 inches long. The thickness of the bracket is calculated from the constraints provided."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "car-wheel-assembly/main.kcl",
|
||||||
|
"multipleFiles": true,
|
||||||
|
"title": "Car Wheel Assembly",
|
||||||
|
"description": "A car wheel assembly with a rotor, tire, and lug nuts."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "dodecahedron/main.kcl",
|
||||||
|
"multipleFiles": false,
|
||||||
|
"title": "Hollow Dodecahedron",
|
||||||
|
"description": "A regular dodecahedron or pentagonal dodecahedron is a dodecahedron composed of regular pentagonal faces, three meeting at each vertex. This example shows constructing the individual faces of the dodecahedron and extruding inwards."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "enclosure/main.kcl",
|
||||||
|
"multipleFiles": false,
|
||||||
|
"title": "Enclosure",
|
||||||
|
"description": "An enclosure body and sealing lid for storing items"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "flange-with-patterns/main.kcl",
|
||||||
|
"multipleFiles": false,
|
||||||
|
"title": "Flange",
|
||||||
|
"description": "A flange is a flat rim, collar, or rib, typically forged or cast, that is used to strengthen an object, guide it, or attach it to another object. Flanges are known for their use in various applications, including piping, plumbing, and mechanical engineering, among others."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "flange-xy/main.kcl",
|
||||||
|
"multipleFiles": false,
|
||||||
|
"title": "Flange with XY coordinates",
|
||||||
|
"description": "A flange is a flat rim, collar, or rib, typically forged or cast, that is used to strengthen an object, guide it, or attach it to another object. Flanges are known for their use in various applications, including piping, plumbing, and mechanical engineering, among others."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "focusrite-scarlett-mounting-bracket/main.kcl",
|
||||||
|
"multipleFiles": false,
|
||||||
|
"title": "A mounting bracket for the Focusrite Scarlett Solo audio interface",
|
||||||
|
"description": "This is a bracket that holds an audio device underneath a desk or shelf. The audio device has dimensions of 144mm wide, 80mm length and 45mm depth with fillets of 6mm. This mounting bracket is designed to be 3D printed with PLA material"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "food-service-spatula/main.kcl",
|
||||||
|
"multipleFiles": false,
|
||||||
|
"title": "Food Service Spatula",
|
||||||
|
"description": "Use these spatulas for mixing, flipping, and scraping."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "french-press/main.kcl",
|
||||||
|
"multipleFiles": false,
|
||||||
|
"title": "French Press",
|
||||||
|
"description": "A french press immersion coffee maker"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "gear/main.kcl",
|
||||||
|
"multipleFiles": false,
|
||||||
|
"title": "Spur Gear",
|
||||||
|
"description": "A rotating machine part having cut teeth or, in the case of a cogwheel, inserted teeth (called cogs), which mesh with another toothed part to transmit torque. Geared devices can change the speed, torque, and direction of a power source. The two elements that define a gear are its circular shape and the teeth that are integrated into its outer edge, which are designed to fit into the teeth of another gear."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "gear-rack/main.kcl",
|
||||||
|
"multipleFiles": false,
|
||||||
|
"title": "100mm Gear Rack",
|
||||||
|
"description": "A flat bar or rail that is engraved with teeth along its length. These teeth are designed to mesh with the teeth of a gear, known as a pinion. When the pinion, a small cylindrical gear, rotates, its teeth engage with the teeth on the rack, causing the rack to move linearly. Conversely, linear motion applied to the rack will cause the pinion to rotate."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "hex-nut/main.kcl",
|
||||||
|
"multipleFiles": false,
|
||||||
|
"title": "Hex nut",
|
||||||
|
"description": "A hex nut is a type of fastener with a threaded hole and a hexagonal outer shape, used in a wide variety of applications to secure parts together. The hexagonal shape allows for a greater torque to be applied with wrenches or tools, making it one of the most common nut types in hardware."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "i-beam/main.kcl",
|
||||||
|
"multipleFiles": false,
|
||||||
|
"title": "I-beam",
|
||||||
|
"description": "A structural metal beam with an I shaped cross section. Often used in construction"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "kitt/main.kcl",
|
||||||
|
"multipleFiles": false,
|
||||||
|
"title": "Kitt",
|
||||||
|
"description": "The beloved KittyCAD mascot in a voxelized style."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "lego/main.kcl",
|
||||||
|
"multipleFiles": false,
|
||||||
|
"title": "Lego Brick",
|
||||||
|
"description": "A standard Lego brick. This is a small, plastic construction block toy that can be interlocked with other blocks to build various structures, models, and figures. There are a lot of hacks used in this code."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "mounting-plate/main.kcl",
|
||||||
|
"multipleFiles": false,
|
||||||
|
"title": "Mounting Plate",
|
||||||
|
"description": "A flat piece of material, often metal or plastic, that serves as a support or base for attaching, securing, or mounting various types of equipment, devices, or components."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "multi-axis-robot/main.kcl",
|
||||||
|
"multipleFiles": true,
|
||||||
|
"title": "Robot Arm",
|
||||||
|
"description": "A 4 axis robotic arm for industrial use. These machines can be used for assembly, packaging, organization of goods, and quality inspection processes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "pipe/main.kcl",
|
||||||
|
"multipleFiles": false,
|
||||||
|
"title": "Pipe",
|
||||||
|
"description": "A tubular section or hollow cylinder, usually but not necessarily of circular cross-section, used mainly to convey substances that can flow."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "pipe-flange-assembly/main.kcl",
|
||||||
|
"multipleFiles": false,
|
||||||
|
"title": "Pipe and Flange Assembly",
|
||||||
|
"description": "A crucial component in various piping systems, designed to facilitate the connection, disconnection, and access to piping for inspection, cleaning, and modifications. This assembly combines pipes (long cylindrical conduits) with flanges (plate-like fittings) to create a secure yet detachable joint."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "pipe-with-bend/main.kcl",
|
||||||
|
"multipleFiles": false,
|
||||||
|
"title": "Pipe with bend",
|
||||||
|
"description": "A tubular section or hollow cylinder, usually but not necessarily of circular cross-section, used mainly to convey substances that can flow."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "poopy-shoe/main.kcl",
|
||||||
|
"multipleFiles": false,
|
||||||
|
"title": "Poopy Shoe",
|
||||||
|
"description": "poop shute for bambu labs printer - optimized for printing."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "router-template-cross-bar/main.kcl",
|
||||||
|
"multipleFiles": false,
|
||||||
|
"title": "Router template for a cross bar",
|
||||||
|
"description": "A guide for routing a notch into a cross bar."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "router-template-slate/main.kcl",
|
||||||
|
"multipleFiles": false,
|
||||||
|
"title": "Router template for a slate",
|
||||||
|
"description": "A guide for routing a slate for a cross bar."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "sheet-metal-bracket/main.kcl",
|
||||||
|
"multipleFiles": false,
|
||||||
|
"title": "Sheet Metal Bracket",
|
||||||
|
"description": "A component typically made from flat sheet metal through various manufacturing processes such as bending, punching, cutting, and forming. These brackets are used to support, attach, or mount other hardware components, often providing a structural or functional base for assembly."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "socket-head-cap-screw/main.kcl",
|
||||||
|
"multipleFiles": false,
|
||||||
|
"title": "Socket Head Cap Screw",
|
||||||
|
"description": "This is for a #10-24 screw that is 1.00 inches long. A socket head cap screw is a type of fastener that is widely used in a variety of applications requiring a high strength fastening solution. It is characterized by its cylindrical head and internal hexagonal drive, which allows for tightening with an Allen wrench or hex key."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "walkie-talkie/main.kcl",
|
||||||
|
"multipleFiles": true,
|
||||||
|
"title": "Walkie Talkie",
|
||||||
|
"description": "A portable, handheld two-way radio device that allows users to communicate wirelessly over short to medium distances. It operates on specific radio frequencies and features a push-to-talk button for transmitting messages, making it ideal for quick and reliable communication in outdoor, work, or emergency settings."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "washer/main.kcl",
|
||||||
|
"multipleFiles": false,
|
||||||
|
"title": "Washer",
|
||||||
|
"description": "A small, typically disk-shaped component with a hole in the middle, used in a wide range of applications, primarily in conjunction with fasteners like bolts and screws. Washers distribute the load of a fastener across a broader area. This is especially important when the fastening surface is soft or uneven, as it helps to prevent damage to the surface and ensures the load is evenly distributed, reducing the risk of the fastener becoming loose over time."
|
||||||
|
}
|
||||||
|
]
|
@ -11,6 +11,7 @@ echo "$PACKAGE" > package.json
|
|||||||
# electron-builder.yml
|
# electron-builder.yml
|
||||||
yq -i '.publish[0].url = "https://dl.zoo.dev/releases/modeling-app/nightly"' electron-builder.yml
|
yq -i '.publish[0].url = "https://dl.zoo.dev/releases/modeling-app/nightly"' electron-builder.yml
|
||||||
yq -i '.appId = "dev.zoo.modeling-app-nightly"' electron-builder.yml
|
yq -i '.appId = "dev.zoo.modeling-app-nightly"' electron-builder.yml
|
||||||
|
yq -i '.nsis.include = "./scripts/installer-nightly.nsh"' electron-builder.yml
|
||||||
|
|
||||||
# Release notes
|
# Release notes
|
||||||
echo "Nightly build $VERSION (commit $COMMIT)" > release-notes.md
|
echo "Nightly build $VERSION (commit $COMMIT)" > release-notes.md
|
||||||
|
8
scripts/installer-nightly.nsh
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
!macro preInit
|
||||||
|
SetRegView 64
|
||||||
|
WriteRegExpandStr HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation "C:\Program Files\Zoo Modeling App (Nightly)"
|
||||||
|
WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "C:\Program Files\Zoo Modeling App (Nightly)"
|
||||||
|
SetRegView 32
|
||||||
|
WriteRegExpandStr HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation "C:\Program Files\Zoo Modeling App (Nightly)"
|
||||||
|
WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "C:\Program Files\Zoo Modeling App (Nightly)"
|
||||||
|
!macroend
|
@ -196,6 +196,7 @@ function ReviewingButton() {
|
|||||||
type="submit"
|
type="submit"
|
||||||
form="review-form"
|
form="review-form"
|
||||||
className="w-fit !p-0 rounded-sm hover:shadow"
|
className="w-fit !p-0 rounded-sm hover:shadow"
|
||||||
|
data-testid="command-bar-submit"
|
||||||
iconStart={{
|
iconStart={{
|
||||||
icon: 'checkmark',
|
icon: 'checkmark',
|
||||||
bgClassName: 'p-1 rounded-sm !bg-primary hover:brightness-110',
|
bgClassName: 'p-1 rounded-sm !bg-primary hover:brightness-110',
|
||||||
@ -214,6 +215,7 @@ function GatheringArgsButton() {
|
|||||||
type="submit"
|
type="submit"
|
||||||
form="arg-form"
|
form="arg-form"
|
||||||
className="w-fit !p-0 rounded-sm hover:shadow"
|
className="w-fit !p-0 rounded-sm hover:shadow"
|
||||||
|
data-testid="command-bar-continue"
|
||||||
iconStart={{
|
iconStart={{
|
||||||
icon: 'arrowRight',
|
icon: 'arrowRight',
|
||||||
bgClassName: 'p-1 rounded-sm !bg-primary hover:brightness-110',
|
bgClassName: 'p-1 rounded-sm !bg-primary hover:brightness-110',
|
||||||
|
@ -20,6 +20,7 @@ import { createIdentifier, createVariableDeclaration } from 'lang/modifyAst'
|
|||||||
import { useCodeMirror } from 'components/ModelingSidebar/ModelingPanes/CodeEditor'
|
import { useCodeMirror } from 'components/ModelingSidebar/ModelingPanes/CodeEditor'
|
||||||
import { useSelector } from '@xstate/react'
|
import { useSelector } from '@xstate/react'
|
||||||
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
|
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
|
||||||
|
import toast from 'react-hot-toast'
|
||||||
|
|
||||||
const machineContextSelector = (snapshot?: {
|
const machineContextSelector = (snapshot?: {
|
||||||
context: Record<string, unknown>
|
context: Record<string, unknown>
|
||||||
@ -97,6 +98,7 @@ function CommandBarKclInput({
|
|||||||
value,
|
value,
|
||||||
initialVariableName,
|
initialVariableName,
|
||||||
})
|
})
|
||||||
|
|
||||||
const varMentionData: Completion[] = prevVariables.map((v) => ({
|
const varMentionData: Completion[] = prevVariables.map((v) => ({
|
||||||
label: v.key,
|
label: v.key,
|
||||||
detail: String(roundOff(v.value as number)),
|
detail: String(roundOff(v.value as number)),
|
||||||
@ -170,7 +172,15 @@ function CommandBarKclInput({
|
|||||||
|
|
||||||
function handleSubmit(e?: React.FormEvent<HTMLFormElement>) {
|
function handleSubmit(e?: React.FormEvent<HTMLFormElement>) {
|
||||||
e?.preventDefault()
|
e?.preventDefault()
|
||||||
if (!canSubmit || valueNode === null) return
|
if (!canSubmit || valueNode === null) {
|
||||||
|
// Gotcha: Our application can attempt to submit a command value before the command bar kcl input is ready. Notify the scene and user.
|
||||||
|
if (!canSubmit) {
|
||||||
|
toast.error('Unable to submit command')
|
||||||
|
} else if (valueNode === null) {
|
||||||
|
toast.error('Unable to submit undefined command value')
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
onSubmit(
|
onSubmit(
|
||||||
createNewVariable
|
createNewVariable
|
||||||
|
@ -57,7 +57,7 @@ function CommandComboBox({
|
|||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
if (
|
if (
|
||||||
(event.metaKey && event.key === 'k') ||
|
(event.metaKey && event.key === 'k') ||
|
||||||
(event.key === 'Backspace' && !event.currentTarget.value)
|
event.key === 'Escape'
|
||||||
) {
|
) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
commandBarActor.send({ type: 'Close' })
|
commandBarActor.send({ type: 'Close' })
|
||||||
|
@ -2,11 +2,15 @@ import { Dialog } from '@headlessui/react'
|
|||||||
import { ActionButton } from './ActionButton'
|
import { ActionButton } from './ActionButton'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
import { useSearchParams } from 'react-router-dom'
|
||||||
|
import { CREATE_FILE_URL_PARAM } from 'lib/constants'
|
||||||
|
|
||||||
const DownloadAppBanner = () => {
|
const DownloadAppBanner = () => {
|
||||||
|
const [searchParams] = useSearchParams()
|
||||||
|
const hasCreateFileParam = searchParams.has(CREATE_FILE_URL_PARAM)
|
||||||
const { settings } = useSettingsAuthContext()
|
const { settings } = useSettingsAuthContext()
|
||||||
const [isBannerDismissed, setIsBannerDismissed] = useState(
|
const [isBannerDismissed, setIsBannerDismissed] = useState(
|
||||||
settings.context.app.dismissWebBanner.current
|
settings.context.app.dismissWebBanner.current || hasCreateFileParam
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -329,11 +329,83 @@ export const ModelingMachineProvider = ({
|
|||||||
otherSelections: [],
|
otherSelections: [],
|
||||||
}
|
}
|
||||||
} else if (setSelections.selection && editorManager.isShiftDown) {
|
} else if (setSelections.selection && editorManager.isShiftDown) {
|
||||||
|
// selecting and deselecting multiple objects
|
||||||
|
|
||||||
|
/**
|
||||||
|
* There are two scenarios:
|
||||||
|
* 1. General case:
|
||||||
|
* When selecting and deselecting edges,
|
||||||
|
* faces or segment (during sketch edit)
|
||||||
|
* we use its artifact ID to identify the selection
|
||||||
|
* 2. Initial sketch setup:
|
||||||
|
* The artifact is not yet created
|
||||||
|
* so we use the codeRef.range
|
||||||
|
*/
|
||||||
|
|
||||||
|
let updatedSelections: typeof selectionRanges.graphSelections
|
||||||
|
|
||||||
|
// 1. General case: Artifact exists, use its ID
|
||||||
|
if (setSelections.selection.artifact?.id) {
|
||||||
|
// check if already selected
|
||||||
|
const alreadySelected = selectionRanges.graphSelections.some(
|
||||||
|
(selection) =>
|
||||||
|
selection.artifact?.id ===
|
||||||
|
setSelections.selection?.artifact?.id
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
alreadySelected &&
|
||||||
|
setSelections.selection?.artifact?.id
|
||||||
|
) {
|
||||||
|
// remove it
|
||||||
|
updatedSelections = selectionRanges.graphSelections.filter(
|
||||||
|
(selection) =>
|
||||||
|
selection.artifact?.id !==
|
||||||
|
setSelections.selection?.artifact?.id
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// add it
|
||||||
|
updatedSelections = [
|
||||||
|
...selectionRanges.graphSelections,
|
||||||
|
setSelections.selection,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 2. Initial sketch setup: Artifact not yet created – use codeRef.range
|
||||||
|
const selectionRange = JSON.stringify(
|
||||||
|
setSelections.selection?.codeRef?.range
|
||||||
|
)
|
||||||
|
|
||||||
|
// check if already selected
|
||||||
|
const alreadySelected = selectionRanges.graphSelections.some(
|
||||||
|
(selection) => {
|
||||||
|
const existingRange = JSON.stringify(
|
||||||
|
selection.codeRef?.range
|
||||||
|
)
|
||||||
|
return existingRange === selectionRange
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
alreadySelected &&
|
||||||
|
setSelections.selection?.codeRef?.range
|
||||||
|
) {
|
||||||
|
// remove it
|
||||||
|
updatedSelections = selectionRanges.graphSelections.filter(
|
||||||
|
(selection) =>
|
||||||
|
JSON.stringify(selection.codeRef?.range) !==
|
||||||
|
selectionRange
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// add it
|
||||||
|
updatedSelections = [
|
||||||
|
...selectionRanges.graphSelections,
|
||||||
|
setSelections.selection,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
selections = {
|
selections = {
|
||||||
graphSelections: [
|
graphSelections: updatedSelections,
|
||||||
...selectionRanges.graphSelections,
|
|
||||||
setSelections.selection,
|
|
||||||
],
|
|
||||||
otherSelections: selectionRanges.otherSelections,
|
otherSelections: selectionRanges.otherSelections,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -168,7 +168,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
|||||||
height: 'auto',
|
height: 'auto',
|
||||||
}}
|
}}
|
||||||
minWidth={200}
|
minWidth={200}
|
||||||
maxWidth={800}
|
maxWidth={window.innerWidth - 10}
|
||||||
handleWrapperClass="sidebar-resize-handles"
|
handleWrapperClass="sidebar-resize-handles"
|
||||||
handleClasses={{
|
handleClasses={{
|
||||||
right:
|
right:
|
||||||
|
@ -104,7 +104,7 @@ function ProjectMenuPopover({
|
|||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const filePath = useAbsoluteFilePath()
|
const filePath = useAbsoluteFilePath()
|
||||||
const { settings } = useSettingsAuthContext()
|
useSettingsAuthContext()
|
||||||
const token = useToken()
|
const token = useToken()
|
||||||
const machineManager = useContext(MachineManagerContext)
|
const machineManager = useContext(MachineManagerContext)
|
||||||
const commands = useSelector(commandBarActor, commandsSelector)
|
const commands = useSelector(commandBarActor, commandsSelector)
|
||||||
@ -193,14 +193,13 @@ function ProjectMenuPopover({
|
|||||||
{
|
{
|
||||||
id: 'share-link',
|
id: 'share-link',
|
||||||
Element: 'button',
|
Element: 'button',
|
||||||
children: 'Share link to file',
|
children: 'Share current part (via Zoo link)',
|
||||||
disabled: IS_NIGHTLY_OR_DEBUG || !findCommand(shareCommandInfo),
|
disabled: !(IS_NIGHTLY_OR_DEBUG && findCommand(shareCommandInfo)),
|
||||||
onClick: async () => {
|
onClick: async () => {
|
||||||
await copyFileShareLink({
|
await copyFileShareLink({
|
||||||
token: token ?? '',
|
token: token ?? '',
|
||||||
code: codeManager.code,
|
code: codeManager.code,
|
||||||
name: project?.name || '',
|
name: project?.name || '',
|
||||||
units: settings.context.modeling.defaultUnit.current,
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -263,7 +262,7 @@ function ProjectMenuPopover({
|
|||||||
as={Fragment}
|
as={Fragment}
|
||||||
>
|
>
|
||||||
<Popover.Panel
|
<Popover.Panel
|
||||||
className={`z-10 absolute top-full left-0 mt-1 pb-1 w-48 bg-chalkboard-10 dark:bg-chalkboard-90
|
className={`z-10 absolute top-full left-0 mt-1 pb-1 w-52 bg-chalkboard-10 dark:bg-chalkboard-90
|
||||||
border border-solid border-chalkboard-20 dark:border-chalkboard-90 rounded
|
border border-solid border-chalkboard-20 dark:border-chalkboard-90 rounded
|
||||||
shadow-lg`}
|
shadow-lg`}
|
||||||
>
|
>
|
||||||
|
@ -30,15 +30,7 @@ import {
|
|||||||
FILE_EXT,
|
FILE_EXT,
|
||||||
PROJECT_ENTRYPOINT,
|
PROJECT_ENTRYPOINT,
|
||||||
} from 'lib/constants'
|
} from 'lib/constants'
|
||||||
import { DeepPartial } from 'lib/types'
|
import { codeManager, kclManager } from 'lib/singletons'
|
||||||
import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
|
|
||||||
import { codeManager } from 'lib/singletons'
|
|
||||||
import {
|
|
||||||
loadAndValidateSettings,
|
|
||||||
projectConfigurationToSettingsPayload,
|
|
||||||
saveSettings,
|
|
||||||
setSettingsAtLevel,
|
|
||||||
} from 'lib/settings/settingsUtils'
|
|
||||||
import { Project } from 'lib/project'
|
import { Project } from 'lib/project'
|
||||||
|
|
||||||
type MachineContext<T extends AnyStateMachine> = {
|
type MachineContext<T extends AnyStateMachine> = {
|
||||||
@ -86,7 +78,7 @@ const ProjectsContextWeb = ({ children }: { children: React.ReactNode }) => {
|
|||||||
setSearchParams(searchParams)
|
setSearchParams(searchParams)
|
||||||
}, [searchParams, setSearchParams])
|
}, [searchParams, setSearchParams])
|
||||||
const {
|
const {
|
||||||
settings: { context: settings, send: settingsSend },
|
settings: { context: settings },
|
||||||
} = useSettingsAuthContext()
|
} = useSettingsAuthContext()
|
||||||
|
|
||||||
const [state, send, actor] = useMachine(
|
const [state, send, actor] = useMachine(
|
||||||
@ -132,17 +124,10 @@ const ProjectsContextWeb = ({ children }: { children: React.ReactNode }) => {
|
|||||||
clearImportSearchParams()
|
clearImportSearchParams()
|
||||||
codeManager.updateCodeStateEditor(input.code || '')
|
codeManager.updateCodeStateEditor(input.code || '')
|
||||||
await codeManager.writeToFile()
|
await codeManager.writeToFile()
|
||||||
|
await kclManager.executeCode(true)
|
||||||
settingsSend({
|
|
||||||
type: 'set.modeling.defaultUnit',
|
|
||||||
data: {
|
|
||||||
level: 'project',
|
|
||||||
value: input.units,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
message: 'File and units overwritten successfully',
|
message: 'File overwritten successfully',
|
||||||
fileName: input.name,
|
fileName: input.name,
|
||||||
projectName: '',
|
projectName: '',
|
||||||
}
|
}
|
||||||
@ -392,16 +377,6 @@ const ProjectsContextDesktop = ({
|
|||||||
? input.name
|
? input.name
|
||||||
: input.name + FILE_EXT
|
: input.name + FILE_EXT
|
||||||
let message = 'File created successfully'
|
let message = 'File created successfully'
|
||||||
const unitsConfiguration: DeepPartial<Configuration> = {
|
|
||||||
settings: {
|
|
||||||
project: {
|
|
||||||
directory: settings.app.projectDirectory.current,
|
|
||||||
},
|
|
||||||
modeling: {
|
|
||||||
base_unit: input.units,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const needsInterpolated = doesProjectNameNeedInterpolated(projectName)
|
const needsInterpolated = doesProjectNameNeedInterpolated(projectName)
|
||||||
if (needsInterpolated) {
|
if (needsInterpolated) {
|
||||||
@ -414,28 +389,10 @@ const ProjectsContextDesktop = ({
|
|||||||
|
|
||||||
// Create the project around the file if newProject
|
// Create the project around the file if newProject
|
||||||
if (input.method === 'newProject') {
|
if (input.method === 'newProject') {
|
||||||
await createNewProjectDirectory(
|
await createNewProjectDirectory(projectName, input.code)
|
||||||
projectName,
|
|
||||||
input.code,
|
|
||||||
unitsConfiguration
|
|
||||||
)
|
|
||||||
message = `Project "${projectName}" created successfully with link contents`
|
message = `Project "${projectName}" created successfully with link contents`
|
||||||
} else {
|
} else {
|
||||||
let projectPath = window.electron.join(
|
|
||||||
settings.app.projectDirectory.current,
|
|
||||||
projectName
|
|
||||||
)
|
|
||||||
|
|
||||||
message = `File "${fileName}" created successfully`
|
message = `File "${fileName}" created successfully`
|
||||||
const existingConfiguration = await loadAndValidateSettings(
|
|
||||||
projectPath
|
|
||||||
)
|
|
||||||
const settingsToSave = setSettingsAtLevel(
|
|
||||||
existingConfiguration.settings,
|
|
||||||
'project',
|
|
||||||
projectConfigurationToSettingsPayload(unitsConfiguration)
|
|
||||||
)
|
|
||||||
await saveSettings(settingsToSave, projectPath)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the file
|
// Create the file
|
||||||
|
@ -302,7 +302,7 @@ export const Stream = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
const path = getArtifactOfTypes(
|
const path = getArtifactOfTypes(
|
||||||
{ key: entity_id, types: ['path', 'solid2d', 'segment'] },
|
{ key: entity_id, types: ['path', 'solid2d', 'segment', 'helix'] },
|
||||||
engineCommandManager.artifactGraph
|
engineCommandManager.artifactGraph
|
||||||
)
|
)
|
||||||
if (err(path)) {
|
if (err(path)) {
|
||||||
|
@ -1,9 +1,31 @@
|
|||||||
import { Popover } from '@headlessui/react'
|
import { Popover } from '@headlessui/react'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
|
import { changeKclSettings, unitLengthToUnitLen } from 'lang/wasm'
|
||||||
import { baseUnitLabels, baseUnitsUnion } from 'lib/settings/settingsTypes'
|
import { baseUnitLabels, baseUnitsUnion } from 'lib/settings/settingsTypes'
|
||||||
|
import { codeManager, kclManager } from 'lib/singletons'
|
||||||
|
import { err, reportRejection } from 'lib/trap'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import toast from 'react-hot-toast'
|
||||||
|
|
||||||
export function UnitsMenu() {
|
export function UnitsMenu() {
|
||||||
const { settings } = useSettingsAuthContext()
|
const { settings } = useSettingsAuthContext()
|
||||||
|
const [hasPerFileLengthUnit, setHasPerFileLengthUnit] = useState(
|
||||||
|
Boolean(kclManager.fileSettings.defaultLengthUnit)
|
||||||
|
)
|
||||||
|
const [lengthSetting, setLengthSetting] = useState(
|
||||||
|
kclManager.fileSettings.defaultLengthUnit ||
|
||||||
|
settings.context.modeling.defaultUnit.current
|
||||||
|
)
|
||||||
|
useEffect(() => {
|
||||||
|
setHasPerFileLengthUnit(Boolean(kclManager.fileSettings.defaultLengthUnit))
|
||||||
|
setLengthSetting(
|
||||||
|
kclManager.fileSettings.defaultLengthUnit ||
|
||||||
|
settings.context.modeling.defaultUnit.current
|
||||||
|
)
|
||||||
|
}, [
|
||||||
|
kclManager.fileSettings.defaultLengthUnit,
|
||||||
|
settings.context.modeling.defaultUnit.current,
|
||||||
|
])
|
||||||
return (
|
return (
|
||||||
<Popover className="relative pointer-events-auto">
|
<Popover className="relative pointer-events-auto">
|
||||||
{({ close }) => (
|
{({ close }) => (
|
||||||
@ -18,7 +40,7 @@ export function UnitsMenu() {
|
|||||||
<div className="absolute w-[1px] h-[1em] bg-primary right-0 top-1/2 -translate-y-1/2"></div>
|
<div className="absolute w-[1px] h-[1em] bg-primary right-0 top-1/2 -translate-y-1/2"></div>
|
||||||
</div>
|
</div>
|
||||||
<span className="sr-only">Current units are: </span>
|
<span className="sr-only">Current units are: </span>
|
||||||
{settings.context.modeling.defaultUnit.current}
|
{lengthSetting}
|
||||||
</Popover.Button>
|
</Popover.Button>
|
||||||
<Popover.Panel
|
<Popover.Panel
|
||||||
className={`absolute bottom-full right-0 mb-2 w-48 bg-chalkboard-10 dark:bg-chalkboard-90
|
className={`absolute bottom-full right-0 mb-2 w-48 bg-chalkboard-10 dark:bg-chalkboard-90
|
||||||
@ -31,18 +53,40 @@ export function UnitsMenu() {
|
|||||||
<button
|
<button
|
||||||
className="flex items-center gap-2 m-0 py-1.5 px-2 cursor-pointer hover:bg-chalkboard-20 dark:hover:bg-chalkboard-80 border-none text-left"
|
className="flex items-center gap-2 m-0 py-1.5 px-2 cursor-pointer hover:bg-chalkboard-20 dark:hover:bg-chalkboard-80 border-none text-left"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
settings.send({
|
if (hasPerFileLengthUnit) {
|
||||||
type: 'set.modeling.defaultUnit',
|
const newCode = changeKclSettings(codeManager.code, {
|
||||||
data: {
|
defaultLengthUnits: unitLengthToUnitLen(unit),
|
||||||
level: 'project',
|
defaultAngleUnits: { type: 'Degrees' },
|
||||||
value: unit,
|
})
|
||||||
},
|
if (err(newCode)) {
|
||||||
})
|
toast.error(
|
||||||
|
`Failed to set per-file units: ${newCode.message}`
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
codeManager.updateCodeStateEditor(newCode)
|
||||||
|
Promise.all([
|
||||||
|
codeManager.writeToFile(),
|
||||||
|
kclManager.executeCode(),
|
||||||
|
])
|
||||||
|
.then(() => {
|
||||||
|
toast.success(`Updated per-file units to ${unit}`)
|
||||||
|
})
|
||||||
|
.catch(reportRejection)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
settings.send({
|
||||||
|
type: 'set.modeling.defaultUnit',
|
||||||
|
data: {
|
||||||
|
level: 'project',
|
||||||
|
value: unit,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
close()
|
close()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="flex-1">{baseUnitLabels[unit]}</span>
|
<span className="flex-1">{baseUnitLabels[unit]}</span>
|
||||||
{unit === settings.context.modeling.defaultUnit.current && (
|
{unit === lengthSetting && (
|
||||||
<span className="text-chalkboard-60">current</span>
|
<span className="text-chalkboard-60">current</span>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
@ -25,6 +25,18 @@ export class KclPlugin implements PluginValue {
|
|||||||
|
|
||||||
constructor(client: LanguageServerClient) {
|
constructor(client: LanguageServerClient) {
|
||||||
this.client = client
|
this.client = client
|
||||||
|
|
||||||
|
// Gotcha: Code can be written into the CodeMirror editor but not propagated to codeManager.code
|
||||||
|
// because the update function has not run. We need to initialize the codeManager.code when lsp initializes
|
||||||
|
// because new code could have been written into the editor before the update callback is initialized.
|
||||||
|
// There appears to be limited ways to safely get the current doc content. This appears to be sync and safe.
|
||||||
|
const kclLspPlugin = this.client.plugins.find((plugin) => {
|
||||||
|
return plugin.client.name === 'kcl'
|
||||||
|
})
|
||||||
|
if (kclLspPlugin) {
|
||||||
|
// @ts-ignore Ignoring this private dereference of .view on the plugin. I do not have another helper method that can give me doc string
|
||||||
|
codeManager.code = kclLspPlugin.view.state.doc.toString()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// When a doc update needs to be sent to the server, this holds the
|
// When a doc update needs to be sent to the server, this holds the
|
||||||
|
@ -6,7 +6,6 @@ import { useSettingsAuthContext } from './useSettingsAuthContext'
|
|||||||
import { isDesktop } from 'lib/isDesktop'
|
import { isDesktop } from 'lib/isDesktop'
|
||||||
import { FileLinkParams } from 'lib/links'
|
import { FileLinkParams } from 'lib/links'
|
||||||
import { ProjectsCommandSchema } from 'lib/commandBarConfigs/projectsCommandConfig'
|
import { ProjectsCommandSchema } from 'lib/commandBarConfigs/projectsCommandConfig'
|
||||||
import { baseUnitsUnion } from 'lib/settings/settingsTypes'
|
|
||||||
|
|
||||||
// For initializing the command arguments, we actually want `method` to be undefined
|
// For initializing the command arguments, we actually want `method` to be undefined
|
||||||
// so that we don't skip it in the command palette.
|
// so that we don't skip it in the command palette.
|
||||||
@ -37,13 +36,7 @@ export function useCreateFileLinkQuery(
|
|||||||
code: base64ToString(
|
code: base64ToString(
|
||||||
decodeURIComponent(searchParams.get('code') ?? '')
|
decodeURIComponent(searchParams.get('code') ?? '')
|
||||||
),
|
),
|
||||||
|
|
||||||
name: searchParams.get('name') ?? DEFAULT_FILE_NAME,
|
name: searchParams.get('name') ?? DEFAULT_FILE_NAME,
|
||||||
|
|
||||||
units:
|
|
||||||
(baseUnitsUnion.find((unit) => searchParams.get('units') === unit) ||
|
|
||||||
settings.context.modeling.defaultUnit.default) ??
|
|
||||||
settings.context.modeling.defaultUnit.current,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const argDefaultValues: CreateFileSchemaMethodOptional = {
|
const argDefaultValues: CreateFileSchemaMethodOptional = {
|
||||||
@ -55,7 +48,6 @@ export function useCreateFileLinkQuery(
|
|||||||
? settings.context.projects.defaultProjectName.current
|
? settings.context.projects.defaultProjectName.current
|
||||||
: DEFAULT_FILE_NAME,
|
: DEFAULT_FILE_NAME,
|
||||||
code: params.code || '',
|
code: params.code || '',
|
||||||
units: params.units,
|
|
||||||
method: isDesktop() ? undefined : 'existingProject',
|
method: isDesktop() ? undefined : 'existingProject',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ import {
|
|||||||
SourceRange,
|
SourceRange,
|
||||||
topLevelRange,
|
topLevelRange,
|
||||||
} from 'lang/wasm'
|
} from 'lang/wasm'
|
||||||
import { getNodeFromPath } from './queryAst'
|
import { getNodeFromPath, getSettingsAnnotation } from './queryAst'
|
||||||
import { codeManager, editorManager, sceneInfra } from 'lib/singletons'
|
import { codeManager, editorManager, sceneInfra } from 'lib/singletons'
|
||||||
import { Diagnostic } from '@codemirror/lint'
|
import { Diagnostic } from '@codemirror/lint'
|
||||||
import { markOnce } from 'lib/performance'
|
import { markOnce } from 'lib/performance'
|
||||||
@ -35,6 +35,7 @@ import {
|
|||||||
ModelingCmdReq_type,
|
ModelingCmdReq_type,
|
||||||
} from '@kittycad/lib/dist/types/src/models'
|
} from '@kittycad/lib/dist/types/src/models'
|
||||||
import { Operation } from 'wasm-lib/kcl/bindings/Operation'
|
import { Operation } from 'wasm-lib/kcl/bindings/Operation'
|
||||||
|
import { KclSettingsAnnotation } from 'lib/settings/settingsTypes'
|
||||||
|
|
||||||
interface ExecuteArgs {
|
interface ExecuteArgs {
|
||||||
ast?: Node<Program>
|
ast?: Node<Program>
|
||||||
@ -57,6 +58,7 @@ export class KclManager {
|
|||||||
nonCodeNodes: {},
|
nonCodeNodes: {},
|
||||||
startNodes: [],
|
startNodes: [],
|
||||||
},
|
},
|
||||||
|
trivia: [],
|
||||||
}
|
}
|
||||||
private _execState: ExecState = emptyExecState()
|
private _execState: ExecState = emptyExecState()
|
||||||
private _programMemory: ProgramMemory = ProgramMemory.empty()
|
private _programMemory: ProgramMemory = ProgramMemory.empty()
|
||||||
@ -70,6 +72,7 @@ export class KclManager {
|
|||||||
private _wasmInitFailed = true
|
private _wasmInitFailed = true
|
||||||
private _hasErrors = false
|
private _hasErrors = false
|
||||||
private _switchedFiles = false
|
private _switchedFiles = false
|
||||||
|
private _fileSettings: KclSettingsAnnotation = {}
|
||||||
|
|
||||||
engineCommandManager: EngineCommandManager
|
engineCommandManager: EngineCommandManager
|
||||||
|
|
||||||
@ -237,6 +240,7 @@ export class KclManager {
|
|||||||
nonCodeNodes: {},
|
nonCodeNodes: {},
|
||||||
startNodes: [],
|
startNodes: [],
|
||||||
},
|
},
|
||||||
|
trivia: [],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -368,6 +372,13 @@ export class KclManager {
|
|||||||
await this.disableSketchMode()
|
await this.disableSketchMode()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let fileSettings = getSettingsAnnotation(ast)
|
||||||
|
if (err(fileSettings)) {
|
||||||
|
console.error(fileSettings)
|
||||||
|
fileSettings = {}
|
||||||
|
}
|
||||||
|
this.fileSettings = fileSettings
|
||||||
|
|
||||||
this.logs = logs
|
this.logs = logs
|
||||||
this.errors = errors
|
this.errors = errors
|
||||||
// Do not add the errors since the program was interrupted and the error is not a real KCL error
|
// Do not add the errors since the program was interrupted and the error is not a real KCL error
|
||||||
@ -413,14 +424,7 @@ export class KclManager {
|
|||||||
|
|
||||||
// NOTE: this always updates the code state and editor.
|
// NOTE: this always updates the code state and editor.
|
||||||
// DO NOT CALL THIS from codemirror ever.
|
// DO NOT CALL THIS from codemirror ever.
|
||||||
async executeAstMock(
|
async executeAstMock(ast: Program = this._ast) {
|
||||||
ast: Program = this._ast,
|
|
||||||
{
|
|
||||||
updates,
|
|
||||||
}: {
|
|
||||||
updates: 'none' | 'artifactRanges'
|
|
||||||
} = { updates: 'none' }
|
|
||||||
) {
|
|
||||||
await this.ensureWasmInit()
|
await this.ensureWasmInit()
|
||||||
|
|
||||||
const newCode = recast(ast)
|
const newCode = recast(ast)
|
||||||
@ -450,34 +454,6 @@ export class KclManager {
|
|||||||
this.lastSuccessfulProgramMemory = execState.memory
|
this.lastSuccessfulProgramMemory = execState.memory
|
||||||
this.lastSuccessfulOperations = execState.operations
|
this.lastSuccessfulOperations = execState.operations
|
||||||
}
|
}
|
||||||
if (updates !== 'artifactRanges') return
|
|
||||||
|
|
||||||
// TODO the below seems like a work around, I wish there's a comment explaining exactly what
|
|
||||||
// problem this solves, but either way we should strive to remove it.
|
|
||||||
Array.from(this.engineCommandManager.artifactGraph).forEach(
|
|
||||||
([commandId, artifact]) => {
|
|
||||||
if (!('codeRef' in artifact && artifact.codeRef)) return
|
|
||||||
const _node1 = getNodeFromPath<Node<CallExpression | CallExpressionKw>>(
|
|
||||||
this.ast,
|
|
||||||
artifact.codeRef.pathToNode,
|
|
||||||
['CallExpression', 'CallExpressionKw']
|
|
||||||
)
|
|
||||||
if (err(_node1)) return
|
|
||||||
const { node } = _node1
|
|
||||||
if (node.type !== 'CallExpression' && node.type !== 'CallExpressionKw')
|
|
||||||
return
|
|
||||||
const [oldStart, oldEnd] = artifact.codeRef.range
|
|
||||||
if (oldStart === 0 && oldEnd === 0) return
|
|
||||||
if (oldStart === node.start && oldEnd === node.end) return
|
|
||||||
this.engineCommandManager.artifactGraph.set(commandId, {
|
|
||||||
...artifact,
|
|
||||||
codeRef: {
|
|
||||||
...artifact.codeRef,
|
|
||||||
range: topLevelRange(node.start, node.end),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
cancelAllExecutions() {
|
cancelAllExecutions() {
|
||||||
this._cancelTokens.forEach((_, key) => {
|
this._cancelTokens.forEach((_, key) => {
|
||||||
@ -699,6 +675,14 @@ export class KclManager {
|
|||||||
_isAstEmpty(ast: Node<Program>) {
|
_isAstEmpty(ast: Node<Program>) {
|
||||||
return ast.start === 0 && ast.end === 0 && ast.body.length === 0
|
return ast.start === 0 && ast.end === 0 && ast.body.length === 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get fileSettings() {
|
||||||
|
return this._fileSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
set fileSettings(settings: KclSettingsAnnotation) {
|
||||||
|
this._fileSettings = settings
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultSelectionFilter: EntityType_type[] = [
|
const defaultSelectionFilter: EntityType_type[] = [
|
||||||
|
@ -55,6 +55,7 @@ const mySketch001 = startSketchOn('XY')
|
|||||||
],
|
],
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
artifactId: expect.any(String),
|
artifactId: expect.any(String),
|
||||||
|
originalId: expect.any(String),
|
||||||
units: {
|
units: {
|
||||||
type: 'Mm',
|
type: 'Mm',
|
||||||
},
|
},
|
||||||
@ -98,6 +99,7 @@ const mySketch001 = startSketchOn('XY')
|
|||||||
],
|
],
|
||||||
sketch: {
|
sketch: {
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
|
originalId: expect.any(String),
|
||||||
artifactId: expect.any(String),
|
artifactId: expect.any(String),
|
||||||
units: {
|
units: {
|
||||||
type: 'Mm',
|
type: 'Mm',
|
||||||
@ -203,6 +205,7 @@ const sk2 = startSketchOn('XY')
|
|||||||
],
|
],
|
||||||
sketch: {
|
sketch: {
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
|
originalId: expect.any(String),
|
||||||
artifactId: expect.any(String),
|
artifactId: expect.any(String),
|
||||||
__meta: expect.any(Array),
|
__meta: expect.any(Array),
|
||||||
on: expect.any(Object),
|
on: expect.any(Object),
|
||||||
@ -308,6 +311,7 @@ const sk2 = startSketchOn('XY')
|
|||||||
],
|
],
|
||||||
sketch: {
|
sketch: {
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
|
originalId: expect.any(String),
|
||||||
artifactId: expect.any(String),
|
artifactId: expect.any(String),
|
||||||
units: {
|
units: {
|
||||||
type: 'Mm',
|
type: 'Mm',
|
||||||
|
@ -221,6 +221,7 @@ const newVar = myVar + 1`
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
|
originalId: expect.any(String),
|
||||||
artifactId: expect.any(String),
|
artifactId: expect.any(String),
|
||||||
units: {
|
units: {
|
||||||
type: 'Mm',
|
type: 'Mm',
|
||||||
|
@ -28,7 +28,14 @@ try {
|
|||||||
console.log(e)
|
console.log(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
child_process.spawnSync('git', ['clone', URL_GIT_KCL_SAMPLES, DIR_KCL_SAMPLES])
|
child_process.spawnSync('git', [
|
||||||
|
'clone',
|
||||||
|
'--single-branch',
|
||||||
|
'--branch',
|
||||||
|
'achalmers/kw-appearance',
|
||||||
|
URL_GIT_KCL_SAMPLES,
|
||||||
|
DIR_KCL_SAMPLES,
|
||||||
|
])
|
||||||
|
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
let files = await fs.readdir(DIR_KCL_SAMPLES)
|
let files = await fs.readdir(DIR_KCL_SAMPLES)
|
||||||
|
@ -128,15 +128,78 @@ describe('Testing findUniqueName', () => {
|
|||||||
it('should find a unique name', () => {
|
it('should find a unique name', () => {
|
||||||
const result = findUniqueName(
|
const result = findUniqueName(
|
||||||
JSON.stringify([
|
JSON.stringify([
|
||||||
{ type: 'Identifier', name: 'yo01', start: 0, end: 0, moduleId: 0 },
|
{
|
||||||
{ type: 'Identifier', name: 'yo02', start: 0, end: 0, moduleId: 0 },
|
type: 'Identifier',
|
||||||
{ type: 'Identifier', name: 'yo03', start: 0, end: 0, moduleId: 0 },
|
name: 'yo01',
|
||||||
{ type: 'Identifier', name: 'yo04', start: 0, end: 0, moduleId: 0 },
|
start: 0,
|
||||||
{ type: 'Identifier', name: 'yo05', start: 0, end: 0, moduleId: 0 },
|
end: 0,
|
||||||
{ type: 'Identifier', name: 'yo06', start: 0, end: 0, moduleId: 0 },
|
moduleId: 0,
|
||||||
{ type: 'Identifier', name: 'yo07', start: 0, end: 0, moduleId: 0 },
|
trivia: [],
|
||||||
{ type: 'Identifier', name: 'yo08', start: 0, end: 0, moduleId: 0 },
|
},
|
||||||
{ type: 'Identifier', name: 'yo09', start: 0, end: 0, moduleId: 0 },
|
{
|
||||||
|
type: 'Identifier',
|
||||||
|
name: 'yo02',
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
moduleId: 0,
|
||||||
|
trivia: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'Identifier',
|
||||||
|
name: 'yo03',
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
moduleId: 0,
|
||||||
|
trivia: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'Identifier',
|
||||||
|
name: 'yo04',
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
moduleId: 0,
|
||||||
|
trivia: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'Identifier',
|
||||||
|
name: 'yo05',
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
moduleId: 0,
|
||||||
|
trivia: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'Identifier',
|
||||||
|
name: 'yo06',
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
moduleId: 0,
|
||||||
|
trivia: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'Identifier',
|
||||||
|
name: 'yo07',
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
moduleId: 0,
|
||||||
|
trivia: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'Identifier',
|
||||||
|
name: 'yo08',
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
moduleId: 0,
|
||||||
|
trivia: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'Identifier',
|
||||||
|
name: 'yo09',
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
moduleId: 0,
|
||||||
|
trivia: [],
|
||||||
|
},
|
||||||
] satisfies Node<Identifier>[]),
|
] satisfies Node<Identifier>[]),
|
||||||
'yo',
|
'yo',
|
||||||
2
|
2
|
||||||
@ -154,6 +217,7 @@ describe('Testing addSketchTo', () => {
|
|||||||
end: 0,
|
end: 0,
|
||||||
moduleId: 0,
|
moduleId: 0,
|
||||||
nonCodeMeta: { nonCodeNodes: {}, startNodes: [] },
|
nonCodeMeta: { nonCodeNodes: {}, startNodes: [] },
|
||||||
|
trivia: [],
|
||||||
},
|
},
|
||||||
'yz'
|
'yz'
|
||||||
)
|
)
|
||||||
|
@ -278,6 +278,7 @@ export function mutateObjExpProp(
|
|||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
moduleId: 0,
|
moduleId: 0,
|
||||||
|
trivia: [],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -431,10 +432,11 @@ export function addSweep(
|
|||||||
} {
|
} {
|
||||||
const modifiedAst = structuredClone(node)
|
const modifiedAst = structuredClone(node)
|
||||||
const name = findUniqueName(node, KCL_DEFAULT_CONSTANT_PREFIXES.SWEEP)
|
const name = findUniqueName(node, KCL_DEFAULT_CONSTANT_PREFIXES.SWEEP)
|
||||||
const sweep = createCallExpressionStdLib('sweep', [
|
const sweep = createCallExpressionStdLibKw(
|
||||||
createObjectExpression({ path: createIdentifier(pathDeclarator.id.name) }),
|
'sweep',
|
||||||
createIdentifier(profileDeclarator.id.name),
|
createIdentifier(profileDeclarator.id.name),
|
||||||
])
|
[createLabeledArg('path', createIdentifier(pathDeclarator.id.name))]
|
||||||
|
)
|
||||||
const declaration = createVariableDeclaration(name, sweep)
|
const declaration = createVariableDeclaration(name, sweep)
|
||||||
modifiedAst.body.push(declaration)
|
modifiedAst.body.push(declaration)
|
||||||
const pathToNode: PathToNode = [
|
const pathToNode: PathToNode = [
|
||||||
@ -442,8 +444,9 @@ export function addSweep(
|
|||||||
[modifiedAst.body.length - 1, 'index'],
|
[modifiedAst.body.length - 1, 'index'],
|
||||||
['declaration', 'VariableDeclaration'],
|
['declaration', 'VariableDeclaration'],
|
||||||
['init', 'VariableDeclarator'],
|
['init', 'VariableDeclarator'],
|
||||||
['arguments', 'CallExpression'],
|
['arguments', 'CallExpressionKw'],
|
||||||
[0, 'index'],
|
[0, ARG_INDEX_FIELD],
|
||||||
|
['arg', LABELED_ARG_FIELD],
|
||||||
]
|
]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -683,6 +686,63 @@ export function addOffsetPlane({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append a helix to the AST
|
||||||
|
*/
|
||||||
|
export function addHelix({
|
||||||
|
node,
|
||||||
|
revolutions,
|
||||||
|
angleStart,
|
||||||
|
counterClockWise,
|
||||||
|
radius,
|
||||||
|
axis,
|
||||||
|
length,
|
||||||
|
}: {
|
||||||
|
node: Node<Program>
|
||||||
|
revolutions: Expr
|
||||||
|
angleStart: Expr
|
||||||
|
counterClockWise: boolean
|
||||||
|
radius: Expr
|
||||||
|
axis: string
|
||||||
|
length: Expr
|
||||||
|
}): { modifiedAst: Node<Program>; pathToNode: PathToNode } {
|
||||||
|
const modifiedAst = structuredClone(node)
|
||||||
|
const name = findUniqueName(node, KCL_DEFAULT_CONSTANT_PREFIXES.HELIX)
|
||||||
|
const variable = createVariableDeclaration(
|
||||||
|
name,
|
||||||
|
createCallExpressionStdLibKw(
|
||||||
|
'helix',
|
||||||
|
null, // Not in a pipeline
|
||||||
|
[
|
||||||
|
createLabeledArg('revolutions', revolutions),
|
||||||
|
createLabeledArg('angleStart', angleStart),
|
||||||
|
createLabeledArg('counterClockWise', createLiteral(counterClockWise)),
|
||||||
|
createLabeledArg('radius', radius),
|
||||||
|
createLabeledArg('axis', createLiteral(axis)),
|
||||||
|
createLabeledArg('length', length),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: figure out smart insertion than just appending at the end
|
||||||
|
const argIndex = 0
|
||||||
|
modifiedAst.body.push(variable)
|
||||||
|
const pathToNode: PathToNode = [
|
||||||
|
['body', ''],
|
||||||
|
[modifiedAst.body.length - 1, 'index'],
|
||||||
|
['declaration', 'VariableDeclaration'],
|
||||||
|
['init', 'VariableDeclarator'],
|
||||||
|
['arguments', 'CallExpressionKw'],
|
||||||
|
[argIndex, ARG_INDEX_FIELD],
|
||||||
|
['arg', LABELED_ARG_FIELD],
|
||||||
|
]
|
||||||
|
|
||||||
|
return {
|
||||||
|
modifiedAst,
|
||||||
|
pathToNode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a modified clone of an AST with a named constant inserted into the body
|
* Return a modified clone of an AST with a named constant inserted into the body
|
||||||
*/
|
*/
|
||||||
@ -831,6 +891,7 @@ export function createLiteral(value: LiteralValue | number): Node<Literal> {
|
|||||||
moduleId: 0,
|
moduleId: 0,
|
||||||
value,
|
value,
|
||||||
raw,
|
raw,
|
||||||
|
trivia: [],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -840,6 +901,7 @@ export function createTagDeclarator(value: string): Node<TagDeclarator> {
|
|||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
moduleId: 0,
|
moduleId: 0,
|
||||||
|
trivia: [],
|
||||||
|
|
||||||
value,
|
value,
|
||||||
}
|
}
|
||||||
@ -851,6 +913,7 @@ export function createIdentifier(name: string): Node<Identifier> {
|
|||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
moduleId: 0,
|
moduleId: 0,
|
||||||
|
trivia: [],
|
||||||
|
|
||||||
name,
|
name,
|
||||||
}
|
}
|
||||||
@ -862,6 +925,7 @@ export function createPipeSubstitution(): Node<PipeSubstitution> {
|
|||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
moduleId: 0,
|
moduleId: 0,
|
||||||
|
trivia: [],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -874,11 +938,13 @@ export function createCallExpressionStdLib(
|
|||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
moduleId: 0,
|
moduleId: 0,
|
||||||
|
trivia: [],
|
||||||
callee: {
|
callee: {
|
||||||
type: 'Identifier',
|
type: 'Identifier',
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
moduleId: 0,
|
moduleId: 0,
|
||||||
|
trivia: [],
|
||||||
|
|
||||||
name,
|
name,
|
||||||
},
|
},
|
||||||
@ -896,11 +962,13 @@ export function createCallExpressionStdLibKw(
|
|||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
moduleId: 0,
|
moduleId: 0,
|
||||||
|
trivia: [],
|
||||||
callee: {
|
callee: {
|
||||||
type: 'Identifier',
|
type: 'Identifier',
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
moduleId: 0,
|
moduleId: 0,
|
||||||
|
trivia: [],
|
||||||
|
|
||||||
name,
|
name,
|
||||||
},
|
},
|
||||||
@ -918,11 +986,13 @@ export function createCallExpression(
|
|||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
moduleId: 0,
|
moduleId: 0,
|
||||||
|
trivia: [],
|
||||||
callee: {
|
callee: {
|
||||||
type: 'Identifier',
|
type: 'Identifier',
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
moduleId: 0,
|
moduleId: 0,
|
||||||
|
trivia: [],
|
||||||
|
|
||||||
name,
|
name,
|
||||||
},
|
},
|
||||||
@ -938,6 +1008,7 @@ export function createArrayExpression(
|
|||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
moduleId: 0,
|
moduleId: 0,
|
||||||
|
trivia: [],
|
||||||
|
|
||||||
nonCodeMeta: nonCodeMetaEmpty(),
|
nonCodeMeta: nonCodeMetaEmpty(),
|
||||||
elements,
|
elements,
|
||||||
@ -952,6 +1023,7 @@ export function createPipeExpression(
|
|||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
moduleId: 0,
|
moduleId: 0,
|
||||||
|
trivia: [],
|
||||||
|
|
||||||
body,
|
body,
|
||||||
nonCodeMeta: nonCodeMetaEmpty(),
|
nonCodeMeta: nonCodeMetaEmpty(),
|
||||||
@ -969,12 +1041,14 @@ export function createVariableDeclaration(
|
|||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
moduleId: 0,
|
moduleId: 0,
|
||||||
|
trivia: [],
|
||||||
|
|
||||||
declaration: {
|
declaration: {
|
||||||
type: 'VariableDeclarator',
|
type: 'VariableDeclarator',
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
moduleId: 0,
|
moduleId: 0,
|
||||||
|
trivia: [],
|
||||||
|
|
||||||
id: createIdentifier(varName),
|
id: createIdentifier(varName),
|
||||||
init,
|
init,
|
||||||
@ -992,6 +1066,7 @@ export function createObjectExpression(properties: {
|
|||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
moduleId: 0,
|
moduleId: 0,
|
||||||
|
trivia: [],
|
||||||
|
|
||||||
nonCodeMeta: nonCodeMetaEmpty(),
|
nonCodeMeta: nonCodeMetaEmpty(),
|
||||||
properties: Object.entries(properties).map(([key, value]) => ({
|
properties: Object.entries(properties).map(([key, value]) => ({
|
||||||
@ -999,6 +1074,7 @@ export function createObjectExpression(properties: {
|
|||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
moduleId: 0,
|
moduleId: 0,
|
||||||
|
trivia: [],
|
||||||
key: createIdentifier(key),
|
key: createIdentifier(key),
|
||||||
|
|
||||||
value,
|
value,
|
||||||
@ -1015,6 +1091,7 @@ export function createUnaryExpression(
|
|||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
moduleId: 0,
|
moduleId: 0,
|
||||||
|
trivia: [],
|
||||||
|
|
||||||
operator,
|
operator,
|
||||||
argument,
|
argument,
|
||||||
@ -1031,6 +1108,7 @@ export function createBinaryExpression([left, operator, right]: [
|
|||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
moduleId: 0,
|
moduleId: 0,
|
||||||
|
trivia: [],
|
||||||
|
|
||||||
operator,
|
operator,
|
||||||
left,
|
left,
|
||||||
@ -1316,6 +1394,7 @@ export async function deleteFromSelection(
|
|||||||
varDec.node.init.type === 'PipeExpression') ||
|
varDec.node.init.type === 'PipeExpression') ||
|
||||||
selection.artifact?.type === 'sweep' ||
|
selection.artifact?.type === 'sweep' ||
|
||||||
selection.artifact?.type === 'plane' ||
|
selection.artifact?.type === 'plane' ||
|
||||||
|
selection.artifact?.type === 'helix' ||
|
||||||
!selection.artifact // aka expected to be a shell at this point
|
!selection.artifact // aka expected to be a shell at this point
|
||||||
) {
|
) {
|
||||||
let extrudeNameToDelete = ''
|
let extrudeNameToDelete = ''
|
||||||
@ -1323,7 +1402,8 @@ export async function deleteFromSelection(
|
|||||||
if (
|
if (
|
||||||
selection.artifact &&
|
selection.artifact &&
|
||||||
selection.artifact.type !== 'sweep' &&
|
selection.artifact.type !== 'sweep' &&
|
||||||
selection.artifact.type !== 'plane'
|
selection.artifact.type !== 'plane' &&
|
||||||
|
selection.artifact.type !== 'helix'
|
||||||
) {
|
) {
|
||||||
const varDecName = varDec.node.id.name
|
const varDecName = varDec.node.id.name
|
||||||
traverse(astClone, {
|
traverse(astClone, {
|
||||||
@ -1362,13 +1442,17 @@ export async function deleteFromSelection(
|
|||||||
if (!pathToNode) return new Error('Could not find extrude variable')
|
if (!pathToNode) return new Error('Could not find extrude variable')
|
||||||
} else {
|
} else {
|
||||||
pathToNode = selection.codeRef.pathToNode
|
pathToNode = selection.codeRef.pathToNode
|
||||||
const extrudeVarDec = getNodeFromPath<VariableDeclarator>(
|
if (varDec.node.type !== 'VariableDeclarator') {
|
||||||
astClone,
|
const callExp = getNodeFromPath<CallExpression>(
|
||||||
pathToNode,
|
astClone,
|
||||||
'VariableDeclarator'
|
pathToNode,
|
||||||
)
|
'CallExpression'
|
||||||
if (err(extrudeVarDec)) return extrudeVarDec
|
)
|
||||||
extrudeNameToDelete = extrudeVarDec.node.id.name
|
if (err(callExp)) return callExp
|
||||||
|
extrudeNameToDelete = callExp.node.callee.name
|
||||||
|
} else {
|
||||||
|
extrudeNameToDelete = varDec.node.id.name
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const expressionIndex = pathToNode[1][0] as number
|
const expressionIndex = pathToNode[1][0] as number
|
||||||
|
@ -46,6 +46,7 @@ export function revolveSketch(
|
|||||||
if (err(sketchNode)) return sketchNode
|
if (err(sketchNode)) return sketchNode
|
||||||
|
|
||||||
let generatedAxis
|
let generatedAxis
|
||||||
|
let axisDeclaration: PathToNode | null = null
|
||||||
|
|
||||||
if (axisOrEdge === 'Edge') {
|
if (axisOrEdge === 'Edge') {
|
||||||
const pathToAxisSelection = getNodePathFromSourceRange(
|
const pathToAxisSelection = getNodePathFromSourceRange(
|
||||||
@ -70,6 +71,13 @@ export function revolveSketch(
|
|||||||
const axisSelection = edge?.graphSelections[0]?.artifact
|
const axisSelection = edge?.graphSelections[0]?.artifact
|
||||||
if (!axisSelection) return new Error('Generated axis selection is missing.')
|
if (!axisSelection) return new Error('Generated axis selection is missing.')
|
||||||
generatedAxis = getEdgeTagCall(tag, axisSelection)
|
generatedAxis = getEdgeTagCall(tag, axisSelection)
|
||||||
|
if (
|
||||||
|
axisSelection.type === 'segment' ||
|
||||||
|
axisSelection.type === 'path' ||
|
||||||
|
axisSelection.type === 'edgeCut'
|
||||||
|
) {
|
||||||
|
axisDeclaration = axisSelection.codeRef.pathToNode
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
generatedAxis = createLiteral(axis)
|
generatedAxis = createLiteral(axis)
|
||||||
}
|
}
|
||||||
@ -139,18 +147,34 @@ export function revolveSketch(
|
|||||||
const sketchIndexInPathToNode =
|
const sketchIndexInPathToNode =
|
||||||
sketchPathToDecleration.findIndex((a) => a[0] === 'body') + 1
|
sketchPathToDecleration.findIndex((a) => a[0] === 'body') + 1
|
||||||
const sketchIndexInBody = sketchPathToDecleration[sketchIndexInPathToNode][0]
|
const sketchIndexInBody = sketchPathToDecleration[sketchIndexInPathToNode][0]
|
||||||
if (typeof sketchIndexInBody !== 'number')
|
let insertIndex = sketchIndexInBody
|
||||||
return new Error('expected sketchIndexInBody to be a number')
|
|
||||||
clonedAst.body.splice(sketchIndexInBody + 1, 0, VariableDeclaration)
|
if (typeof insertIndex !== 'number')
|
||||||
|
return new Error('expected insertIndex to be a number')
|
||||||
|
|
||||||
|
// If an axis was selected in KCL, find the max index to insert the revolve command
|
||||||
|
if (axisDeclaration) {
|
||||||
|
const axisIndexInPathToNode =
|
||||||
|
axisDeclaration.findIndex((a) => a[0] === 'body') + 1
|
||||||
|
const axisIndex = axisDeclaration[axisIndexInPathToNode][0]
|
||||||
|
|
||||||
|
if (typeof axisIndex !== 'number')
|
||||||
|
return new Error('expected axisIndex to be a number')
|
||||||
|
|
||||||
|
insertIndex = Math.max(insertIndex, axisIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
clonedAst.body.splice(insertIndex + 1, 0, VariableDeclaration)
|
||||||
|
|
||||||
const pathToRevolveArg: PathToNode = [
|
const pathToRevolveArg: PathToNode = [
|
||||||
['body', ''],
|
['body', ''],
|
||||||
[sketchIndexInBody + 1, 'index'],
|
[insertIndex + 1, 'index'],
|
||||||
['declaration', 'VariableDeclaration'],
|
['declaration', 'VariableDeclaration'],
|
||||||
['init', 'VariableDeclarator'],
|
['init', 'VariableDeclarator'],
|
||||||
['arguments', 'CallExpression'],
|
['arguments', 'CallExpression'],
|
||||||
[0, 'index'],
|
[0, 'index'],
|
||||||
]
|
]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
modifiedAst: clonedAst,
|
modifiedAst: clonedAst,
|
||||||
pathToSketchNode: [...pathToSketchNode.slice(0, -1), [-1, 'index']],
|
pathToSketchNode: [...pathToSketchNode.slice(0, -1), [-1, 'index']],
|
||||||
|
@ -17,6 +17,8 @@ import {
|
|||||||
createObjectExpression,
|
createObjectExpression,
|
||||||
createArrayExpression,
|
createArrayExpression,
|
||||||
createVariableDeclaration,
|
createVariableDeclaration,
|
||||||
|
createCallExpressionStdLibKw,
|
||||||
|
createLabeledArg,
|
||||||
} from 'lang/modifyAst'
|
} from 'lang/modifyAst'
|
||||||
import { KCL_DEFAULT_CONSTANT_PREFIXES } from 'lib/constants'
|
import { KCL_DEFAULT_CONSTANT_PREFIXES } from 'lib/constants'
|
||||||
import { KclManager } from 'lang/KclSingleton'
|
import { KclManager } from 'lang/KclSingleton'
|
||||||
@ -121,13 +123,14 @@ export function addShell({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const name = findUniqueName(node, KCL_DEFAULT_CONSTANT_PREFIXES.SHELL)
|
const name = findUniqueName(node, KCL_DEFAULT_CONSTANT_PREFIXES.SHELL)
|
||||||
const shell = createCallExpressionStdLib('shell', [
|
const shell = createCallExpressionStdLibKw(
|
||||||
createObjectExpression({
|
'shell',
|
||||||
faces: createArrayExpression(expressions),
|
|
||||||
thickness,
|
|
||||||
}),
|
|
||||||
createIdentifier(extrudeNode.node.id.name),
|
createIdentifier(extrudeNode.node.id.name),
|
||||||
])
|
[
|
||||||
|
createLabeledArg('faces', createArrayExpression(expressions)),
|
||||||
|
createLabeledArg('thickness', thickness),
|
||||||
|
]
|
||||||
|
)
|
||||||
const declaration = createVariableDeclaration(name, shell)
|
const declaration = createVariableDeclaration(name, shell)
|
||||||
|
|
||||||
// TODO: check if we should append at the end like here or right after the extrude
|
// TODO: check if we should append at the end like here or right after the extrude
|
||||||
@ -137,8 +140,7 @@ export function addShell({
|
|||||||
[modifiedAst.body.length - 1, 'index'],
|
[modifiedAst.body.length - 1, 'index'],
|
||||||
['declaration', 'VariableDeclaration'],
|
['declaration', 'VariableDeclaration'],
|
||||||
['init', 'VariableDeclarator'],
|
['init', 'VariableDeclarator'],
|
||||||
['arguments', 'CallExpression'],
|
['unlabeled', 'CallExpressionKw'],
|
||||||
[0, 'index'],
|
|
||||||
]
|
]
|
||||||
return {
|
return {
|
||||||
modifiedAst,
|
modifiedAst,
|
||||||
|
@ -23,6 +23,9 @@ import {
|
|||||||
VariableDeclaration,
|
VariableDeclaration,
|
||||||
VariableDeclarator,
|
VariableDeclarator,
|
||||||
recast,
|
recast,
|
||||||
|
kclSettings,
|
||||||
|
unitLenToUnitLength,
|
||||||
|
unitAngToUnitAngle,
|
||||||
} from './wasm'
|
} from './wasm'
|
||||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||||
import { createIdentifier, splitPathAtLastIndex } from './modifyAst'
|
import { createIdentifier, splitPathAtLastIndex } from './modifyAst'
|
||||||
@ -38,6 +41,7 @@ import { ImportStatement } from 'wasm-lib/kcl/bindings/ImportStatement'
|
|||||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||||
import { findKwArg } from './util'
|
import { findKwArg } from './util'
|
||||||
import { codeRefFromRange } from './std/artifactGraph'
|
import { codeRefFromRange } from './std/artifactGraph'
|
||||||
|
import { KclSettingsAnnotation } from 'lib/settings/settingsTypes'
|
||||||
|
|
||||||
export const LABELED_ARG_FIELD = 'LabeledArg -> Arg'
|
export const LABELED_ARG_FIELD = 'LabeledArg -> Arg'
|
||||||
export const ARG_INDEX_FIELD = 'arg index'
|
export const ARG_INDEX_FIELD = 'arg index'
|
||||||
@ -866,3 +870,24 @@ export function getObjExprProperty(
|
|||||||
if (index === -1) return null
|
if (index === -1) return null
|
||||||
return { expr: node.properties[index].value, index }
|
return { expr: node.properties[index].value, index }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given KCL, returns the settings annotation object if it exists.
|
||||||
|
*/
|
||||||
|
export function getSettingsAnnotation(
|
||||||
|
kcl: string | Node<Program>
|
||||||
|
): KclSettingsAnnotation | Error {
|
||||||
|
const metaSettings = kclSettings(kcl)
|
||||||
|
if (err(metaSettings)) return metaSettings
|
||||||
|
|
||||||
|
const settings: KclSettingsAnnotation = {}
|
||||||
|
// No settings in the KCL.
|
||||||
|
if (!metaSettings) return settings
|
||||||
|
|
||||||
|
settings.defaultLengthUnit = unitLenToUnitLength(
|
||||||
|
metaSettings.defaultLengthUnits
|
||||||
|
)
|
||||||
|
settings.defaultAngleUnit = unitAngToUnitAngle(metaSettings.defaultAngleUnits)
|
||||||
|
|
||||||
|
return settings
|
||||||
|
}
|
||||||
|
@ -1406,6 +1406,8 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
commandId: string
|
commandId: string
|
||||||
}
|
}
|
||||||
settings: SettingsViaQueryString
|
settings: SettingsViaQueryString
|
||||||
|
width: number = 1337
|
||||||
|
height: number = 1337
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Export intent traxcks the intent of the export. If it is null there is no
|
* Export intent traxcks the intent of the export. If it is null there is no
|
||||||
@ -1511,6 +1513,9 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.width = width
|
||||||
|
this.height = height
|
||||||
|
|
||||||
// If we already have an engine connection, just need to resize the stream.
|
// If we already have an engine connection, just need to resize the stream.
|
||||||
if (this.engineConnection) {
|
if (this.engineConnection) {
|
||||||
this.handleResize({
|
this.handleResize({
|
||||||
@ -1823,6 +1828,9 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.width = streamWidth
|
||||||
|
this.height = streamHeight
|
||||||
|
|
||||||
const resizeCmd: EngineCommand = {
|
const resizeCmd: EngineCommand = {
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
|
@ -1954,6 +1954,7 @@ export const updateStartProfileAtArgs: SketchLineHelper['updateArgs'] = ({
|
|||||||
startNodes: [],
|
startNodes: [],
|
||||||
nonCodeNodes: [],
|
nonCodeNodes: [],
|
||||||
},
|
},
|
||||||
|
trivia: [],
|
||||||
},
|
},
|
||||||
pathToNode,
|
pathToNode,
|
||||||
}
|
}
|
||||||
@ -2534,6 +2535,8 @@ function addTagKw(): addTagFn {
|
|||||||
...primaryCallExp,
|
...primaryCallExp,
|
||||||
start: callExpr.node.start,
|
start: callExpr.node.start,
|
||||||
end: callExpr.node.end,
|
end: callExpr.node.end,
|
||||||
|
moduleId: callExpr.node.moduleId,
|
||||||
|
trivia: callExpr.node.trivia,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ import {
|
|||||||
default_project_settings,
|
default_project_settings,
|
||||||
base64_decode,
|
base64_decode,
|
||||||
clear_scene_and_bust_cache,
|
clear_scene_and_bust_cache,
|
||||||
|
kcl_settings,
|
||||||
change_kcl_settings,
|
change_kcl_settings,
|
||||||
reloadModule,
|
reloadModule,
|
||||||
} from 'lib/wasm_lib_wrapper'
|
} from 'lib/wasm_lib_wrapper'
|
||||||
@ -58,6 +59,9 @@ import { Artifact } from './std/artifactGraph'
|
|||||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||||
import { NumericSuffix } from 'wasm-lib/kcl/bindings/NumericSuffix'
|
import { NumericSuffix } from 'wasm-lib/kcl/bindings/NumericSuffix'
|
||||||
import { MetaSettings } from 'wasm-lib/kcl/bindings/MetaSettings'
|
import { MetaSettings } from 'wasm-lib/kcl/bindings/MetaSettings'
|
||||||
|
import { UnitAngle, UnitLength } from 'wasm-lib/kcl/bindings/ModelingCmd'
|
||||||
|
import { UnitLen } from 'wasm-lib/kcl/bindings/UnitLen'
|
||||||
|
import { UnitAngle as UnitAng } from 'wasm-lib/kcl/bindings/UnitAngle'
|
||||||
|
|
||||||
export type { Artifact } from 'wasm-lib/kcl/bindings/Artifact'
|
export type { Artifact } from 'wasm-lib/kcl/bindings/Artifact'
|
||||||
export type { ArtifactCommand } from 'wasm-lib/kcl/bindings/Artifact'
|
export type { ArtifactCommand } from 'wasm-lib/kcl/bindings/Artifact'
|
||||||
@ -857,8 +861,35 @@ export function base64Decode(base64: string): ArrayBuffer | Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Change the meta settings for the kcl file.
|
/**
|
||||||
/// Returns the new kcl string with the updated settings.
|
* Get the meta settings for the KCL. If no settings were set in the file,
|
||||||
|
* returns null.
|
||||||
|
*/
|
||||||
|
export function kclSettings(
|
||||||
|
kcl: string | Node<Program>
|
||||||
|
): MetaSettings | null | Error {
|
||||||
|
let program: Node<Program>
|
||||||
|
if (typeof kcl === 'string') {
|
||||||
|
const parseResult = parse(kcl)
|
||||||
|
if (err(parseResult)) return parseResult
|
||||||
|
if (!resultIsOk(parseResult)) {
|
||||||
|
return new Error(`parse result had errors`, { cause: parseResult })
|
||||||
|
}
|
||||||
|
program = parseResult.program
|
||||||
|
} else {
|
||||||
|
program = kcl
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return kcl_settings(JSON.stringify(program))
|
||||||
|
} catch (e) {
|
||||||
|
return new Error('Caught error getting kcl settings', { cause: e })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the meta settings for the kcl file.
|
||||||
|
* @returns the new kcl string with the updated settings.
|
||||||
|
*/
|
||||||
export function changeKclSettings(
|
export function changeKclSettings(
|
||||||
kcl: string,
|
kcl: string,
|
||||||
settings: MetaSettings
|
settings: MetaSettings
|
||||||
@ -866,7 +897,59 @@ export function changeKclSettings(
|
|||||||
try {
|
try {
|
||||||
return change_kcl_settings(kcl, JSON.stringify(settings))
|
return change_kcl_settings(kcl, JSON.stringify(settings))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Caught error changing kcl settings: ' + e)
|
console.error('Caught error changing kcl settings', e)
|
||||||
return new Error('Caught error changing kcl settings: ' + e)
|
return new Error('Caught error changing kcl settings', { cause: e })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a `UnitLength_type` to a `UnitLen`
|
||||||
|
*/
|
||||||
|
export function unitLengthToUnitLen(input: UnitLength): UnitLen {
|
||||||
|
switch (input) {
|
||||||
|
case 'm':
|
||||||
|
return { type: 'M' }
|
||||||
|
case 'cm':
|
||||||
|
return { type: 'Cm' }
|
||||||
|
case 'yd':
|
||||||
|
return { type: 'Yards' }
|
||||||
|
case 'ft':
|
||||||
|
return { type: 'Feet' }
|
||||||
|
case 'in':
|
||||||
|
return { type: 'Inches' }
|
||||||
|
default:
|
||||||
|
return { type: 'Mm' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert `UnitLen` to `UnitLength_type`.
|
||||||
|
*/
|
||||||
|
export function unitLenToUnitLength(input: UnitLen): UnitLength {
|
||||||
|
switch (input.type) {
|
||||||
|
case 'M':
|
||||||
|
return 'm'
|
||||||
|
case 'Cm':
|
||||||
|
return 'cm'
|
||||||
|
case 'Yards':
|
||||||
|
return 'yd'
|
||||||
|
case 'Feet':
|
||||||
|
return 'ft'
|
||||||
|
case 'Inches':
|
||||||
|
return 'in'
|
||||||
|
default:
|
||||||
|
return 'mm'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert `UnitAngle` to `UnitAngle_type`.
|
||||||
|
*/
|
||||||
|
export function unitAngToUnitAngle(input: UnitAng): UnitAngle {
|
||||||
|
switch (input.type) {
|
||||||
|
case 'Radians':
|
||||||
|
return 'radians'
|
||||||
|
default:
|
||||||
|
return 'degrees'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,6 +76,14 @@ export type ModelingCommandSchema = {
|
|||||||
plane: Selections
|
plane: Selections
|
||||||
distance: KclCommandValue
|
distance: KclCommandValue
|
||||||
}
|
}
|
||||||
|
Helix: {
|
||||||
|
revolutions: KclCommandValue
|
||||||
|
angleStart: KclCommandValue
|
||||||
|
counterClockWise: boolean
|
||||||
|
radius: KclCommandValue
|
||||||
|
axis: string
|
||||||
|
length: KclCommandValue
|
||||||
|
}
|
||||||
'change tool': {
|
'change tool': {
|
||||||
tool: SketchTool
|
tool: SketchTool
|
||||||
}
|
}
|
||||||
@ -447,6 +455,53 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Helix: {
|
||||||
|
description: 'Create a helix or spiral in 3D about an axis.',
|
||||||
|
icon: 'helix',
|
||||||
|
status: 'development',
|
||||||
|
needsReview: true,
|
||||||
|
args: {
|
||||||
|
revolutions: {
|
||||||
|
inputType: 'kcl',
|
||||||
|
defaultValue: '1',
|
||||||
|
required: true,
|
||||||
|
warningMessage:
|
||||||
|
'The helix workflow is new and under tested. Please break it and report issues.',
|
||||||
|
},
|
||||||
|
angleStart: {
|
||||||
|
inputType: 'kcl',
|
||||||
|
defaultValue: KCL_DEFAULT_DEGREE,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
counterClockWise: {
|
||||||
|
inputType: 'options',
|
||||||
|
required: true,
|
||||||
|
options: [
|
||||||
|
{ name: 'True', isCurrent: false, value: true },
|
||||||
|
{ name: 'False', isCurrent: true, value: false },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
radius: {
|
||||||
|
inputType: 'kcl',
|
||||||
|
defaultValue: KCL_DEFAULT_LENGTH,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
axis: {
|
||||||
|
inputType: 'options',
|
||||||
|
required: true,
|
||||||
|
options: [
|
||||||
|
{ name: 'X Axis', isCurrent: true, value: 'X' },
|
||||||
|
{ name: 'Y Axis', isCurrent: false, value: 'Y' },
|
||||||
|
{ name: 'Z Axis', isCurrent: false, value: 'Z' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
length: {
|
||||||
|
inputType: 'kcl',
|
||||||
|
defaultValue: KCL_DEFAULT_LENGTH,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
Fillet: {
|
Fillet: {
|
||||||
description: 'Fillet edge',
|
description: 'Fillet edge',
|
||||||
icon: 'fillet3d',
|
icon: 'fillet3d',
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import { UnitLength_type } from '@kittycad/lib/dist/types/src/models'
|
|
||||||
import { CommandBarOverwriteWarning } from 'components/CommandBarOverwriteWarning'
|
import { CommandBarOverwriteWarning } from 'components/CommandBarOverwriteWarning'
|
||||||
import { StateMachineCommandSetConfig } from 'lib/commandTypes'
|
import { StateMachineCommandSetConfig } from 'lib/commandTypes'
|
||||||
import { isDesktop } from 'lib/isDesktop'
|
import { isDesktop } from 'lib/isDesktop'
|
||||||
import { baseUnitLabels, baseUnitsUnion } from 'lib/settings/settingsTypes'
|
|
||||||
import { projectsMachine } from 'machines/projectsMachine'
|
import { projectsMachine } from 'machines/projectsMachine'
|
||||||
|
|
||||||
export type ProjectsCommandSchema = {
|
export type ProjectsCommandSchema = {
|
||||||
@ -23,7 +21,6 @@ export type ProjectsCommandSchema = {
|
|||||||
'Import file from URL': {
|
'Import file from URL': {
|
||||||
name: string
|
name: string
|
||||||
code?: string
|
code?: string
|
||||||
units: UnitLength_type
|
|
||||||
method: 'newProject' | 'existingProject'
|
method: 'newProject' | 'existingProject'
|
||||||
projectName?: string
|
projectName?: string
|
||||||
}
|
}
|
||||||
@ -157,15 +154,6 @@ export const projectsCommandBarConfig: StateMachineCommandSetConfig<
|
|||||||
return `${lineCount} line${lineCount === 1 ? '' : 's'}`
|
return `${lineCount} line${lineCount === 1 ? '' : 's'}`
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
units: {
|
|
||||||
inputType: 'options',
|
|
||||||
required: false,
|
|
||||||
skip: true,
|
|
||||||
options: baseUnitsUnion.map((unit) => ({
|
|
||||||
name: baseUnitLabels[unit],
|
|
||||||
value: unit,
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
reviewMessage(commandBarContext) {
|
reviewMessage(commandBarContext) {
|
||||||
return isDesktop()
|
return isDesktop()
|
||||||
|
@ -58,6 +58,7 @@ export const KCL_DEFAULT_CONSTANT_PREFIXES = {
|
|||||||
SEGMENT: 'seg',
|
SEGMENT: 'seg',
|
||||||
REVOLVE: 'revolve',
|
REVOLVE: 'revolve',
|
||||||
PLANE: 'plane',
|
PLANE: 'plane',
|
||||||
|
HELIX: 'helix',
|
||||||
} as const
|
} as const
|
||||||
/** The default KCL length expression */
|
/** The default KCL length expression */
|
||||||
export const KCL_DEFAULT_LENGTH = `5`
|
export const KCL_DEFAULT_LENGTH = `5`
|
||||||
|
@ -12,104 +12,68 @@ wallMountL = 2 // inches
|
|||||||
shelfDepth = 12 // Shelf is 12 inches in depth from the wall
|
shelfDepth = 12 // Shelf is 12 inches in depth from the wall
|
||||||
moment = shelfDepth * p // assume the force is applied at the end of the shelf to be conservative (lb-in)
|
moment = shelfDepth * p // assume the force is applied at the end of the shelf to be conservative (lb-in)
|
||||||
|
|
||||||
|
|
||||||
filletRadius = .375 // inches
|
|
||||||
extFilletRadius = .25 // inches
|
|
||||||
mountingHoleDiameter = 0.5 // inches
|
|
||||||
|
|
||||||
|
|
||||||
// Calculate required thickness of bracket
|
// Calculate required thickness of bracket
|
||||||
thickness = sqrt(moment * factorOfSafety * 6 / (sigmaAllow * width)) // this is the calculation of two brackets holding up the shelf (inches)
|
thickness = sqrt(moment * factorOfSafety * 6 / (sigmaAllow * width)) // this is the calculation of two brackets holding up the shelf (inches)
|
||||||
|
|
||||||
|
filletRadius = .25
|
||||||
|
extFilletRadius = filletRadius + thickness
|
||||||
|
mountingHoleDiameter = 0.5
|
||||||
|
|
||||||
// Sketch the bracket body and fillet the inner and outer edges of the bend
|
sketch001 = startSketchOn('XZ')
|
||||||
bracketLeg1Sketch = startSketchOn('XY')
|
|
||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> line(end = [shelfMountL - filletRadius, 0], tag = $fillet1)
|
|> xLine(shelfMountL - thickness, %, $seg01)
|
||||||
|> line(end = [0, width], tag = $fillet2)
|
|> yLine(thickness, %, $seg02)
|
||||||
|> line(end = [-shelfMountL + filletRadius, 0])
|
|> xLine(-shelfMountL, %, $seg03)
|
||||||
|
|> yLine(-wallMountL, %, $seg04)
|
||||||
|
|> xLine(thickness, %, $seg05)
|
||||||
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg06)
|
||||||
|> close()
|
|> close()
|
||||||
|> hole(circle({
|
|> extrude(%, length = width)
|
||||||
center = [1, 1],
|
|
||||||
radius = mountingHoleDiameter / 2
|
|
||||||
}, %), %)
|
|
||||||
|> hole(circle({
|
|
||||||
center = [shelfMountL - 1.5, width - 1],
|
|
||||||
radius = mountingHoleDiameter / 2
|
|
||||||
}, %), %)
|
|
||||||
|> hole(circle({
|
|
||||||
center = [1, width - 1],
|
|
||||||
radius = mountingHoleDiameter / 2
|
|
||||||
}, %), %)
|
|
||||||
|> hole(circle({
|
|
||||||
center = [shelfMountL - 1.5, 1],
|
|
||||||
radius = mountingHoleDiameter / 2
|
|
||||||
}, %), %)
|
|
||||||
|
|
||||||
// Extrude the leg 2 bracket sketch
|
|
||||||
bracketLeg1Extrude = extrude(bracketLeg1Sketch, length = thickness)
|
|
||||||
|> fillet({
|
|> fillet({
|
||||||
radius = extFilletRadius,
|
radius = extFilletRadius,
|
||||||
tags = [
|
tags = [getNextAdjacentEdge(seg03)]
|
||||||
getNextAdjacentEdge(fillet1),
|
|
||||||
getNextAdjacentEdge(fillet2)
|
|
||||||
]
|
|
||||||
}, %)
|
}, %)
|
||||||
|
|
||||||
// Sketch the fillet arc
|
|
||||||
filletSketch = startSketchOn('XZ')
|
|
||||||
|> startProfileAt([0, 0], %)
|
|
||||||
|> line(end = [0, thickness])
|
|
||||||
|> arc({
|
|
||||||
angleEnd = 180,
|
|
||||||
angleStart = 90,
|
|
||||||
radius = filletRadius + thickness
|
|
||||||
}, %)
|
|
||||||
|> line(end = [thickness, 0])
|
|
||||||
|> arc({
|
|
||||||
angleEnd = 90,
|
|
||||||
angleStart = 180,
|
|
||||||
radius = filletRadius
|
|
||||||
}, %)
|
|
||||||
|
|
||||||
// Sketch the bend
|
|
||||||
filletExtrude = extrude(filletSketch, length = -width)
|
|
||||||
|
|
||||||
// Create a custom plane for the leg that sits on the wall
|
|
||||||
customPlane = {
|
|
||||||
plane = {
|
|
||||||
origin = { x = -filletRadius, y = 0, z = 0 },
|
|
||||||
xAxis = { x = 0, y = 1, z = 0 },
|
|
||||||
yAxis = { x = 0, y = 0, z = 1 },
|
|
||||||
zAxis = { x = 1, y = 0, z = 0 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a sketch for the second leg
|
|
||||||
bracketLeg2Sketch = startSketchOn(customPlane)
|
|
||||||
|> startProfileAt([0, -filletRadius], %)
|
|
||||||
|> line(end = [width, 0])
|
|
||||||
|> line(end = [0, -wallMountL], tag = $fillet3)
|
|
||||||
|> line(end = [-width, 0], tag = $fillet4)
|
|
||||||
|> close()
|
|
||||||
|> hole(circle({
|
|
||||||
center = [1, -1.5],
|
|
||||||
radius = mountingHoleDiameter / 2
|
|
||||||
}, %), %)
|
|
||||||
|> hole(circle({
|
|
||||||
center = [5, -1.5],
|
|
||||||
radius = mountingHoleDiameter / 2
|
|
||||||
}, %), %)
|
|
||||||
|
|
||||||
// Extrude the second leg
|
|
||||||
bracketLeg2Extrude = extrude(bracketLeg2Sketch, length = -thickness)
|
|
||||||
|> fillet({
|
|> fillet({
|
||||||
radius = extFilletRadius,
|
radius = filletRadius,
|
||||||
tags = [
|
tags = [getNextAdjacentEdge(seg06)]
|
||||||
getNextAdjacentEdge(fillet3),
|
|
||||||
getNextAdjacentEdge(fillet4)
|
|
||||||
]
|
|
||||||
}, %)
|
}, %)
|
||||||
|
|> fillet({
|
||||||
|
radius = filletRadius,
|
||||||
|
tags = [seg02, getOppositeEdge(seg02)],
|
||||||
|
}, %)
|
||||||
|
|> fillet({
|
||||||
|
radius = filletRadius,
|
||||||
|
tags = [seg05, getOppositeEdge(seg05)],
|
||||||
|
}, %)
|
||||||
|
|
||||||
|
sketch002 = startSketchOn(sketch001, seg03)
|
||||||
|
|> circle({
|
||||||
|
center = [-1.25, 1],
|
||||||
|
radius = mountingHoleDiameter / 2,
|
||||||
|
}, %)
|
||||||
|
|> patternLinear2d({
|
||||||
|
instances = 2,
|
||||||
|
distance = 2.5,
|
||||||
|
axis = [-1, 0],
|
||||||
|
}, %)
|
||||||
|
|> patternLinear2d({
|
||||||
|
instances = 2,
|
||||||
|
distance = 4,
|
||||||
|
axis = [0, 1],
|
||||||
|
}, %)
|
||||||
|
|> extrude(%, length = -thickness-.01)
|
||||||
|
|
||||||
|
sketch003 = startSketchOn(sketch001, seg04)
|
||||||
|
|> circle({
|
||||||
|
center = [1, -1],
|
||||||
|
radius = mountingHoleDiameter / 2,
|
||||||
|
}, %)
|
||||||
|
|> patternLinear2d({
|
||||||
|
instances = 2,
|
||||||
|
distance = 4,
|
||||||
|
axis = [1, 0],
|
||||||
|
}, %)
|
||||||
|
|> extrude(%, length = -thickness-0.1)
|
||||||
`
|
`
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -136,7 +136,7 @@ export function kclCommands(commandProps: KclCommandConfig): Command[] {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'share-file-link',
|
name: 'share-file-link',
|
||||||
displayName: 'Share file',
|
displayName: 'Share current part (via Zoo link)',
|
||||||
hide: IS_NIGHTLY_OR_DEBUG ? undefined : 'desktop',
|
hide: IS_NIGHTLY_OR_DEBUG ? undefined : 'desktop',
|
||||||
description: 'Create a link that contains a copy of the current file.',
|
description: 'Create a link that contains a copy of the current file.',
|
||||||
groupId: 'code',
|
groupId: 'code',
|
||||||
@ -147,7 +147,6 @@ export function kclCommands(commandProps: KclCommandConfig): Command[] {
|
|||||||
token: commandProps.authToken,
|
token: commandProps.authToken,
|
||||||
code: codeManager.code,
|
code: codeManager.code,
|
||||||
name: commandProps.projectData.project?.name || '',
|
name: commandProps.projectData.project?.name || '',
|
||||||
units: commandProps.settings.defaultUnit,
|
|
||||||
}).catch(reportRejection)
|
}).catch(reportRejection)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -5,13 +5,12 @@ describe(`link creation tests`, () => {
|
|||||||
test(`createCreateFileUrl happy path`, async () => {
|
test(`createCreateFileUrl happy path`, async () => {
|
||||||
const code = `extrusionDistance = 12`
|
const code = `extrusionDistance = 12`
|
||||||
const name = `test`
|
const name = `test`
|
||||||
const units = `mm`
|
|
||||||
|
|
||||||
// Converted with external online tools
|
// Converted with external online tools
|
||||||
const expectedEncodedCode = `ZXh0cnVzaW9uRGlzdGFuY2UgPSAxMg%3D%3D`
|
const expectedEncodedCode = `ZXh0cnVzaW9uRGlzdGFuY2UgPSAxMg%3D%3D`
|
||||||
const expectedLink = `${VITE_KC_SITE_APP_URL}/?create-file=true&name=test&units=mm&code=${expectedEncodedCode}&ask-open-desktop=true`
|
const expectedLink = `${VITE_KC_SITE_APP_URL}/?create-file=true&name=test&code=${expectedEncodedCode}&ask-open-desktop=true`
|
||||||
|
|
||||||
const result = createCreateFileUrl({ code, name, units })
|
const result = createCreateFileUrl({ code, name })
|
||||||
expect(result.toString()).toBe(expectedLink)
|
expect(result.toString()).toBe(expectedLink)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { UnitLength_type } from '@kittycad/lib/dist/types/src/models'
|
|
||||||
import { ASK_TO_OPEN_QUERY_PARAM, CREATE_FILE_URL_PARAM } from './constants'
|
import { ASK_TO_OPEN_QUERY_PARAM, CREATE_FILE_URL_PARAM } from './constants'
|
||||||
import { stringToBase64 } from './base64'
|
import { stringToBase64 } from './base64'
|
||||||
import { VITE_KC_API_BASE_URL, VITE_KC_SITE_APP_URL } from 'env'
|
import { VITE_KC_API_BASE_URL, VITE_KC_SITE_APP_URL } from 'env'
|
||||||
@ -7,7 +6,6 @@ import { err } from './trap'
|
|||||||
export interface FileLinkParams {
|
export interface FileLinkParams {
|
||||||
code: string
|
code: string
|
||||||
name: string
|
name: string
|
||||||
units: UnitLength_type
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function copyFileShareLink(
|
export async function copyFileShareLink(
|
||||||
@ -46,12 +44,11 @@ export async function copyFileShareLink(
|
|||||||
* With the additional step of asking the user if they want to
|
* With the additional step of asking the user if they want to
|
||||||
* open the URL in the desktop app.
|
* open the URL in the desktop app.
|
||||||
*/
|
*/
|
||||||
export function createCreateFileUrl({ code, name, units }: FileLinkParams) {
|
export function createCreateFileUrl({ code, name }: FileLinkParams) {
|
||||||
let origin = VITE_KC_SITE_APP_URL
|
let origin = VITE_KC_SITE_APP_URL
|
||||||
const searchParams = new URLSearchParams({
|
const searchParams = new URLSearchParams({
|
||||||
[CREATE_FILE_URL_PARAM]: String(true),
|
[CREATE_FILE_URL_PARAM]: String(true),
|
||||||
name,
|
name,
|
||||||
units,
|
|
||||||
code: stringToBase64(code),
|
code: stringToBase64(code),
|
||||||
[ASK_TO_OPEN_QUERY_PARAM]: String(true),
|
[ASK_TO_OPEN_QUERY_PARAM]: String(true),
|
||||||
})
|
})
|
||||||
|
@ -819,8 +819,8 @@ export async function sendSelectEventToEngine(
|
|||||||
clientX: e.clientX,
|
clientX: e.clientX,
|
||||||
clientY: e.clientY,
|
clientY: e.clientY,
|
||||||
el,
|
el,
|
||||||
streamWidth: el.clientWidth,
|
streamWidth: engineCommandManager.width,
|
||||||
streamHeight: el.clientHeight,
|
streamHeight: engineCommandManager.height,
|
||||||
})
|
})
|
||||||
const res = await engineCommandManager.sendSceneCommand({
|
const res = await engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
|
@ -4,6 +4,10 @@ import { AtLeast, PathValue, Paths } from 'lib/types'
|
|||||||
import { CommandArgumentConfig } from 'lib/commandTypes'
|
import { CommandArgumentConfig } from 'lib/commandTypes'
|
||||||
import { Themes } from 'lib/theme'
|
import { Themes } from 'lib/theme'
|
||||||
import { CameraProjectionType } from 'wasm-lib/kcl/bindings/CameraProjectionType'
|
import { CameraProjectionType } from 'wasm-lib/kcl/bindings/CameraProjectionType'
|
||||||
|
import {
|
||||||
|
UnitAngle_type,
|
||||||
|
UnitLength_type,
|
||||||
|
} from '@kittycad/lib/dist/types/src/models'
|
||||||
import { CameraOrbitType } from 'wasm-lib/kcl/bindings/CameraOrbitType'
|
import { CameraOrbitType } from 'wasm-lib/kcl/bindings/CameraOrbitType'
|
||||||
|
|
||||||
export interface SettingsViaQueryString {
|
export interface SettingsViaQueryString {
|
||||||
@ -138,3 +142,12 @@ type RecursiveSettingsPayloads<T> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type SaveSettingsPayload = RecursiveSettingsPayloads<typeof settings>
|
export type SaveSettingsPayload = RecursiveSettingsPayloads<typeof settings>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Annotation names for default units are defined on rust side in
|
||||||
|
* src/wasm-lib/kcl/src/execution/annotations.rs
|
||||||
|
*/
|
||||||
|
export interface KclSettingsAnnotation {
|
||||||
|
defaultLengthUnit?: UnitLength_type
|
||||||
|
defaultAngleUnit?: UnitAngle_type
|
||||||
|
}
|
||||||
|
@ -290,9 +290,15 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
|||||||
],
|
],
|
||||||
{
|
{
|
||||||
id: 'helix',
|
id: 'helix',
|
||||||
onClick: () => console.error('Helix not yet implemented'),
|
onClick: () => {
|
||||||
|
commandBarActor.send({
|
||||||
|
type: 'Find and select command',
|
||||||
|
data: { name: 'Helix', groupId: 'modeling' },
|
||||||
|
})
|
||||||
|
},
|
||||||
|
hotkey: 'H',
|
||||||
icon: 'helix',
|
icon: 'helix',
|
||||||
status: 'kcl-only',
|
status: DEV || IS_NIGHTLY_OR_DEBUG ? 'available' : 'kcl-only',
|
||||||
title: 'Helix',
|
title: 'Helix',
|
||||||
description: 'Create a helix or spiral in 3D about an axis.',
|
description: 'Create a helix or spiral in 3D about an axis.',
|
||||||
links: [{ label: 'KCL docs', url: 'https://zoo.dev/docs/kcl/helix' }],
|
links: [{ label: 'KCL docs', url: 'https://zoo.dev/docs/kcl/helix' }],
|
||||||
|
@ -9,6 +9,8 @@ import {
|
|||||||
getCalculatedKclExpressionValue,
|
getCalculatedKclExpressionValue,
|
||||||
programMemoryFromVariables,
|
programMemoryFromVariables,
|
||||||
} from './kclHelpers'
|
} from './kclHelpers'
|
||||||
|
import { parse, resultIsOk } from 'lang/wasm'
|
||||||
|
import { err } from 'lib/trap'
|
||||||
|
|
||||||
const isValidVariableName = (name: string) =>
|
const isValidVariableName = (name: string) =>
|
||||||
/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)
|
/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)
|
||||||
@ -50,7 +52,20 @@ export function useCalculateKclExpression({
|
|||||||
bodyPath: [],
|
bodyPath: [],
|
||||||
})
|
})
|
||||||
const [valueNode, setValueNode] = useState<Expr | null>(null)
|
const [valueNode, setValueNode] = useState<Expr | null>(null)
|
||||||
const [calcResult, setCalcResult] = useState('NAN')
|
// Gotcha: If we do not attempt to parse numeric literals instantly it means that there is an async action to verify
|
||||||
|
// the value is good. This means all E2E tests have a race condition on when they can hit "next" in the command bar.
|
||||||
|
// Most scenarios automatically pass a numeric literal. We can try to parse that first, otherwise make it go through the slow
|
||||||
|
// async method.
|
||||||
|
// If we pass in numeric literals, we should instantly parse them, they have nothing to do with application memory
|
||||||
|
const _code_value = `const __result__ = ${value}`
|
||||||
|
const codeValueParseResult = parse(_code_value)
|
||||||
|
let isValueParsable = true
|
||||||
|
if (err(codeValueParseResult) || !resultIsOk(codeValueParseResult)) {
|
||||||
|
isValueParsable = false
|
||||||
|
}
|
||||||
|
const initialCalcResult: number | string =
|
||||||
|
Number.isNaN(Number(value)) || !isValueParsable ? 'NAN' : value
|
||||||
|
const [calcResult, setCalcResult] = useState(initialCalcResult)
|
||||||
const [newVariableName, setNewVariableName] = useState('')
|
const [newVariableName, setNewVariableName] = useState('')
|
||||||
const [isNewVariableNameUnique, setIsNewVariableNameUnique] = useState(true)
|
const [isNewVariableNameUnique, setIsNewVariableNameUnique] = useState(true)
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ import {
|
|||||||
default_project_settings as DefaultProjectSettings,
|
default_project_settings as DefaultProjectSettings,
|
||||||
base64_decode as Base64Decode,
|
base64_decode as Base64Decode,
|
||||||
clear_scene_and_bust_cache as ClearSceneAndBustCache,
|
clear_scene_and_bust_cache as ClearSceneAndBustCache,
|
||||||
|
kcl_settings as KclSettings,
|
||||||
change_kcl_settings as ChangeKclSettings,
|
change_kcl_settings as ChangeKclSettings,
|
||||||
} from '../wasm-lib/pkg/wasm_lib'
|
} from '../wasm-lib/pkg/wasm_lib'
|
||||||
|
|
||||||
@ -111,6 +112,9 @@ export const clear_scene_and_bust_cache: typeof ClearSceneAndBustCache = (
|
|||||||
) => {
|
) => {
|
||||||
return getModule().clear_scene_and_bust_cache(...args)
|
return getModule().clear_scene_and_bust_cache(...args)
|
||||||
}
|
}
|
||||||
|
export const kcl_settings: typeof KclSettings = (...args) => {
|
||||||
|
return getModule().kcl_settings(...args)
|
||||||
|
}
|
||||||
export const change_kcl_settings: typeof ChangeKclSettings = (...args) => {
|
export const change_kcl_settings: typeof ChangeKclSettings = (...args) => {
|
||||||
return getModule().change_kcl_settings(...args)
|
return getModule().change_kcl_settings(...args)
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,7 @@ import {
|
|||||||
} from 'components/Toolbar/EqualLength'
|
} from 'components/Toolbar/EqualLength'
|
||||||
import { revolveSketch } from 'lang/modifyAst/addRevolve'
|
import { revolveSketch } from 'lang/modifyAst/addRevolve'
|
||||||
import {
|
import {
|
||||||
|
addHelix,
|
||||||
addOffsetPlane,
|
addOffsetPlane,
|
||||||
addSweep,
|
addSweep,
|
||||||
deleteFromSelection,
|
deleteFromSelection,
|
||||||
@ -276,6 +277,7 @@ export type ModelingMachineEvent =
|
|||||||
| { type: 'Fillet'; data?: ModelingCommandSchema['Fillet'] }
|
| { type: 'Fillet'; data?: ModelingCommandSchema['Fillet'] }
|
||||||
| { type: 'Chamfer'; data?: ModelingCommandSchema['Chamfer'] }
|
| { type: 'Chamfer'; data?: ModelingCommandSchema['Chamfer'] }
|
||||||
| { type: 'Offset plane'; data: ModelingCommandSchema['Offset plane'] }
|
| { type: 'Offset plane'; data: ModelingCommandSchema['Offset plane'] }
|
||||||
|
| { type: 'Helix'; data: ModelingCommandSchema['Helix'] }
|
||||||
| { type: 'Text-to-CAD'; data: ModelingCommandSchema['Text-to-CAD'] }
|
| { type: 'Text-to-CAD'; data: ModelingCommandSchema['Text-to-CAD'] }
|
||||||
| { type: 'Prompt-to-edit'; data: ModelingCommandSchema['Prompt-to-edit'] }
|
| { type: 'Prompt-to-edit'; data: ModelingCommandSchema['Prompt-to-edit'] }
|
||||||
| {
|
| {
|
||||||
@ -1615,6 +1617,73 @@ export const modelingMachine = setup({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
helixAstMod: fromPromise(
|
||||||
|
async ({
|
||||||
|
input,
|
||||||
|
}: {
|
||||||
|
input: ModelingCommandSchema['Helix'] | undefined
|
||||||
|
}) => {
|
||||||
|
if (!input) return new Error('No input provided')
|
||||||
|
// Extract inputs
|
||||||
|
const ast = kclManager.ast
|
||||||
|
const {
|
||||||
|
revolutions,
|
||||||
|
angleStart,
|
||||||
|
counterClockWise,
|
||||||
|
radius,
|
||||||
|
axis,
|
||||||
|
length,
|
||||||
|
} = input
|
||||||
|
|
||||||
|
for (const variable of [revolutions, angleStart, radius, length]) {
|
||||||
|
// Insert the variable if it exists
|
||||||
|
if (
|
||||||
|
'variableName' in variable &&
|
||||||
|
variable.variableName &&
|
||||||
|
variable.insertIndex !== undefined
|
||||||
|
) {
|
||||||
|
const newBody = [...ast.body]
|
||||||
|
newBody.splice(
|
||||||
|
variable.insertIndex,
|
||||||
|
0,
|
||||||
|
variable.variableDeclarationAst
|
||||||
|
)
|
||||||
|
ast.body = newBody
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const valueOrVariable = (variable: KclCommandValue) =>
|
||||||
|
'variableName' in variable
|
||||||
|
? variable.variableIdentifierAst
|
||||||
|
: variable.valueAst
|
||||||
|
|
||||||
|
const result = addHelix({
|
||||||
|
node: ast,
|
||||||
|
revolutions: valueOrVariable(revolutions),
|
||||||
|
angleStart: valueOrVariable(angleStart),
|
||||||
|
counterClockWise,
|
||||||
|
radius: valueOrVariable(radius),
|
||||||
|
axis,
|
||||||
|
length: valueOrVariable(length),
|
||||||
|
})
|
||||||
|
|
||||||
|
const updateAstResult = await kclManager.updateAst(
|
||||||
|
result.modifiedAst,
|
||||||
|
true,
|
||||||
|
{
|
||||||
|
focusPath: [result.pathToNode],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
await codeManager.updateEditorWithAstAndWriteToFile(
|
||||||
|
updateAstResult.newAst
|
||||||
|
)
|
||||||
|
|
||||||
|
if (updateAstResult?.selections) {
|
||||||
|
editorManager.selectRange(updateAstResult?.selections)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
sweepAstMod: fromPromise(
|
sweepAstMod: fromPromise(
|
||||||
async ({
|
async ({
|
||||||
input,
|
input,
|
||||||
@ -1974,6 +2043,11 @@ export const modelingMachine = setup({
|
|||||||
reenter: true,
|
reenter: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Helix: {
|
||||||
|
target: 'Applying helix',
|
||||||
|
reenter: true,
|
||||||
|
},
|
||||||
|
|
||||||
'Prompt-to-edit': 'Applying Prompt-to-edit',
|
'Prompt-to-edit': 'Applying Prompt-to-edit',
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -2734,6 +2808,19 @@ export const modelingMachine = setup({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'Applying helix': {
|
||||||
|
invoke: {
|
||||||
|
src: 'helixAstMod',
|
||||||
|
id: 'helixAstMod',
|
||||||
|
input: ({ event }) => {
|
||||||
|
if (event.type !== 'Helix') return undefined
|
||||||
|
return event.data
|
||||||
|
},
|
||||||
|
onDone: ['idle'],
|
||||||
|
onError: ['idle'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
'Applying sweep': {
|
'Applying sweep': {
|
||||||
invoke: {
|
invoke: {
|
||||||
src: 'sweepAstMod',
|
src: 'sweepAstMod',
|
||||||
|
@ -306,7 +306,6 @@ export const projectsMachine = setup({
|
|||||||
return {
|
return {
|
||||||
code: '',
|
code: '',
|
||||||
name: '',
|
name: '',
|
||||||
units: 'mm',
|
|
||||||
method: 'existingProject',
|
method: 'existingProject',
|
||||||
projects: context.projects,
|
projects: context.projects,
|
||||||
}
|
}
|
||||||
@ -314,7 +313,6 @@ export const projectsMachine = setup({
|
|||||||
return {
|
return {
|
||||||
code: event.data.code || '',
|
code: event.data.code || '',
|
||||||
name: event.data.name,
|
name: event.data.name,
|
||||||
units: event.data.units,
|
|
||||||
method: event.data.method,
|
method: event.data.method,
|
||||||
projectName: event.data.projectName,
|
projectName: event.data.projectName,
|
||||||
projects: context.projects,
|
projects: context.projects,
|
||||||
|
14
src/main.ts
@ -40,7 +40,6 @@ dotenv.config({ path: [`.env.${NODE_ENV}.local`, `.env.${NODE_ENV}`] })
|
|||||||
|
|
||||||
// default vite values based on mode
|
// default vite values based on mode
|
||||||
process.env.NODE_ENV ??= viteEnv.MODE
|
process.env.NODE_ENV ??= viteEnv.MODE
|
||||||
process.env.DEV ??= viteEnv.DEV + ''
|
|
||||||
process.env.BASE_URL ??= viteEnv.VITE_KC_API_BASE_URL
|
process.env.BASE_URL ??= viteEnv.VITE_KC_API_BASE_URL
|
||||||
process.env.VITE_KC_API_WS_MODELING_URL ??= viteEnv.VITE_KC_API_WS_MODELING_URL
|
process.env.VITE_KC_API_WS_MODELING_URL ??= viteEnv.VITE_KC_API_WS_MODELING_URL
|
||||||
process.env.VITE_KC_API_BASE_URL ??= viteEnv.VITE_KC_API_BASE_URL
|
process.env.VITE_KC_API_BASE_URL ??= viteEnv.VITE_KC_API_BASE_URL
|
||||||
@ -94,12 +93,11 @@ const createWindow = (pathToOpen?: string, reuse?: boolean): BrowserWindow => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Deep Link: Case of a cold start from Windows or Linux
|
// Deep Link: Case of a cold start from Windows or Linux
|
||||||
if (
|
const zooProtocolArg = process.argv.find((a) =>
|
||||||
!pathToOpen &&
|
a.startsWith(ZOO_STUDIO_PROTOCOL + '://')
|
||||||
process.argv.length > 1 &&
|
)
|
||||||
process.argv[1].indexOf(ZOO_STUDIO_PROTOCOL + '://') > -1
|
if (!pathToOpen && zooProtocolArg) {
|
||||||
) {
|
pathToOpen = zooProtocolArg
|
||||||
pathToOpen = process.argv[1]
|
|
||||||
console.log('Retrieved deep link from argv', pathToOpen)
|
console.log('Retrieved deep link from argv', pathToOpen)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -329,6 +327,7 @@ ipcMain.handle('kittycad', (event, data) => {
|
|||||||
)(data.args)
|
)(data.args)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Used to find other devices on the local network, e.g. 3D printers, CNC machines, etc.
|
||||||
ipcMain.handle('find_machine_api', () => {
|
ipcMain.handle('find_machine_api', () => {
|
||||||
const timeoutAfterMs = 5000
|
const timeoutAfterMs = 5000
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@ -339,7 +338,6 @@ ipcMain.handle('find_machine_api', () => {
|
|||||||
console.error(error)
|
console.error(error)
|
||||||
resolve(null)
|
resolve(null)
|
||||||
})
|
})
|
||||||
console.log('Looking for machine API...')
|
|
||||||
bonjourEt.find(
|
bonjourEt.find(
|
||||||
{ protocol: 'tcp', type: 'machine-api' },
|
{ protocol: 'tcp', type: 'machine-api' },
|
||||||
(service: Service) => {
|
(service: Service) => {
|
||||||
|
@ -26,7 +26,7 @@ export default function Units() {
|
|||||||
<div className="fixed inset-0 z-50 grid items-end justify-start px-4 pointer-events-none">
|
<div className="fixed inset-0 z-50 grid items-end justify-start px-4 pointer-events-none">
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
'pointer-events-auto max-w-2xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded'
|
'relative pointer-events-auto max-w-2xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<SettingsSection
|
<SettingsSection
|
||||||
@ -72,9 +72,7 @@ export default function Units() {
|
|||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
<OnboardingButtons
|
<OnboardingButtons
|
||||||
currentSlug={onboardingPaths.CAMERA}
|
currentSlug={onboardingPaths.CAMERA}
|
||||||
dismiss={dismiss}
|
dismissClassName="right-auto left-full"
|
||||||
next={next}
|
|
||||||
nextText="Next: Streaming"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,19 +1,17 @@
|
|||||||
import usePlatform from 'hooks/usePlatform'
|
import usePlatform from 'hooks/usePlatform'
|
||||||
import { OnboardingButtons, kbdClasses, useDismiss, useNextClick } from '.'
|
import { OnboardingButtons, kbdClasses } from '.'
|
||||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||||
import { hotkeyDisplay } from 'lib/hotkeyWrapper'
|
import { hotkeyDisplay } from 'lib/hotkeyWrapper'
|
||||||
import { COMMAND_PALETTE_HOTKEY } from 'components/CommandBar/CommandBar'
|
import { COMMAND_PALETTE_HOTKEY } from 'components/CommandBar/CommandBar'
|
||||||
|
|
||||||
export default function CmdK() {
|
export default function CmdK() {
|
||||||
const dismiss = useDismiss()
|
|
||||||
const next = useNextClick(onboardingPaths.USER_MENU)
|
|
||||||
const platformName = usePlatform()
|
const platformName = usePlatform()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 grid items-end justify-center pointer-events-none">
|
<div className="fixed inset-0 z-50 grid items-end justify-center pointer-events-none">
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
'pointer-events-auto max-w-full xl:max-w-4xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded'
|
'relative pointer-events-auto max-w-full xl:max-w-4xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<h2 className="text-2xl font-bold">Command Bar</h2>
|
<h2 className="text-2xl font-bold">Command Bar</h2>
|
||||||
@ -38,12 +36,7 @@ export default function CmdK() {
|
|||||||
. You can control settings, authentication, and file management from
|
. You can control settings, authentication, and file management from
|
||||||
the command bar, as well as a growing number of modeling commands.
|
the command bar, as well as a growing number of modeling commands.
|
||||||
</p>
|
</p>
|
||||||
<OnboardingButtons
|
<OnboardingButtons currentSlug={onboardingPaths.COMMAND_K} />
|
||||||
currentSlug={onboardingPaths.COMMAND_K}
|
|
||||||
dismiss={dismiss}
|
|
||||||
next={next}
|
|
||||||
nextText="Next: User Menu"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -1,22 +1,14 @@
|
|||||||
import {
|
import { kbdClasses, OnboardingButtons, useDemoCode } from '.'
|
||||||
kbdClasses,
|
|
||||||
OnboardingButtons,
|
|
||||||
useDemoCode,
|
|
||||||
useDismiss,
|
|
||||||
useNextClick,
|
|
||||||
} from '.'
|
|
||||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||||
|
|
||||||
export default function OnboardingCodeEditor() {
|
export default function OnboardingCodeEditor() {
|
||||||
useDemoCode()
|
useDemoCode()
|
||||||
const dismiss = useDismiss()
|
|
||||||
const next = useNextClick(onboardingPaths.PARAMETRIC_MODELING)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed grid justify-end items-center inset-0 z-50 pointer-events-none">
|
<div className="fixed grid justify-end items-center inset-0 z-50 pointer-events-none">
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
'pointer-events-auto z-10 max-w-xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg h-[75vh] flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded'
|
'relative pointer-events-auto z-10 max-w-xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg h-[75vh] flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<section className="flex-1 overflow-y-auto">
|
<section className="flex-1 overflow-y-auto">
|
||||||
@ -73,12 +65,7 @@ export default function OnboardingCodeEditor() {
|
|||||||
pressing <kbd className={kbdClasses}>Shift + C</kbd>.
|
pressing <kbd className={kbdClasses}>Shift + C</kbd>.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
<OnboardingButtons
|
<OnboardingButtons currentSlug={onboardingPaths.EDITOR} />
|
||||||
currentSlug={onboardingPaths.EDITOR}
|
|
||||||
dismiss={dismiss}
|
|
||||||
next={next}
|
|
||||||
nextText="Next: Parametric Modeling"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -1,16 +1,13 @@
|
|||||||
import { APP_NAME } from 'lib/constants'
|
import { APP_NAME } from 'lib/constants'
|
||||||
import { OnboardingButtons, useDismiss, useNextClick } from '.'
|
import { OnboardingButtons } from '.'
|
||||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||||
|
|
||||||
export default function Export() {
|
export default function Export() {
|
||||||
const dismiss = useDismiss()
|
|
||||||
const next = useNextClick(onboardingPaths.SKETCHING)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed grid justify-center items-end inset-0 z-50 pointer-events-none">
|
<div className="fixed grid justify-center items-end inset-0 z-50 pointer-events-none">
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
'pointer-events-auto max-w-full xl:max-w-2xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded'
|
'relative pointer-events-auto max-w-full xl:max-w-2xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<section className="flex-1">
|
<section className="flex-1">
|
||||||
@ -52,12 +49,7 @@ export default function Export() {
|
|||||||
!
|
!
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
<OnboardingButtons
|
<OnboardingButtons currentSlug={onboardingPaths.EXPORT} />
|
||||||
currentSlug={onboardingPaths.EXPORT}
|
|
||||||
next={next}
|
|
||||||
dismiss={dismiss}
|
|
||||||
nextText="Next: Sketching"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { OnboardingButtons, useDemoCode, useDismiss } from '.'
|
import { OnboardingButtons, useDemoCode } from '.'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { useModelingContext } from 'hooks/useModelingContext'
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
import { APP_NAME } from 'lib/constants'
|
import { APP_NAME } from 'lib/constants'
|
||||||
@ -7,7 +7,6 @@ import { sceneInfra } from 'lib/singletons'
|
|||||||
|
|
||||||
export default function FutureWork() {
|
export default function FutureWork() {
|
||||||
const { send } = useModelingContext()
|
const { send } = useModelingContext()
|
||||||
const dismiss = useDismiss()
|
|
||||||
|
|
||||||
// Reset the code, the camera, and the modeling state
|
// Reset the code, the camera, and the modeling state
|
||||||
useDemoCode()
|
useDemoCode()
|
||||||
@ -19,7 +18,7 @@ export default function FutureWork() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed grid justify-center items-center inset-0 bg-chalkboard-100/50 z-50">
|
<div className="fixed grid justify-center items-center inset-0 bg-chalkboard-100/50 z-50">
|
||||||
<div className="max-w-full xl:max-w-2xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded">
|
<div className="relative max-w-full xl:max-w-2xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded">
|
||||||
<h1 className="text-2xl font-bold">Future Work</h1>
|
<h1 className="text-2xl font-bold">Future Work</h1>
|
||||||
<p className="my-4">
|
<p className="my-4">
|
||||||
We have curves, cuts, multi-profile sketch mode, and many more CAD
|
We have curves, cuts, multi-profile sketch mode, and many more CAD
|
||||||
@ -59,9 +58,6 @@ export default function FutureWork() {
|
|||||||
<OnboardingButtons
|
<OnboardingButtons
|
||||||
currentSlug={onboardingPaths.FUTURE_WORK}
|
currentSlug={onboardingPaths.FUTURE_WORK}
|
||||||
className="mt-6"
|
className="mt-6"
|
||||||
dismiss={dismiss}
|
|
||||||
next={dismiss}
|
|
||||||
nextText="Finish"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,23 +1,15 @@
|
|||||||
import {
|
import { OnboardingButtons, kbdClasses, useDemoCode } from '.'
|
||||||
OnboardingButtons,
|
|
||||||
kbdClasses,
|
|
||||||
useDemoCode,
|
|
||||||
useDismiss,
|
|
||||||
useNextClick,
|
|
||||||
} from '.'
|
|
||||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||||
import { bracketWidthConstantLine } from 'lib/exampleKcl'
|
import { bracketWidthConstantLine } from 'lib/exampleKcl'
|
||||||
|
|
||||||
export default function OnboardingInteractiveNumbers() {
|
export default function OnboardingInteractiveNumbers() {
|
||||||
useDemoCode()
|
useDemoCode()
|
||||||
const dismiss = useDismiss()
|
|
||||||
const next = useNextClick(onboardingPaths.COMMAND_K)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed grid justify-end items-center inset-0 z-50 pointer-events-none">
|
<div className="fixed grid justify-end items-center inset-0 z-50 pointer-events-none">
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
'pointer-events-auto z-10 max-w-xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg h-[75vh] flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded'
|
'relative pointer-events-auto z-10 max-w-xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg h-[75vh] flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<section className="flex-1 overflow-y-auto mb-6">
|
<section className="flex-1 overflow-y-auto mb-6">
|
||||||
@ -88,12 +80,7 @@ export default function OnboardingInteractiveNumbers() {
|
|||||||
your ideas for how to make it better.
|
your ideas for how to make it better.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
<OnboardingButtons
|
<OnboardingButtons currentSlug={onboardingPaths.INTERACTIVE_NUMBERS} />
|
||||||
currentSlug={onboardingPaths.INTERACTIVE_NUMBERS}
|
|
||||||
dismiss={dismiss}
|
|
||||||
next={next}
|
|
||||||
nextText="Next: Command Bar"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { OnboardingButtons, useDemoCode, useDismiss, useNextClick } from '.'
|
import { OnboardingButtons, useDemoCode } from '.'
|
||||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
import { Themes, getSystemTheme } from 'lib/theme'
|
import { Themes, getSystemTheme } from 'lib/theme'
|
||||||
@ -8,13 +8,12 @@ import { isDesktop } from 'lib/isDesktop'
|
|||||||
import { useNavigate, useRouteLoaderData } from 'react-router-dom'
|
import { useNavigate, useRouteLoaderData } from 'react-router-dom'
|
||||||
import { codeManager, kclManager } from 'lib/singletons'
|
import { codeManager, kclManager } from 'lib/singletons'
|
||||||
import { APP_NAME } from 'lib/constants'
|
import { APP_NAME } from 'lib/constants'
|
||||||
import { useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { IndexLoaderData } from 'lib/types'
|
import { IndexLoaderData } from 'lib/types'
|
||||||
import { PATHS } from 'lib/paths'
|
import { PATHS } from 'lib/paths'
|
||||||
import { useFileContext } from 'hooks/useFileContext'
|
import { useFileContext } from 'hooks/useFileContext'
|
||||||
import { useLspContext } from 'components/LspProvider'
|
import { useLspContext } from 'components/LspProvider'
|
||||||
import { reportRejection } from 'lib/trap'
|
import { reportRejection } from 'lib/trap'
|
||||||
import { toSync } from 'lib/utils'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show either a welcome screen or a warning screen
|
* Show either a welcome screen or a warning screen
|
||||||
@ -39,7 +38,7 @@ interface OnboardingResetWarningProps {
|
|||||||
function OnboardingResetWarning(props: OnboardingResetWarningProps) {
|
function OnboardingResetWarning(props: OnboardingResetWarningProps) {
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 grid place-content-center bg-chalkboard-110/50">
|
<div className="fixed inset-0 z-50 grid place-content-center bg-chalkboard-110/50">
|
||||||
<div className="max-w-3xl p-8 rounded bg-chalkboard-10 dark:bg-chalkboard-90">
|
<div className="relative max-w-3xl p-8 rounded bg-chalkboard-10 dark:bg-chalkboard-90">
|
||||||
{!isDesktop() ? (
|
{!isDesktop() ? (
|
||||||
<OnboardingWarningWeb {...props} />
|
<OnboardingWarningWeb {...props} />
|
||||||
) : (
|
) : (
|
||||||
@ -52,7 +51,6 @@ function OnboardingResetWarning(props: OnboardingResetWarningProps) {
|
|||||||
|
|
||||||
function OnboardingWarningDesktop(props: OnboardingResetWarningProps) {
|
function OnboardingWarningDesktop(props: OnboardingResetWarningProps) {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const dismiss = useDismiss()
|
|
||||||
const loaderData = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
|
const loaderData = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
|
||||||
const { context: fileContext } = useFileContext()
|
const { context: fileContext } = useFileContext()
|
||||||
const { onProjectClose, onProjectOpen } = useLspContext()
|
const { onProjectClose, onProjectOpen } = useLspContext()
|
||||||
@ -81,17 +79,28 @@ function OnboardingWarningDesktop(props: OnboardingResetWarningProps) {
|
|||||||
</section>
|
</section>
|
||||||
<OnboardingButtons
|
<OnboardingButtons
|
||||||
className="mt-6"
|
className="mt-6"
|
||||||
dismiss={dismiss}
|
onNextOverride={() => {
|
||||||
next={toSync(onAccept, reportRejection)}
|
onAccept().catch(reportRejection)
|
||||||
nextText="Make a new project"
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function OnboardingWarningWeb(props: OnboardingResetWarningProps) {
|
function OnboardingWarningWeb(props: OnboardingResetWarningProps) {
|
||||||
const dismiss = useDismiss()
|
useEffect(() => {
|
||||||
|
async function beforeNavigate() {
|
||||||
|
// We do want to update both the state and editor here.
|
||||||
|
codeManager.updateCodeStateEditor(bracket)
|
||||||
|
await codeManager.writeToFile()
|
||||||
|
|
||||||
|
await kclManager.executeCode(true)
|
||||||
|
props.setShouldShowWarning(false)
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
beforeNavigate().catch(reportRejection)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1 className="text-3xl font-bold text-warn-80 dark:text-warn-10">
|
<h1 className="text-3xl font-bold text-warn-80 dark:text-warn-10">
|
||||||
@ -101,19 +110,7 @@ function OnboardingWarningWeb(props: OnboardingResetWarningProps) {
|
|||||||
We see you have some of your own code written in this project. Please
|
We see you have some of your own code written in this project. Please
|
||||||
save it somewhere else before continuing the onboarding.
|
save it somewhere else before continuing the onboarding.
|
||||||
</p>
|
</p>
|
||||||
<OnboardingButtons
|
<OnboardingButtons className="mt-6" />
|
||||||
className="mt-6"
|
|
||||||
dismiss={dismiss}
|
|
||||||
next={toSync(async () => {
|
|
||||||
// We do want to update both the state and editor here.
|
|
||||||
codeManager.updateCodeStateEditor(bracket)
|
|
||||||
await codeManager.writeToFile()
|
|
||||||
|
|
||||||
await kclManager.executeCode(true)
|
|
||||||
props.setShouldShowWarning(false)
|
|
||||||
}, reportRejection)}
|
|
||||||
nextText="Overwrite code and continue"
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -136,12 +133,10 @@ function OnboardingIntroductionInner() {
|
|||||||
(theme.current === Themes.System && getSystemTheme() === Themes.Light)
|
(theme.current === Themes.System && getSystemTheme() === Themes.Light)
|
||||||
? '-dark'
|
? '-dark'
|
||||||
: ''
|
: ''
|
||||||
const dismiss = useDismiss()
|
|
||||||
const next = useNextClick(onboardingPaths.CAMERA)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 grid place-content-center bg-chalkboard-110/50">
|
<div className="fixed inset-0 z-50 grid place-content-center bg-chalkboard-110/50">
|
||||||
<div className="max-w-3xl p-8 rounded bg-chalkboard-10 dark:bg-chalkboard-90">
|
<div className="relative max-w-3xl p-8 rounded bg-chalkboard-10 dark:bg-chalkboard-90">
|
||||||
<h1 className="flex flex-wrap items-center gap-4 text-3xl font-bold">
|
<h1 className="flex flex-wrap items-center gap-4 text-3xl font-bold">
|
||||||
<img
|
<img
|
||||||
src={`${isDesktop() ? '.' : ''}/zma-logomark${getLogoTheme()}.svg`}
|
src={`${isDesktop() ? '.' : ''}/zma-logomark${getLogoTheme()}.svg`}
|
||||||
@ -192,9 +187,6 @@ function OnboardingIntroductionInner() {
|
|||||||
<OnboardingButtons
|
<OnboardingButtons
|
||||||
currentSlug={onboardingPaths.INDEX}
|
currentSlug={onboardingPaths.INDEX}
|
||||||
className="mt-6"
|
className="mt-6"
|
||||||
dismiss={dismiss}
|
|
||||||
next={next}
|
|
||||||
nextText="Mouse Controls"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|