Merge branch 'main' into kurt-multi-profile-again
This commit is contained in:
@ -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
|
||||||
|
File diff suppressed because one or more lines are too long
@ -33,7 +33,14 @@ helix(revolutions: number, angle_start: number, ccw?: bool, radius: number, axis
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
// Create a helix around the Z axis.
|
// Create a helix around the Z axis.
|
||||||
helixPath = helix(angleStart = 0, ccw = true, revolutions = 5, length = 10, radius = 5, axis = 'Z')
|
helixPath = helix(
|
||||||
|
angleStart = 0,
|
||||||
|
ccw = true,
|
||||||
|
revolutions = 5,
|
||||||
|
length = 10,
|
||||||
|
radius = 5,
|
||||||
|
axis = 'Z',
|
||||||
|
)
|
||||||
|
|
||||||
// Create a spring by sweeping around the helix path.
|
// Create a spring by sweeping around the helix path.
|
||||||
springSketch = startSketchOn('YZ')
|
springSketch = startSketchOn('YZ')
|
||||||
@ -49,7 +56,14 @@ helper001 = startSketchOn('XZ')
|
|||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> line(end = [0, 10], tag = $edge001)
|
|> line(end = [0, 10], tag = $edge001)
|
||||||
|
|
||||||
helixPath = helix(angleStart = 0, ccw = true, revolutions = 5, length = 10, radius = 5, axis = edge001)
|
helixPath = helix(
|
||||||
|
angleStart = 0,
|
||||||
|
ccw = true,
|
||||||
|
revolutions = 5,
|
||||||
|
length = 10,
|
||||||
|
radius = 5,
|
||||||
|
axis = edge001,
|
||||||
|
)
|
||||||
|
|
||||||
// Create a spring by sweeping around the helix path.
|
// Create a spring by sweeping around the helix path.
|
||||||
springSketch = startSketchOn('XY')
|
springSketch = startSketchOn('XY')
|
||||||
@ -61,12 +75,19 @@ springSketch = startSketchOn('XY')
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
// Create a helix around a custom axis.
|
// Create a helix around a custom axis.
|
||||||
helixPath = helix(angleStart = 0, ccw = true, revolutions = 5, length = 10, radius = 5, axis = {
|
helixPath = helix(
|
||||||
|
angleStart = 0,
|
||||||
|
ccw = true,
|
||||||
|
revolutions = 5,
|
||||||
|
length = 10,
|
||||||
|
radius = 5,
|
||||||
|
axis = {
|
||||||
custom = {
|
custom = {
|
||||||
axis = [0, 0, 1.0],
|
axis = [0, 0, 1.0],
|
||||||
origin = [0, 0.25, 0]
|
origin = [0, 0.25, 0]
|
||||||
}
|
}
|
||||||
})
|
},
|
||||||
|
)
|
||||||
|
|
||||||
// Create a spring by sweeping around the helix path.
|
// Create a spring by sweeping around the helix path.
|
||||||
springSketch = startSketchOn('XY')
|
springSketch = startSketchOn('XY')
|
||||||
|
File diff suppressed because one or more lines are too long
@ -9,7 +9,7 @@ Repeat a 2-dimensional sketch some number of times along a partial or
|
|||||||
complete circle some specified number of times. Each object may additionally be rotated along the circle, ensuring orentation of the solid with respect to the center of the circle is maintained.
|
complete circle some specified number of times. Each object may additionally be rotated along the circle, ensuring orentation of the solid with respect to the center of the circle is maintained.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
patternCircular2d(data: CircularPattern2dData, sketch_set: SketchSet) -> [Sketch]
|
patternCircular2d(sketch_set: SketchSet, instances: integer, center: [number], arc_degrees: number, rotate_duplicates: bool, use_original?: bool) -> [Sketch]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@ -17,8 +17,12 @@ patternCircular2d(data: CircularPattern2dData, sketch_set: SketchSet) -> [Sketch
|
|||||||
|
|
||||||
| Name | Type | Description | Required |
|
| Name | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `data` | [`CircularPattern2dData`](/docs/kcl/types/CircularPattern2dData) | Data for a circular pattern on a 2D sketch. | Yes |
|
| `sketch_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | Which sketch(es) to pattern | Yes |
|
||||||
| `sketch_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | A sketch or a group of sketches. | Yes |
|
| `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
|
||||||
|
| `center` | `[number]` | The center about which to make the pattern. This is a 2D vector. | Yes |
|
||||||
|
| `arc_degrees` | `number` | The arc angle (in degrees) to place the repetitions. Must be greater than 0. | Yes |
|
||||||
|
| `rotate_duplicates` | `bool` | Whether or not to rotate the duplicates as they are copied. | Yes |
|
||||||
|
| `use_original` | `bool` | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |
|
||||||
|
|
||||||
### Returns
|
### Returns
|
||||||
|
|
||||||
@ -34,12 +38,12 @@ exampleSketch = startSketchOn('XZ')
|
|||||||
|> line(end = [-1, 0])
|
|> line(end = [-1, 0])
|
||||||
|> line(end = [0, -5])
|
|> line(end = [0, -5])
|
||||||
|> close()
|
|> close()
|
||||||
|> patternCircular2d({
|
|> patternCircular2d(
|
||||||
center = [0, 0],
|
center = [0, 0],
|
||||||
instances = 13,
|
instances = 13,
|
||||||
arcDegrees = 360,
|
arcDegrees = 360,
|
||||||
rotateDuplicates = true
|
rotateDuplicates = true,
|
||||||
}, %)
|
)
|
||||||
|
|
||||||
example = extrude(exampleSketch, length = 1)
|
example = extrude(exampleSketch, length = 1)
|
||||||
```
|
```
|
||||||
|
File diff suppressed because one or more lines are too long
@ -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, use_original?: bool) -> [Sketch]
|
patternLinear2d(sketch_set: SketchSet, instances: integer, distance: number, axis: [number], use_original?: bool) -> [Sketch]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@ -17,9 +17,11 @@ patternLinear2d(data: LinearPattern2dData, sketch_set: SketchSet, use_original?:
|
|||||||
|
|
||||||
| Name | Type | Description | Required |
|
| Name | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `data` | [`LinearPattern2dData`](/docs/kcl/types/LinearPattern2dData) | Data for a linear pattern on a 2D sketch. | Yes |
|
| `sketch_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | The sketch(es) to duplicate | Yes |
|
||||||
| `sketch_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | A sketch or a group of sketches. | Yes |
|
| `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
|
||||||
| `use_original` | `bool` | | No |
|
| `distance` | `number` | Distance between each repetition. Also known as 'spacing'. | Yes |
|
||||||
|
| `axis` | `[number]` | The axis of the pattern. A 2D vector. | Yes |
|
||||||
|
| `use_original` | `bool` | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |
|
||||||
|
|
||||||
### Returns
|
### Returns
|
||||||
|
|
||||||
@ -31,11 +33,7 @@ patternLinear2d(data: LinearPattern2dData, sketch_set: SketchSet, use_original?:
|
|||||||
```js
|
```js
|
||||||
exampleSketch = startSketchOn('XZ')
|
exampleSketch = startSketchOn('XZ')
|
||||||
|> circle({ center = [0, 0], radius = 1 }, %)
|
|> circle({ center = [0, 0], radius = 1 }, %)
|
||||||
|> patternLinear2d({
|
|> patternLinear2d(axis = [1, 0], instances = 7, distance = 4)
|
||||||
axis = [1, 0],
|
|
||||||
instances = 7,
|
|
||||||
distance = 4
|
|
||||||
}, %)
|
|
||||||
|
|
||||||
example = extrude(exampleSketch, length = 1)
|
example = extrude(exampleSketch, length = 1)
|
||||||
```
|
```
|
||||||
|
File diff suppressed because one or more lines are too long
26417
docs/kcl/std.json
26417
docs/kcl/std.json
File diff suppressed because it is too large
Load Diff
@ -59,7 +59,14 @@ sweepSketch = startSketchOn('XY')
|
|||||||
|
|
||||||
|
|
||||||
// Create a helix around the Z axis.
|
// Create a helix around the Z axis.
|
||||||
helixPath = helix(angleStart = 0, ccw = true, revolutions = 4, length = 10, radius = 5, axis = 'Z')
|
helixPath = helix(
|
||||||
|
angleStart = 0,
|
||||||
|
ccw = true,
|
||||||
|
revolutions = 4,
|
||||||
|
length = 10,
|
||||||
|
radius = 5,
|
||||||
|
axis = 'Z',
|
||||||
|
)
|
||||||
|
|
||||||
// Create a spring by sweeping around the helix path.
|
// Create a spring by sweeping around the helix path.
|
||||||
springSketch = startSketchOn('YZ')
|
springSketch = startSketchOn('YZ')
|
||||||
|
16
docs/kcl/types/EnvironmentRef.md
Normal file
16
docs/kcl/types/EnvironmentRef.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
title: "EnvironmentRef"
|
||||||
|
excerpt: ""
|
||||||
|
layout: manual
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
[`SnapshotRef`](/docs/kcl/types/SnapshotRef)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -311,7 +311,7 @@ Data for an imported geometry.
|
|||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `type` |enum: `Function`| | No |
|
| `type` |enum: `Function`| | No |
|
||||||
| `memory` |[`ProgramMemory`](/docs/kcl/types/ProgramMemory)| Any KCL value. | No |
|
| `memory` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| Any KCL value. | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||||
|
|
||||||
|
|
||||||
@ -351,6 +351,23 @@ Data for an imported geometry.
|
|||||||
|
|
||||||
----
|
----
|
||||||
|
|
||||||
|
**Type:** `object`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `type` |enum: `Tombstone`| | No |
|
||||||
|
| `value` |`null`| | No |
|
||||||
|
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,6 +17,5 @@ layout: manual
|
|||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `environments` |`[` [`Environment`](/docs/kcl/types/Environment) `]`| | No |
|
| `environments` |`[` [`Environment`](/docs/kcl/types/Environment) `]`| | No |
|
||||||
| `currentEnv` |`integer`| | No |
|
| `currentEnv` |`integer`| | No |
|
||||||
| `return` |[`KclValue`](/docs/kcl/types/KclValue)| | No |
|
|
||||||
|
|
||||||
|
|
||||||
|
16
docs/kcl/types/SnapshotRef.md
Normal file
16
docs/kcl/types/SnapshotRef.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
title: "SnapshotRef"
|
||||||
|
excerpt: "An index pointing to a snapshot within a specific (unspecified) environment."
|
||||||
|
layout: manual
|
||||||
|
---
|
||||||
|
|
||||||
|
An index pointing to a snapshot within a specific (unspecified) environment.
|
||||||
|
|
||||||
|
**Type:** `integer` (`uint`)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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()
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -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)
|
||||||
|
|
||||||
@ -402,6 +405,7 @@ profile001 = startProfileAt([205.96, 254.59], sketch002)
|
|||||||
}, 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)
|
||||||
|
|
||||||
@ -688,6 +692,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,
|
||||||
@ -703,6 +1031,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)
|
||||||
@ -762,13 +1093,14 @@ openSketch = startSketchOn('XY')
|
|||||||
}) => {
|
}) => {
|
||||||
// One dumb hardcoded screen pixel value
|
// One dumb hardcoded screen pixel value
|
||||||
const testPoint = { x: 620, y: 257 }
|
const testPoint = { x: 620, y: 257 }
|
||||||
const expectedOutput = `helix001 = helix(revolutions = 1, angleStart = 360, counterClockWise = false, radius = 5, axis = 'X', length = 5)`
|
const expectedOutput = `helix001 = helix( revolutions = 1, angleStart = 360, counterClockWise = false, radius = 5, axis = 'X', length = 5,)`
|
||||||
|
const expectedLine = `revolutions=1,`
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
await test.step(`Look for the red of the default plane`, async () => {
|
// await test.step(`Look for the red of the default plane`, async () => {
|
||||||
await scene.expectPixelColor([96, 52, 52], testPoint, 15)
|
// await scene.expectPixelColor([96, 52, 52], testPoint, 15)
|
||||||
})
|
// })
|
||||||
await test.step(`Go through the command bar flow`, async () => {
|
await test.step(`Go through the command bar flow`, async () => {
|
||||||
await toolbar.helixButton.click()
|
await toolbar.helixButton.click()
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
@ -799,7 +1131,7 @@ openSketch = startSketchOn('XY')
|
|||||||
await editor.expectEditor.toContain(expectedOutput)
|
await editor.expectEditor.toContain(expectedOutput)
|
||||||
await editor.expectState({
|
await editor.expectState({
|
||||||
diagnostics: [],
|
diagnostics: [],
|
||||||
activeLines: [expectedOutput],
|
activeLines: [expectedLine],
|
||||||
highlightedCode: '',
|
highlightedCode: '',
|
||||||
})
|
})
|
||||||
// Red plane is now gone, white helix is there
|
// Red plane is now gone, white helix is there
|
||||||
@ -928,6 +1260,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 }
|
||||||
@ -1570,16 +1903,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
|
||||||
@ -1804,6 +2128,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(
|
||||||
@ -1926,6 +2251,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()
|
||||||
|
|
||||||
// One dumb hardcoded screen pixel value
|
// One dumb hardcoded screen pixel value
|
||||||
const testPoint = { x: 575, y: 200 }
|
const testPoint = { x: 575, y: 200 }
|
||||||
@ -2024,6 +2350,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 }
|
||||||
|
@ -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')
|
||||||
|
@ -60,6 +60,7 @@ test.describe('Prompt-to-edit tests', { 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 greenCheckCoords = { x: 565, y: 345 }
|
const greenCheckCoords = { x: 565, y: 345 }
|
||||||
@ -76,7 +77,9 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
|
|||||||
'Submitting to Text-to-CAD API...'
|
'Submitting to Text-to-CAD API...'
|
||||||
)
|
)
|
||||||
const successToast = page.getByText('Prompt to edit successful')
|
const successToast = page.getByText('Prompt to edit successful')
|
||||||
const acceptBtn = page.getByRole('button', { name: 'checkmark Accept' })
|
const acceptBtn = page.getByRole('button', {
|
||||||
|
name: 'checkmark Accept',
|
||||||
|
})
|
||||||
const rejectBtn = page.getByRole('button', { name: 'close Reject' })
|
const rejectBtn = page.getByRole('button', { name: 'close Reject' })
|
||||||
|
|
||||||
await test.step('wait for scene to load select body and check selection came through', async () => {
|
await test.step('wait for scene to load select body and check selection came through', async () => {
|
||||||
@ -99,7 +102,9 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
|
|||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await expect(submittingToast).toBeVisible()
|
await expect(submittingToast).toBeVisible()
|
||||||
await expect(submittingToast).not.toBeVisible({ timeout: 2 * 60_000 }) // can take a while
|
await expect(submittingToast).not.toBeVisible({
|
||||||
|
timeout: 2 * 60_000,
|
||||||
|
}) // can take a while
|
||||||
await expect(successToast).toBeVisible()
|
await expect(successToast).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -150,6 +155,7 @@ test.describe('Prompt-to-edit tests', { 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(
|
||||||
|
@ -192,11 +192,11 @@ extrude001 = extrude(sketch001, length = 50)
|
|||||||
|> line(end = [0, -1])
|
|> line(end = [0, -1])
|
||||||
|> close()
|
|> close()
|
||||||
|> extrude(length = 1)
|
|> extrude(length = 1)
|
||||||
|> patternLinear3d({
|
|> patternLinear3d(
|
||||||
axis: [1, 0, 1],
|
axis = [1, 0, 1],
|
||||||
repetitions: 3,
|
repetitions = 3,
|
||||||
distance: 6
|
distance = 6,
|
||||||
}, %)`
|
)`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
@ -313,106 +313,106 @@ extrude001 = extrude(sketch001, length = 50)
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
test('when engine fails export we handle the failure and alert the user', async ({
|
test(
|
||||||
scene,
|
'when engine fails export we handle the failure and alert the user',
|
||||||
page,
|
{ tag: '@skipLocalEngine' },
|
||||||
homePage,
|
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')
|
||||||
|
|
||||||
await scene.waitForExecutionDone()
|
await scene.waitForExecutionDone()
|
||||||
|
|
||||||
// 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'] },
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
TEST_COLORS,
|
TEST_COLORS,
|
||||||
} 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 ({
|
||||||
@ -188,7 +189,8 @@ sketch001 = startProfileAt([12.34, -12.34], sketch002)
|
|||||||
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 () => {
|
||||||
@ -204,6 +206,7 @@ sketch001 = startProfileAt([12.34, -12.34], sketch002)
|
|||||||
|
|
||||||
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' })
|
||||||
@ -321,7 +324,7 @@ sketch001 = startProfileAt([12.34, -12.34], sketch002)
|
|||||||
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(
|
||||||
@ -334,14 +337,14 @@ sketch001 = startProfileAt([12.34, -12.34], sketch002)
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
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(
|
||||||
@ -349,7 +352,7 @@ sketch001 = startProfileAt([12.34, -12.34], sketch002)
|
|||||||
JSON.stringify({ openPanes: [] })
|
JSON.stringify({ openPanes: [] })
|
||||||
)
|
)
|
||||||
}, PERSIST_MODELING_CONTEXT)
|
}, PERSIST_MODELING_CONTEXT)
|
||||||
await doEditSegmentsByDraggingHandle(page, homePage, [])
|
await doEditSegmentsByDraggingHandle(page, homePage, [], scene)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -551,6 +554,7 @@ sketch001 = startProfileAt([12.34, -12.34], sketch002)
|
|||||||
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 () => {
|
||||||
@ -566,6 +570,7 @@ sketch001 = startProfileAt([12.34, -12.34], sketch002)
|
|||||||
})
|
})
|
||||||
|
|
||||||
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' })
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
Binary file not shown.
Before Width: | Height: | Size: 143 KiB After Width: | Height: | Size: 143 KiB |
Binary file not shown.
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 127 KiB |
@ -4,225 +4,226 @@ import { EngineCommand } from 'lang/std/artifactGraph'
|
|||||||
import { uuidv4 } from 'lib/utils'
|
import { uuidv4 } from 'lib/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 ({
|
|
||||||
page,
|
|
||||||
homePage,
|
|
||||||
}) => {
|
|
||||||
// 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 u = await getUtils(page)
|
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
|
||||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
|
||||||
await u.waitForPageLoad()
|
|
||||||
|
|
||||||
await u.openDebugPanel()
|
|
||||||
// click on "Start Sketch" button
|
|
||||||
await u.clearCommandLogs()
|
|
||||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
|
|
||||||
// select a plane
|
|
||||||
await page.mouse.click(700, 200)
|
|
||||||
|
|
||||||
await expect(page.locator('.cm-content')).toHaveText(
|
|
||||||
`sketch001 = startSketchOn('XZ')`
|
|
||||||
)
|
|
||||||
await u.closeDebugPanel()
|
|
||||||
|
|
||||||
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
|
||||||
|
|
||||||
const startXPx = 600
|
|
||||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
|
||||||
await expect(page.locator('.cm-content')).toHaveText(
|
|
||||||
`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${commonPoints.startAt}, sketch001)`
|
|
||||||
)
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
|
|
||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
|
||||||
.toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${commonPoints.startAt}, sketch001)
|
|
||||||
|> xLine(${commonPoints.num1}, %)`)
|
|
||||||
|
|
||||||
// Expect the network to be up
|
|
||||||
await expect(networkToggle).toContainText('Connected')
|
|
||||||
|
|
||||||
// simulate network down
|
|
||||||
await u.emulateNetworkConditions({
|
|
||||||
offline: true,
|
|
||||||
// values of 0 remove any active throttling. crbug.com/456324#c9
|
|
||||||
latency: 0,
|
|
||||||
downloadThroughput: -1,
|
|
||||||
uploadThroughput: -1,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Expect the network to be down
|
|
||||||
await expect(networkToggle).toContainText('Problem')
|
|
||||||
|
|
||||||
// Ensure we are not in sketch mode
|
|
||||||
await expect(
|
|
||||||
page.getByRole('button', { name: 'Exit Sketch' })
|
|
||||||
).not.toBeVisible()
|
|
||||||
await expect(
|
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
|
||||||
).toBeVisible()
|
|
||||||
|
|
||||||
// simulate network up
|
|
||||||
await u.emulateNetworkConditions({
|
|
||||||
offline: false,
|
|
||||||
// values of 0 remove any active throttling. crbug.com/456324#c9
|
|
||||||
latency: 0,
|
|
||||||
downloadThroughput: -1,
|
|
||||||
uploadThroughput: -1,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Wait for the app to be ready for use
|
|
||||||
await expect(
|
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
|
||||||
).not.toBeDisabled({ timeout: 15000 })
|
|
||||||
|
|
||||||
// Expect the network to be up
|
|
||||||
await expect(networkToggle).toContainText('Connected')
|
|
||||||
await expect(page.getByTestId('loading-stream')).not.toBeAttached()
|
|
||||||
|
|
||||||
// Click off the code pane.
|
|
||||||
await page.mouse.click(100, 100)
|
|
||||||
|
|
||||||
// select a line
|
|
||||||
await page
|
|
||||||
.getByText(`startProfileAt(${commonPoints.startAt}, sketch001)`)
|
|
||||||
.click()
|
|
||||||
|
|
||||||
// enter sketch again
|
|
||||||
await u.doAndWaitForCmd(
|
|
||||||
() => page.getByRole('button', { name: 'Edit Sketch' }).click(),
|
|
||||||
'default_camera_get_settings'
|
|
||||||
)
|
|
||||||
await page.waitForTimeout(150)
|
|
||||||
|
|
||||||
// Click the line tool
|
|
||||||
await page.getByRole('button', { name: 'line Line', exact: true }).click()
|
|
||||||
|
|
||||||
await page.waitForTimeout(150)
|
|
||||||
|
|
||||||
const camCommand: EngineCommand = {
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'default_camera_look_at',
|
|
||||||
center: { x: 109, y: 0, z: -152 },
|
|
||||||
vantage: { x: 115, y: -505, z: -152 },
|
|
||||||
up: { x: 0, y: 0, z: 1 },
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
const updateCamCommand: EngineCommand = {
|
)
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'default_camera_get_settings',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
await u.sendCustomCmd(camCommand)
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
await u.sendCustomCmd(updateCamCommand)
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
|
|
||||||
// click to continue profile
|
test(
|
||||||
await page.mouse.click(1007, 400)
|
'Engine disconnect & reconnect in sketch mode',
|
||||||
await page.waitForTimeout(100)
|
{ tag: '@skipLocalEngine' },
|
||||||
// Ensure we can continue sketching
|
async ({ page, homePage }) => {
|
||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
// TODO: Don't skip Mac for these. After `window.tearDown` is working in Safari, these should work on webkit
|
||||||
await expect.poll(u.normalisedEditorCode)
|
const networkToggle = page.getByTestId('network-toggle')
|
||||||
.toBe(`sketch001 = startSketchOn('XZ')
|
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
|
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
|
await u.openDebugPanel()
|
||||||
|
// click on "Start Sketch" button
|
||||||
|
await u.clearCommandLogs()
|
||||||
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
|
// select a plane
|
||||||
|
await page.mouse.click(700, 200)
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
|
`sketch001 = startSketchOn('XZ')`
|
||||||
|
)
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
||||||
|
|
||||||
|
const startXPx = 600
|
||||||
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
|
`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${commonPoints.startAt}, sketch001)`
|
||||||
|
)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content'))
|
||||||
|
.toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${commonPoints.startAt}, sketch001)
|
||||||
|
|> xLine(${commonPoints.num1}, %)`)
|
||||||
|
|
||||||
|
// Expect the network to be up
|
||||||
|
await expect(networkToggle).toContainText('Connected')
|
||||||
|
|
||||||
|
// simulate network down
|
||||||
|
await u.emulateNetworkConditions({
|
||||||
|
offline: true,
|
||||||
|
// values of 0 remove any active throttling. crbug.com/456324#c9
|
||||||
|
latency: 0,
|
||||||
|
downloadThroughput: -1,
|
||||||
|
uploadThroughput: -1,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Expect the network to be down
|
||||||
|
await expect(networkToggle).toContainText('Problem')
|
||||||
|
|
||||||
|
// Ensure we are not in sketch mode
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Exit Sketch' })
|
||||||
|
).not.toBeVisible()
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
|
).toBeVisible()
|
||||||
|
|
||||||
|
// simulate network up
|
||||||
|
await u.emulateNetworkConditions({
|
||||||
|
offline: false,
|
||||||
|
// values of 0 remove any active throttling. crbug.com/456324#c9
|
||||||
|
latency: 0,
|
||||||
|
downloadThroughput: -1,
|
||||||
|
uploadThroughput: -1,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Wait for the app to be ready for use
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
|
).not.toBeDisabled({ timeout: 15000 })
|
||||||
|
|
||||||
|
// Expect the network to be up
|
||||||
|
await expect(networkToggle).toContainText('Connected')
|
||||||
|
await expect(page.getByTestId('loading-stream')).not.toBeAttached()
|
||||||
|
|
||||||
|
// Click off the code pane.
|
||||||
|
await page.mouse.click(100, 100)
|
||||||
|
|
||||||
|
// select a line
|
||||||
|
await page
|
||||||
|
.getByText(`startProfileAt(${commonPoints.startAt}, sketch001)`)
|
||||||
|
.click()
|
||||||
|
|
||||||
|
// enter sketch again
|
||||||
|
await u.doAndWaitForCmd(
|
||||||
|
() => page.getByRole('button', { name: 'Edit Sketch' }).click(),
|
||||||
|
'default_camera_get_settings'
|
||||||
|
)
|
||||||
|
await page.waitForTimeout(150)
|
||||||
|
|
||||||
|
// Click the line tool
|
||||||
|
await page.getByRole('button', { name: 'line Line', exact: true }).click()
|
||||||
|
|
||||||
|
await page.waitForTimeout(150)
|
||||||
|
|
||||||
|
const camCommand: EngineCommand = {
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'default_camera_look_at',
|
||||||
|
center: { x: 109, y: 0, z: -152 },
|
||||||
|
vantage: { x: 115, y: -505, z: -152 },
|
||||||
|
up: { x: 0, y: 0, z: 1 },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const updateCamCommand: EngineCommand = {
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'default_camera_get_settings',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
await u.sendCustomCmd(camCommand)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await u.sendCustomCmd(updateCamCommand)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
|
// click to continue profile
|
||||||
|
await page.mouse.click(1007, 400)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
// Ensure we can continue sketching
|
||||||
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
||||||
|
await expect.poll(u.normalisedEditorCode)
|
||||||
|
.toBe(`sketch001 = startSketchOn('XZ')
|
||||||
profile001 = startProfileAt([12.34, -12.34], sketch001)
|
profile001 = startProfileAt([12.34, -12.34], sketch001)
|
||||||
|> 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')
|
||||||
profile001 = startProfileAt([12.34, -12.34], sketch001)
|
profile001 = startProfileAt([12.34, -12.34], sketch001)
|
||||||
|> xLine(12.34, %)
|
|> xLine(12.34, %)
|
||||||
|> line(end = [-12.34, 12.34])
|
|> line(end = [-12.34, 12.34])
|
||||||
@ -230,20 +231,21 @@ profile001 = startProfileAt([12.34, -12.34], sketch001)
|
|||||||
|
|
||||||
`)
|
`)
|
||||||
|
|
||||||
// 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])
|
||||||
|
@ -249,7 +249,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 () => {
|
||||||
@ -343,10 +347,7 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
|
|||||||
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({
|
||||||
@ -940,6 +941,7 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
|
|||||||
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 = [
|
||||||
{
|
{
|
||||||
@ -975,6 +977,7 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
|
|||||||
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({
|
||||||
@ -1008,6 +1011,7 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
|
|||||||
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(
|
||||||
@ -1026,6 +1030,7 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
|
|||||||
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({
|
||||||
@ -1059,19 +1064,19 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
|
|||||||
.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)
|
||||||
@ -1082,7 +1087,7 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
|
|||||||
).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
|
||||||
@ -1096,11 +1101,11 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
|
|||||||
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)
|
||||||
|
@ -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,
|
||||||
@ -461,7 +465,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 () => {
|
||||||
@ -487,11 +491,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' })
|
||||||
@ -595,10 +595,9 @@ profile001 = startProfileAt([-12.34, 12.34], sketch002)
|
|||||||
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/achalmers/kw-appearance/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-pattern/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"
|
||||||
},
|
},
|
||||||
|
@ -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
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
|
@ -31,7 +31,6 @@ import {
|
|||||||
recast,
|
recast,
|
||||||
defaultSourceRange,
|
defaultSourceRange,
|
||||||
resultIsOk,
|
resultIsOk,
|
||||||
ProgramMemory,
|
|
||||||
topLevelRange,
|
topLevelRange,
|
||||||
} from 'lang/wasm'
|
} from 'lang/wasm'
|
||||||
import { CustomIcon, CustomIconName } from 'components/CustomIcon'
|
import { CustomIcon, CustomIconName } from 'components/CustomIcon'
|
||||||
@ -428,7 +427,7 @@ export async function deleteSegment({
|
|||||||
modifiedAst = deleteSegmentFromPipeExpression(
|
modifiedAst = deleteSegmentFromPipeExpression(
|
||||||
dependentRanges,
|
dependentRanges,
|
||||||
modifiedAst,
|
modifiedAst,
|
||||||
kclManager.programMemory,
|
kclManager.variables,
|
||||||
codeManager.code,
|
codeManager.code,
|
||||||
pathToNode
|
pathToNode
|
||||||
)
|
)
|
||||||
@ -442,8 +441,8 @@ export async function deleteSegment({
|
|||||||
const testExecute = await executeAst({
|
const testExecute = await executeAst({
|
||||||
ast: modifiedAst,
|
ast: modifiedAst,
|
||||||
engineCommandManager: engineCommandManager,
|
engineCommandManager: engineCommandManager,
|
||||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
isMock: true,
|
||||||
programMemoryOverride: ProgramMemory.empty(),
|
usePrevMemory: false,
|
||||||
})
|
})
|
||||||
if (testExecute.errors.length) {
|
if (testExecute.errors.length) {
|
||||||
toast.error('Segment tag used outside of current Sketch. Could not delete.')
|
toast.error('Segment tag used outside of current Sketch. Could not delete.')
|
||||||
@ -680,7 +679,7 @@ const ConstraintSymbol = ({
|
|||||||
shallowPath,
|
shallowPath,
|
||||||
argPosition,
|
argPosition,
|
||||||
kclManager.ast,
|
kclManager.ast,
|
||||||
kclManager.programMemory
|
kclManager.variables
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!transform) return
|
if (!transform) return
|
||||||
|
@ -40,7 +40,6 @@ import {
|
|||||||
PathToNode,
|
PathToNode,
|
||||||
PipeExpression,
|
PipeExpression,
|
||||||
Program,
|
Program,
|
||||||
ProgramMemory,
|
|
||||||
recast,
|
recast,
|
||||||
Sketch,
|
Sketch,
|
||||||
VariableDeclaration,
|
VariableDeclaration,
|
||||||
@ -52,6 +51,7 @@ import {
|
|||||||
SourceRange,
|
SourceRange,
|
||||||
topLevelRange,
|
topLevelRange,
|
||||||
CallExpressionKw,
|
CallExpressionKw,
|
||||||
|
VariableMap,
|
||||||
} from 'lang/wasm'
|
} from 'lang/wasm'
|
||||||
import {
|
import {
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
@ -164,7 +164,6 @@ type Vec3Array = [number, number, number]
|
|||||||
export class SceneEntities {
|
export class SceneEntities {
|
||||||
engineCommandManager: EngineCommandManager
|
engineCommandManager: EngineCommandManager
|
||||||
scene: Scene
|
scene: Scene
|
||||||
sceneProgramMemory: ProgramMemory = ProgramMemory.empty()
|
|
||||||
activeSegments: { [key: string]: Group } = {}
|
activeSegments: { [key: string]: Group } = {}
|
||||||
intersectionPlane: Mesh | null = null
|
intersectionPlane: Mesh | null = null
|
||||||
axisGroup: Group | null = null
|
axisGroup: Group | null = null
|
||||||
@ -576,33 +575,25 @@ export class SceneEntities {
|
|||||||
selectionRanges?: Selections
|
selectionRanges?: Selections
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
truncatedAst: Node<Program>
|
truncatedAst: Node<Program>
|
||||||
programMemoryOverride: ProgramMemory
|
|
||||||
variableDeclarationName: string
|
variableDeclarationName: string
|
||||||
}> {
|
}> {
|
||||||
this.createIntersectionPlane()
|
this.createIntersectionPlane()
|
||||||
|
|
||||||
const prepared = this.prepareTruncatedMemoryAndAst(
|
const prepared = this.prepareTruncatedAst(sketchNodePaths, maybeModdedAst)
|
||||||
sketchNodePaths,
|
|
||||||
maybeModdedAst
|
|
||||||
)
|
|
||||||
if (err(prepared)) return Promise.reject(prepared)
|
if (err(prepared)) return Promise.reject(prepared)
|
||||||
const { truncatedAst, programMemoryOverride, variableDeclarationName } =
|
const { truncatedAst, variableDeclarationName } = prepared
|
||||||
prepared
|
|
||||||
|
|
||||||
const { execState } = await executeAst({
|
const { execState } = await executeAst({
|
||||||
ast: truncatedAst,
|
ast: truncatedAst,
|
||||||
engineCommandManager: this.engineCommandManager,
|
engineCommandManager: this.engineCommandManager,
|
||||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
isMock: true,
|
||||||
programMemoryOverride,
|
|
||||||
})
|
})
|
||||||
const programMemory = execState.memory
|
|
||||||
const sketchesInfo = getSketchesInfo({
|
const sketchesInfo = getSketchesInfo({
|
||||||
sketchNodePaths,
|
sketchNodePaths,
|
||||||
ast: maybeModdedAst,
|
ast: maybeModdedAst,
|
||||||
programMemory,
|
variables: execState.variables,
|
||||||
})
|
})
|
||||||
|
|
||||||
this.sceneProgramMemory = programMemory
|
|
||||||
const group = new Group()
|
const group = new Group()
|
||||||
position && group.position.set(...position)
|
position && group.position.set(...position)
|
||||||
group.userData = {
|
group.userData = {
|
||||||
@ -773,7 +764,6 @@ export class SceneEntities {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
truncatedAst,
|
truncatedAst,
|
||||||
programMemoryOverride,
|
|
||||||
variableDeclarationName,
|
variableDeclarationName,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -829,7 +819,7 @@ export class SceneEntities {
|
|||||||
const variableDeclarationName = _node1.node?.declaration.id?.name || ''
|
const variableDeclarationName = _node1.node?.declaration.id?.name || ''
|
||||||
|
|
||||||
const sg = sketchFromKclValue(
|
const sg = sketchFromKclValue(
|
||||||
kclManager.programMemory.get(variableDeclarationName),
|
kclManager.variables[variableDeclarationName],
|
||||||
variableDeclarationName
|
variableDeclarationName
|
||||||
)
|
)
|
||||||
if (err(sg)) return Promise.reject(sg)
|
if (err(sg)) return Promise.reject(sg)
|
||||||
@ -838,7 +828,7 @@ export class SceneEntities {
|
|||||||
const index = sg.paths.length // because we've added a new segment that's not in the memory yet, no need for `-1`
|
const index = sg.paths.length // because we've added a new segment that's not in the memory yet, no need for `-1`
|
||||||
const mod = addNewSketchLn({
|
const mod = addNewSketchLn({
|
||||||
node: _ast,
|
node: _ast,
|
||||||
programMemory: kclManager.programMemory,
|
variables: kclManager.variables,
|
||||||
input: {
|
input: {
|
||||||
type: 'straight-segment',
|
type: 'straight-segment',
|
||||||
to: lastSeg.to,
|
to: lastSeg.to,
|
||||||
@ -857,7 +847,7 @@ export class SceneEntities {
|
|||||||
if (shouldTearDown) this.tearDownSketch({ removeAxis: false })
|
if (shouldTearDown) this.tearDownSketch({ removeAxis: false })
|
||||||
sceneInfra.resetMouseListeners()
|
sceneInfra.resetMouseListeners()
|
||||||
|
|
||||||
const { truncatedAst, programMemoryOverride } = await this.setupSketch({
|
const { truncatedAst } = await this.setupSketch({
|
||||||
sketchEntryNodePath,
|
sketchEntryNodePath,
|
||||||
sketchNodePaths,
|
sketchNodePaths,
|
||||||
forward,
|
forward,
|
||||||
@ -887,7 +877,7 @@ export class SceneEntities {
|
|||||||
const sketch = sketchFromPathToNode({
|
const sketch = sketchFromPathToNode({
|
||||||
pathToNode: sketchEntryNodePath,
|
pathToNode: sketchEntryNodePath,
|
||||||
ast: kclManager.ast,
|
ast: kclManager.ast,
|
||||||
programMemory: kclManager.programMemory,
|
variables: kclManager.variables,
|
||||||
})
|
})
|
||||||
if (err(sketch)) return Promise.reject(sketch)
|
if (err(sketch)) return Promise.reject(sketch)
|
||||||
if (!sketch) return Promise.reject(new Error('No sketch found'))
|
if (!sketch) return Promise.reject(new Error('No sketch found'))
|
||||||
@ -905,7 +895,7 @@ export class SceneEntities {
|
|||||||
])
|
])
|
||||||
modifiedAst = addCallExpressionsToPipe({
|
modifiedAst = addCallExpressionsToPipe({
|
||||||
node: kclManager.ast,
|
node: kclManager.ast,
|
||||||
programMemory: kclManager.programMemory,
|
variables: kclManager.variables,
|
||||||
pathToNode: sketchEntryNodePath,
|
pathToNode: sketchEntryNodePath,
|
||||||
expressions: [
|
expressions: [
|
||||||
lastSegment.type === 'TangentialArcTo'
|
lastSegment.type === 'TangentialArcTo'
|
||||||
@ -921,7 +911,7 @@ export class SceneEntities {
|
|||||||
if (trap(modifiedAst)) return Promise.reject(modifiedAst)
|
if (trap(modifiedAst)) return Promise.reject(modifiedAst)
|
||||||
modifiedAst = addCloseToPipe({
|
modifiedAst = addCloseToPipe({
|
||||||
node: modifiedAst,
|
node: modifiedAst,
|
||||||
programMemory: kclManager.programMemory,
|
variables: kclManager.variables,
|
||||||
pathToNode: sketchEntryNodePath,
|
pathToNode: sketchEntryNodePath,
|
||||||
})
|
})
|
||||||
if (trap(modifiedAst)) return Promise.reject(modifiedAst)
|
if (trap(modifiedAst)) return Promise.reject(modifiedAst)
|
||||||
@ -975,7 +965,7 @@ export class SceneEntities {
|
|||||||
|
|
||||||
const tmp = addNewSketchLn({
|
const tmp = addNewSketchLn({
|
||||||
node: kclManager.ast,
|
node: kclManager.ast,
|
||||||
programMemory: kclManager.programMemory,
|
variables: kclManager.variables,
|
||||||
input: {
|
input: {
|
||||||
type: 'straight-segment',
|
type: 'straight-segment',
|
||||||
from: [lastSegment.to[0], lastSegment.to[1]],
|
from: [lastSegment.to[0], lastSegment.to[1]],
|
||||||
@ -1030,7 +1020,6 @@ export class SceneEntities {
|
|||||||
planeNodePath,
|
planeNodePath,
|
||||||
draftInfo: {
|
draftInfo: {
|
||||||
truncatedAst,
|
truncatedAst,
|
||||||
programMemoryOverride,
|
|
||||||
variableDeclarationName,
|
variableDeclarationName,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -1111,7 +1100,7 @@ export class SceneEntities {
|
|||||||
return Promise.reject(_recastAst)
|
return Promise.reject(_recastAst)
|
||||||
_ast = _recastAst.program
|
_ast = _recastAst.program
|
||||||
|
|
||||||
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
|
const { truncatedAst } = await this.setupSketch({
|
||||||
sketchEntryNodePath: updatedEntryNodePath,
|
sketchEntryNodePath: updatedEntryNodePath,
|
||||||
sketchNodePaths: updatedSketchNodePaths,
|
sketchNodePaths: updatedSketchNodePaths,
|
||||||
forward,
|
forward,
|
||||||
@ -1150,12 +1139,9 @@ export class SceneEntities {
|
|||||||
const { execState } = await executeAst({
|
const { execState } = await executeAst({
|
||||||
ast: truncatedAst,
|
ast: truncatedAst,
|
||||||
engineCommandManager: this.engineCommandManager,
|
engineCommandManager: this.engineCommandManager,
|
||||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
isMock: true,
|
||||||
programMemoryOverride,
|
|
||||||
})
|
})
|
||||||
const programMemory = execState.memory
|
const sketch = sketchFromKclValue(execState.variables[varName], varName)
|
||||||
this.sceneProgramMemory = programMemory
|
|
||||||
const sketch = sketchFromKclValue(programMemory.get(varName), varName)
|
|
||||||
if (err(sketch)) return Promise.reject(sketch)
|
if (err(sketch)) return Promise.reject(sketch)
|
||||||
const sgPaths = sketch.paths
|
const sgPaths = sketch.paths
|
||||||
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
||||||
@ -1291,7 +1277,7 @@ export class SceneEntities {
|
|||||||
return Promise.reject(__recastAst)
|
return Promise.reject(__recastAst)
|
||||||
_ast = __recastAst.program
|
_ast = __recastAst.program
|
||||||
|
|
||||||
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
|
const { truncatedAst } = await this.setupSketch({
|
||||||
sketchEntryNodePath: updatedEntryNodePath,
|
sketchEntryNodePath: updatedEntryNodePath,
|
||||||
sketchNodePaths: updatedSketchNodePaths,
|
sketchNodePaths: updatedSketchNodePaths,
|
||||||
forward,
|
forward,
|
||||||
@ -1337,12 +1323,9 @@ export class SceneEntities {
|
|||||||
const { execState } = await executeAst({
|
const { execState } = await executeAst({
|
||||||
ast: truncatedAst,
|
ast: truncatedAst,
|
||||||
engineCommandManager: this.engineCommandManager,
|
engineCommandManager: this.engineCommandManager,
|
||||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
isMock: true,
|
||||||
programMemoryOverride,
|
|
||||||
})
|
})
|
||||||
const programMemory = execState.memory
|
const sketch = sketchFromKclValue(execState.variables[varName], varName)
|
||||||
this.sceneProgramMemory = programMemory
|
|
||||||
const sketch = sketchFromKclValue(programMemory.get(varName), varName)
|
|
||||||
if (err(sketch)) return Promise.reject(sketch)
|
if (err(sketch)) return Promise.reject(sketch)
|
||||||
const sgPaths = sketch.paths
|
const sgPaths = sketch.paths
|
||||||
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
||||||
@ -1461,7 +1444,7 @@ export class SceneEntities {
|
|||||||
// do a quick mock execution to get the program memory up-to-date
|
// do a quick mock execution to get the program memory up-to-date
|
||||||
await kclManager.executeAstMock(_ast)
|
await kclManager.executeAstMock(_ast)
|
||||||
|
|
||||||
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
|
const { truncatedAst } = await this.setupSketch({
|
||||||
sketchEntryNodePath: updatedEntryNodePath,
|
sketchEntryNodePath: updatedEntryNodePath,
|
||||||
sketchNodePaths: updatedSketchNodePaths,
|
sketchNodePaths: updatedSketchNodePaths,
|
||||||
forward,
|
forward,
|
||||||
@ -1492,7 +1475,7 @@ export class SceneEntities {
|
|||||||
if (sketchInit.type === 'CallExpressionKw') {
|
if (sketchInit.type === 'CallExpressionKw') {
|
||||||
const moddedResult = changeSketchArguments(
|
const moddedResult = changeSketchArguments(
|
||||||
modded,
|
modded,
|
||||||
kclManager.programMemory,
|
kclManager.variables,
|
||||||
{
|
{
|
||||||
type: 'path',
|
type: 'path',
|
||||||
pathToNode: nodePathWithCorrectedIndexForTruncatedAst,
|
pathToNode: nodePathWithCorrectedIndexForTruncatedAst,
|
||||||
@ -1514,12 +1497,9 @@ export class SceneEntities {
|
|||||||
const { execState } = await executeAst({
|
const { execState } = await executeAst({
|
||||||
ast: modded,
|
ast: modded,
|
||||||
engineCommandManager: this.engineCommandManager,
|
engineCommandManager: this.engineCommandManager,
|
||||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
isMock: true,
|
||||||
programMemoryOverride,
|
|
||||||
})
|
})
|
||||||
const programMemory = execState.memory
|
const sketch = sketchFromKclValue(execState.variables[varName], varName)
|
||||||
this.sceneProgramMemory = programMemory
|
|
||||||
const sketch = sketchFromKclValue(programMemory.get(varName), varName)
|
|
||||||
if (err(sketch)) return
|
if (err(sketch)) return
|
||||||
const sgPaths = sketch.paths
|
const sgPaths = sketch.paths
|
||||||
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
||||||
@ -1560,7 +1540,7 @@ export class SceneEntities {
|
|||||||
if (sketchInit.type === 'CallExpressionKw') {
|
if (sketchInit.type === 'CallExpressionKw') {
|
||||||
const moddedResult = changeSketchArguments(
|
const moddedResult = changeSketchArguments(
|
||||||
modded,
|
modded,
|
||||||
kclManager.programMemory,
|
kclManager.variables,
|
||||||
{
|
{
|
||||||
type: 'path',
|
type: 'path',
|
||||||
pathToNode: updatedEntryNodePath,
|
pathToNode: updatedEntryNodePath,
|
||||||
@ -1643,7 +1623,7 @@ export class SceneEntities {
|
|||||||
// do a quick mock execution to get the program memory up-to-date
|
// do a quick mock execution to get the program memory up-to-date
|
||||||
await kclManager.executeAstMock(_ast)
|
await kclManager.executeAstMock(_ast)
|
||||||
|
|
||||||
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
|
const { truncatedAst } = await this.setupSketch({
|
||||||
sketchEntryNodePath: updatedEntryNodePath,
|
sketchEntryNodePath: updatedEntryNodePath,
|
||||||
sketchNodePaths: updatedSketchNodePaths,
|
sketchNodePaths: updatedSketchNodePaths,
|
||||||
forward,
|
forward,
|
||||||
@ -1676,7 +1656,7 @@ export class SceneEntities {
|
|||||||
if (sketchInit.type === 'CallExpression') {
|
if (sketchInit.type === 'CallExpression') {
|
||||||
const moddedResult = changeSketchArguments(
|
const moddedResult = changeSketchArguments(
|
||||||
modded,
|
modded,
|
||||||
kclManager.programMemory,
|
kclManager.variables,
|
||||||
{
|
{
|
||||||
type: 'path',
|
type: 'path',
|
||||||
pathToNode: nodePathWithCorrectedIndexForTruncatedAst,
|
pathToNode: nodePathWithCorrectedIndexForTruncatedAst,
|
||||||
@ -1695,12 +1675,9 @@ export class SceneEntities {
|
|||||||
const { execState } = await executeAst({
|
const { execState } = await executeAst({
|
||||||
ast: modded,
|
ast: modded,
|
||||||
engineCommandManager: this.engineCommandManager,
|
engineCommandManager: this.engineCommandManager,
|
||||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
isMock: true,
|
||||||
programMemoryOverride,
|
|
||||||
})
|
})
|
||||||
const programMemory = execState.memory
|
const sketch = sketchFromKclValue(execState.variables[varName], varName)
|
||||||
this.sceneProgramMemory = programMemory
|
|
||||||
const sketch = sketchFromKclValue(programMemory.get(varName), varName)
|
|
||||||
if (err(sketch)) return
|
if (err(sketch)) return
|
||||||
const sgPaths = sketch.paths
|
const sgPaths = sketch.paths
|
||||||
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
||||||
@ -1744,7 +1721,7 @@ export class SceneEntities {
|
|||||||
if (sketchInit.type === 'CallExpression') {
|
if (sketchInit.type === 'CallExpression') {
|
||||||
const moddedResult = changeSketchArguments(
|
const moddedResult = changeSketchArguments(
|
||||||
modded,
|
modded,
|
||||||
kclManager.programMemory,
|
kclManager.variables,
|
||||||
{
|
{
|
||||||
type: 'path',
|
type: 'path',
|
||||||
pathToNode: updatedEntryNodePath,
|
pathToNode: updatedEntryNodePath,
|
||||||
@ -1835,7 +1812,7 @@ export class SceneEntities {
|
|||||||
const sketch = sketchFromPathToNode({
|
const sketch = sketchFromPathToNode({
|
||||||
pathToNode,
|
pathToNode,
|
||||||
ast: kclManager.ast,
|
ast: kclManager.ast,
|
||||||
programMemory: kclManager.programMemory,
|
variables: kclManager.variables,
|
||||||
})
|
})
|
||||||
if (trap(sketch)) return
|
if (trap(sketch)) return
|
||||||
if (!sketch) {
|
if (!sketch) {
|
||||||
@ -1848,7 +1825,7 @@ export class SceneEntities {
|
|||||||
const prevSegment = sketch.paths[pipeIndex - 2]
|
const prevSegment = sketch.paths[pipeIndex - 2]
|
||||||
const mod = addNewSketchLn({
|
const mod = addNewSketchLn({
|
||||||
node: kclManager.ast,
|
node: kclManager.ast,
|
||||||
programMemory: kclManager.programMemory,
|
variables: kclManager.variables,
|
||||||
input: {
|
input: {
|
||||||
type: 'straight-segment',
|
type: 'straight-segment',
|
||||||
to: [intersectionPoint.twoD.x, intersectionPoint.twoD.y],
|
to: [intersectionPoint.twoD.x, intersectionPoint.twoD.y],
|
||||||
@ -1925,15 +1902,15 @@ export class SceneEntities {
|
|||||||
...this.mouseEnterLeaveCallbacks(),
|
...this.mouseEnterLeaveCallbacks(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
prepareTruncatedMemoryAndAst = (
|
prepareTruncatedAst = (
|
||||||
sketchNodePaths: PathToNode[],
|
sketchNodePaths: PathToNode[],
|
||||||
ast?: Node<Program>,
|
ast?: Node<Program>,
|
||||||
draftSegment?: DraftSegment
|
draftSegment?: DraftSegment
|
||||||
) =>
|
) =>
|
||||||
prepareTruncatedMemoryAndAst(
|
prepareTruncatedAst(
|
||||||
sketchNodePaths,
|
sketchNodePaths,
|
||||||
ast || kclManager.ast,
|
ast || kclManager.ast,
|
||||||
kclManager.lastSuccessfulProgramMemory,
|
kclManager.lastSuccessfulVariables,
|
||||||
draftSegment
|
draftSegment
|
||||||
)
|
)
|
||||||
onDragSegment({
|
onDragSegment({
|
||||||
@ -1953,7 +1930,6 @@ export class SceneEntities {
|
|||||||
intersects: Intersection<Object3D<Object3DEventMap>>[]
|
intersects: Intersection<Object3D<Object3DEventMap>>[]
|
||||||
draftInfo?: {
|
draftInfo?: {
|
||||||
truncatedAst: Node<Program>
|
truncatedAst: Node<Program>
|
||||||
programMemoryOverride: ProgramMemory
|
|
||||||
variableDeclarationName: string
|
variableDeclarationName: string
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
@ -2098,12 +2074,12 @@ export class SceneEntities {
|
|||||||
to: dragTo,
|
to: dragTo,
|
||||||
from,
|
from,
|
||||||
},
|
},
|
||||||
previousProgramMemory: kclManager.programMemory,
|
variables: kclManager.variables,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
modded = changeSketchArguments(
|
modded = changeSketchArguments(
|
||||||
modifiedAst,
|
modifiedAst,
|
||||||
kclManager.programMemory,
|
kclManager.variables,
|
||||||
{
|
{
|
||||||
type: 'sourceRange',
|
type: 'sourceRange',
|
||||||
sourceRange: topLevelRange(node.start, node.end),
|
sourceRange: topLevelRange(node.start, node.end),
|
||||||
@ -2116,9 +2092,9 @@ export class SceneEntities {
|
|||||||
modifiedAst = modded.modifiedAst
|
modifiedAst = modded.modifiedAst
|
||||||
const info = draftInfo
|
const info = draftInfo
|
||||||
? draftInfo
|
? draftInfo
|
||||||
: this.prepareTruncatedMemoryAndAst(sketchNodePaths || [], modifiedAst)
|
: this.prepareTruncatedAst(sketchNodePaths || [], modifiedAst)
|
||||||
if (trap(info, { suppress: true })) return
|
if (trap(info, { suppress: true })) return
|
||||||
const { truncatedAst, programMemoryOverride } = info
|
const { truncatedAst } = info
|
||||||
;(async () => {
|
;(async () => {
|
||||||
const code = recast(modifiedAst)
|
const code = recast(modifiedAst)
|
||||||
if (trap(code)) return
|
if (trap(code)) return
|
||||||
@ -2129,15 +2105,13 @@ export class SceneEntities {
|
|||||||
const { execState } = await executeAst({
|
const { execState } = await executeAst({
|
||||||
ast: truncatedAst,
|
ast: truncatedAst,
|
||||||
engineCommandManager: this.engineCommandManager,
|
engineCommandManager: this.engineCommandManager,
|
||||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
isMock: true,
|
||||||
programMemoryOverride,
|
|
||||||
})
|
})
|
||||||
const programMemory = execState.memory
|
const variables = execState.variables
|
||||||
this.sceneProgramMemory = programMemory
|
|
||||||
const sketchesInfo = getSketchesInfo({
|
const sketchesInfo = getSketchesInfo({
|
||||||
sketchNodePaths,
|
sketchNodePaths,
|
||||||
ast: truncatedAst,
|
ast: truncatedAst,
|
||||||
programMemory,
|
variables,
|
||||||
})
|
})
|
||||||
const callBacks: (() => SegmentOverlayPayload | null)[] = []
|
const callBacks: (() => SegmentOverlayPayload | null)[] = []
|
||||||
for (const sketchInfo of sketchesInfo) {
|
for (const sketchInfo of sketchesInfo) {
|
||||||
@ -2463,15 +2437,14 @@ export class SceneEntities {
|
|||||||
|
|
||||||
// calculations/pure-functions/easy to test so no excuse not to
|
// calculations/pure-functions/easy to test so no excuse not to
|
||||||
|
|
||||||
function prepareTruncatedMemoryAndAst(
|
function prepareTruncatedAst(
|
||||||
sketchNodePaths: PathToNode[],
|
sketchNodePaths: PathToNode[],
|
||||||
ast: Node<Program>,
|
ast: Node<Program>,
|
||||||
programMemory: ProgramMemory,
|
variables: VariableMap,
|
||||||
draftSegment?: DraftSegment
|
draftSegment?: DraftSegment
|
||||||
):
|
):
|
||||||
| {
|
| {
|
||||||
truncatedAst: Node<Program>
|
truncatedAst: Node<Program>
|
||||||
programMemoryOverride: ProgramMemory
|
|
||||||
// can I remove the below?
|
// can I remove the below?
|
||||||
variableDeclarationName: string
|
variableDeclarationName: string
|
||||||
}
|
}
|
||||||
@ -2490,7 +2463,7 @@ function prepareTruncatedMemoryAndAst(
|
|||||||
if (err(_node)) return _node
|
if (err(_node)) return _node
|
||||||
const variableDeclarationName = _node.node?.declaration?.id?.name || ''
|
const variableDeclarationName = _node.node?.declaration?.id?.name || ''
|
||||||
const sg = sketchFromKclValue(
|
const sg = sketchFromKclValue(
|
||||||
programMemory.get(variableDeclarationName),
|
variables[variableDeclarationName],
|
||||||
variableDeclarationName
|
variableDeclarationName
|
||||||
)
|
)
|
||||||
if (err(sg)) return sg
|
if (err(sg)) return sg
|
||||||
@ -2549,43 +2522,8 @@ function prepareTruncatedMemoryAndAst(
|
|||||||
body: structuredClone(_ast.body.slice(bodyStartIndex, bodyEndIndex + 1)),
|
body: structuredClone(_ast.body.slice(bodyStartIndex, bodyEndIndex + 1)),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grab all the TagDeclarators and TagIdentifiers from memory.
|
|
||||||
let start = _node.node.start
|
|
||||||
const programMemoryOverride = programMemory.filterVariables(true, (value) => {
|
|
||||||
if (
|
|
||||||
!('__meta' in value) ||
|
|
||||||
value.__meta === undefined ||
|
|
||||||
value.__meta.length === 0 ||
|
|
||||||
value.__meta[0].sourceRange === undefined
|
|
||||||
) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value.__meta[0].sourceRange[0] >= start) {
|
|
||||||
// We only want things before our start point.
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return value.type === 'TagIdentifier'
|
|
||||||
})
|
|
||||||
if (err(programMemoryOverride)) return programMemoryOverride
|
|
||||||
|
|
||||||
for (let i = 0; i < bodyStartIndex; i++) {
|
|
||||||
const node = _ast.body[i]
|
|
||||||
if (node.type !== 'VariableDeclaration') {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
const name = node.declaration.id.name
|
|
||||||
const memoryItem = programMemory.get(name)
|
|
||||||
if (!memoryItem) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
const error = programMemoryOverride.set(name, structuredClone(memoryItem))
|
|
||||||
if (err(error)) return error
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
truncatedAst,
|
truncatedAst,
|
||||||
programMemoryOverride,
|
|
||||||
variableDeclarationName,
|
variableDeclarationName,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2605,11 +2543,11 @@ export function getParentGroup(
|
|||||||
export function sketchFromPathToNode({
|
export function sketchFromPathToNode({
|
||||||
pathToNode,
|
pathToNode,
|
||||||
ast,
|
ast,
|
||||||
programMemory,
|
variables,
|
||||||
}: {
|
}: {
|
||||||
pathToNode: PathToNode
|
pathToNode: PathToNode
|
||||||
ast: Program
|
ast: Program
|
||||||
programMemory: ProgramMemory
|
variables: VariableMap
|
||||||
}): Sketch | null | Error {
|
}): Sketch | null | Error {
|
||||||
const _varDec = getNodeFromPath<VariableDeclarator>(
|
const _varDec = getNodeFromPath<VariableDeclarator>(
|
||||||
kclManager.ast,
|
kclManager.ast,
|
||||||
@ -2618,7 +2556,7 @@ export function sketchFromPathToNode({
|
|||||||
)
|
)
|
||||||
if (err(_varDec)) return _varDec
|
if (err(_varDec)) return _varDec
|
||||||
const varDec = _varDec.node
|
const varDec = _varDec.node
|
||||||
const result = programMemory.get(varDec?.id?.name || '')
|
const result = variables[varDec?.id?.name || '']
|
||||||
if (result?.type === 'Solid') {
|
if (result?.type === 'Solid') {
|
||||||
return result.value.sketch
|
return result.value.sketch
|
||||||
}
|
}
|
||||||
@ -2657,7 +2595,7 @@ export function getSketchQuaternion(
|
|||||||
const sketch = sketchFromPathToNode({
|
const sketch = sketchFromPathToNode({
|
||||||
pathToNode: sketchPathToNode,
|
pathToNode: sketchPathToNode,
|
||||||
ast: kclManager.ast,
|
ast: kclManager.ast,
|
||||||
programMemory: kclManager.programMemory,
|
variables: kclManager.variables,
|
||||||
})
|
})
|
||||||
if (err(sketch)) return sketch
|
if (err(sketch)) return sketch
|
||||||
const zAxis = sketch?.on.zAxis || sketchNormalBackUp
|
const zAxis = sketch?.on.zAxis || sketchNormalBackUp
|
||||||
@ -2677,7 +2615,7 @@ export async function getSketchOrientationDetails(
|
|||||||
const sketch = sketchFromPathToNode({
|
const sketch = sketchFromPathToNode({
|
||||||
pathToNode: sketchEntryNodePath,
|
pathToNode: sketchEntryNodePath,
|
||||||
ast: kclManager.ast,
|
ast: kclManager.ast,
|
||||||
programMemory: kclManager.programMemory,
|
variables: kclManager.variables,
|
||||||
})
|
})
|
||||||
if (err(sketch)) return Promise.reject(sketch)
|
if (err(sketch)) return Promise.reject(sketch)
|
||||||
if (!sketch) return Promise.reject('sketch not found')
|
if (!sketch) return Promise.reject('sketch not found')
|
||||||
@ -2790,11 +2728,11 @@ function massageFormats(a: Vec3Array | Point3d): Vector3 {
|
|||||||
function getSketchesInfo({
|
function getSketchesInfo({
|
||||||
sketchNodePaths,
|
sketchNodePaths,
|
||||||
ast,
|
ast,
|
||||||
programMemory,
|
variables,
|
||||||
}: {
|
}: {
|
||||||
sketchNodePaths: PathToNode[]
|
sketchNodePaths: PathToNode[]
|
||||||
ast: Node<Program>
|
ast: Node<Program>
|
||||||
programMemory: ProgramMemory
|
variables: VariableMap
|
||||||
}): {
|
}): {
|
||||||
sketch: Sketch
|
sketch: Sketch
|
||||||
pathToNode: PathToNode
|
pathToNode: PathToNode
|
||||||
@ -2807,7 +2745,7 @@ function getSketchesInfo({
|
|||||||
const sketch = sketchFromPathToNode({
|
const sketch = sketchFromPathToNode({
|
||||||
pathToNode: path,
|
pathToNode: path,
|
||||||
ast,
|
ast,
|
||||||
programMemory,
|
variables,
|
||||||
})
|
})
|
||||||
if (err(sketch)) continue
|
if (err(sketch)) continue
|
||||||
if (!sketch) continue
|
if (!sketch) continue
|
||||||
|
@ -1,11 +1,5 @@
|
|||||||
import { useEffect, useState, useRef } from 'react'
|
import { useEffect, useState, useRef } from 'react'
|
||||||
import {
|
import { parse, BinaryPart, Expr, resultIsOk, VariableMap } from '../lang/wasm'
|
||||||
parse,
|
|
||||||
BinaryPart,
|
|
||||||
Expr,
|
|
||||||
ProgramMemory,
|
|
||||||
resultIsOk,
|
|
||||||
} from '../lang/wasm'
|
|
||||||
import {
|
import {
|
||||||
createIdentifier,
|
createIdentifier,
|
||||||
createLiteral,
|
createLiteral,
|
||||||
@ -100,7 +94,7 @@ export function useCalc({
|
|||||||
newVariableInsertIndex: number
|
newVariableInsertIndex: number
|
||||||
setNewVariableName: (a: string) => void
|
setNewVariableName: (a: string) => void
|
||||||
} {
|
} {
|
||||||
const { programMemory } = useKclContext()
|
const { variables } = useKclContext()
|
||||||
const { context } = useModelingContext()
|
const { context } = useModelingContext()
|
||||||
const selectionRange =
|
const selectionRange =
|
||||||
context.selectionRanges?.graphSelections[0]?.codeRef?.range
|
context.selectionRanges?.graphSelections[0]?.codeRef?.range
|
||||||
@ -127,7 +121,7 @@ export function useCalc({
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (programMemory.has(newVariableName)) {
|
if (variables[newVariableName]) {
|
||||||
setIsNewVariableNameUnique(false)
|
setIsNewVariableNameUnique(false)
|
||||||
} else {
|
} else {
|
||||||
setIsNewVariableNameUnique(true)
|
setIsNewVariableNameUnique(true)
|
||||||
@ -135,14 +129,14 @@ export function useCalc({
|
|||||||
}, [newVariableName])
|
}, [newVariableName])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!programMemory || !selectionRange) return
|
if (!variables || !selectionRange) return
|
||||||
const varInfo = findAllPreviousVariables(
|
const varInfo = findAllPreviousVariables(
|
||||||
kclManager.ast,
|
kclManager.ast,
|
||||||
kclManager.programMemory,
|
kclManager.variables,
|
||||||
selectionRange
|
selectionRange
|
||||||
)
|
)
|
||||||
setAvailableVarInfo(varInfo)
|
setAvailableVarInfo(varInfo)
|
||||||
}, [kclManager.ast, kclManager.programMemory, selectionRange])
|
}, [kclManager.ast, kclManager.variables, selectionRange])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
try {
|
try {
|
||||||
@ -150,9 +144,9 @@ export function useCalc({
|
|||||||
const pResult = parse(code)
|
const pResult = parse(code)
|
||||||
if (trap(pResult) || !resultIsOk(pResult)) return
|
if (trap(pResult) || !resultIsOk(pResult)) return
|
||||||
const ast = pResult.program
|
const ast = pResult.program
|
||||||
const _programMem: ProgramMemory = ProgramMemory.empty()
|
const _variables: VariableMap = {}
|
||||||
for (const { key, value } of availableVarInfo.variables) {
|
for (const { key, value } of availableVarInfo.variables) {
|
||||||
const error = _programMem.set(key, {
|
const error = (_variables[key] = {
|
||||||
type: 'String',
|
type: 'String',
|
||||||
value,
|
value,
|
||||||
__meta: [],
|
__meta: [],
|
||||||
@ -163,8 +157,8 @@ export function useCalc({
|
|||||||
executeAst({
|
executeAst({
|
||||||
ast,
|
ast,
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
isMock: true,
|
||||||
programMemoryOverride: kclManager.programMemory.clone(),
|
variables,
|
||||||
}).then(({ execState }) => {
|
}).then(({ execState }) => {
|
||||||
const resultDeclaration = ast.body.find(
|
const resultDeclaration = ast.body.find(
|
||||||
(a) =>
|
(a) =>
|
||||||
@ -174,7 +168,7 @@ export function useCalc({
|
|||||||
const init =
|
const init =
|
||||||
resultDeclaration?.type === 'VariableDeclaration' &&
|
resultDeclaration?.type === 'VariableDeclaration' &&
|
||||||
resultDeclaration?.declaration.init
|
resultDeclaration?.declaration.init
|
||||||
const result = execState.memory?.get('__result__')?.value
|
const result = execState.variables['__result__']?.value
|
||||||
setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
|
setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
|
||||||
init && setValueNode(init)
|
init && setValueNode(init)
|
||||||
})
|
})
|
||||||
|
@ -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
|
||||||
|
@ -157,8 +157,6 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
plugin.requestSemanticTokens()
|
plugin.requestSemanticTokens()
|
||||||
break
|
break
|
||||||
case 'kcl/memoryUpdated':
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
|
@ -350,11 +350,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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { processMemory } from './MemoryPane'
|
import { processMemory } from './MemoryPane'
|
||||||
import { enginelessExecutor } from '../../../lib/testHelpers'
|
import { enginelessExecutor } from '../../../lib/testHelpers'
|
||||||
import { assertParse, initPromise, ProgramMemory } from '../../../lang/wasm'
|
import { assertParse, initPromise } from '../../../lang/wasm'
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await initPromise
|
await initPromise
|
||||||
@ -29,15 +29,11 @@ describe('processMemory', () => {
|
|||||||
|> line(endAbsolute = [2.15, 4.32])
|
|> line(endAbsolute = [2.15, 4.32])
|
||||||
// |> rx(90, %)`
|
// |> rx(90, %)`
|
||||||
const ast = assertParse(code)
|
const ast = assertParse(code)
|
||||||
const execState = await enginelessExecutor(ast, ProgramMemory.empty())
|
const execState = await enginelessExecutor(ast)
|
||||||
const output = processMemory(execState.memory)
|
const output = processMemory(execState.variables)
|
||||||
expect(output.myVar).toEqual(5)
|
expect(output.myVar).toEqual(5)
|
||||||
expect(output.otherVar).toEqual(3)
|
expect(output.otherVar).toEqual(3)
|
||||||
expect(output).toEqual({
|
expect(output).toEqual({
|
||||||
HALF_TURN: 180,
|
|
||||||
QUARTER_TURN: 90,
|
|
||||||
THREE_QUARTER_TURN: 270,
|
|
||||||
ZERO: 0,
|
|
||||||
myVar: 5,
|
myVar: 5,
|
||||||
myFn: '__function(a)__',
|
myFn: '__function(a)__',
|
||||||
otherVar: 3,
|
otherVar: 3,
|
||||||
|
@ -2,10 +2,10 @@ import toast from 'react-hot-toast'
|
|||||||
import ReactJson from 'react-json-view'
|
import ReactJson from 'react-json-view'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import {
|
import {
|
||||||
ProgramMemory,
|
|
||||||
Path,
|
Path,
|
||||||
ExtrudeSurface,
|
ExtrudeSurface,
|
||||||
sketchFromKclValueOptional,
|
sketchFromKclValueOptional,
|
||||||
|
VariableMap,
|
||||||
} from 'lang/wasm'
|
} from 'lang/wasm'
|
||||||
import { useKclContext } from 'lang/KclProvider'
|
import { useKclContext } from 'lang/KclProvider'
|
||||||
import { useResolvedTheme } from 'hooks/useResolvedTheme'
|
import { useResolvedTheme } from 'hooks/useResolvedTheme'
|
||||||
@ -15,12 +15,12 @@ import Tooltip from 'components/Tooltip'
|
|||||||
import { useModelingContext } from 'hooks/useModelingContext'
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
|
|
||||||
export const MemoryPaneMenu = () => {
|
export const MemoryPaneMenu = () => {
|
||||||
const { programMemory } = useKclContext()
|
const { variables } = useKclContext()
|
||||||
|
|
||||||
function copyProgramMemoryToClipboard() {
|
function copyProgramMemoryToClipboard() {
|
||||||
if (globalThis && 'navigator' in globalThis) {
|
if (globalThis && 'navigator' in globalThis) {
|
||||||
navigator.clipboard
|
navigator.clipboard
|
||||||
.writeText(JSON.stringify(programMemory))
|
.writeText(JSON.stringify(variables))
|
||||||
.then(() => toast.success('Program memory copied to clipboard'))
|
.then(() => toast.success('Program memory copied to clipboard'))
|
||||||
.catch((e) =>
|
.catch((e) =>
|
||||||
trap(new Error('Failed to copy program memory to clipboard'))
|
trap(new Error('Failed to copy program memory to clipboard'))
|
||||||
@ -50,12 +50,9 @@ export const MemoryPaneMenu = () => {
|
|||||||
|
|
||||||
export const MemoryPane = () => {
|
export const MemoryPane = () => {
|
||||||
const theme = useResolvedTheme()
|
const theme = useResolvedTheme()
|
||||||
const { programMemory } = useKclContext()
|
const { variables } = useKclContext()
|
||||||
const { state } = useModelingContext()
|
const { state } = useModelingContext()
|
||||||
const ProcessedMemory = useMemo(
|
const ProcessedMemory = useMemo(() => processMemory(variables), [variables])
|
||||||
() => processMemory(programMemory),
|
|
||||||
[programMemory]
|
|
||||||
)
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full relative">
|
<div className="h-full relative">
|
||||||
<div className="absolute inset-0 p-2 flex flex-col items-start">
|
<div className="absolute inset-0 p-2 flex flex-col items-start">
|
||||||
@ -85,9 +82,10 @@ export const MemoryPane = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const processMemory = (programMemory: ProgramMemory) => {
|
export const processMemory = (variables: VariableMap) => {
|
||||||
const processedMemory: any = {}
|
const processedMemory: any = {}
|
||||||
for (const [key, val] of programMemory?.visibleEntries()) {
|
for (const [key, val] of Object.entries(variables)) {
|
||||||
|
if (val === undefined) continue
|
||||||
if (
|
if (
|
||||||
val.type === 'Sketch' ||
|
val.type === 'Sketch' ||
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -19,7 +19,6 @@ import { commandBarActor } from 'machines/commandBarMachine'
|
|||||||
import { useSelector } from '@xstate/react'
|
import { useSelector } from '@xstate/react'
|
||||||
import { copyFileShareLink } from 'lib/links'
|
import { copyFileShareLink } from 'lib/links'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
import { IS_NIGHTLY_OR_DEBUG } from 'routes/Settings'
|
|
||||||
import { useToken } from 'machines/appMachine'
|
import { useToken } from 'machines/appMachine'
|
||||||
|
|
||||||
const ProjectSidebarMenu = ({
|
const ProjectSidebarMenu = ({
|
||||||
@ -194,7 +193,7 @@ function ProjectMenuPopover({
|
|||||||
id: 'share-link',
|
id: 'share-link',
|
||||||
Element: 'button',
|
Element: 'button',
|
||||||
children: 'Share current part (via Zoo link)',
|
children: 'Share current part (via Zoo link)',
|
||||||
disabled: !(IS_NIGHTLY_OR_DEBUG && findCommand(shareCommandInfo)),
|
disabled: !findCommand(shareCommandInfo),
|
||||||
onClick: async () => {
|
onClick: async () => {
|
||||||
await copyFileShareLink({
|
await copyFileShareLink({
|
||||||
token: token ?? '',
|
token: token ?? '',
|
||||||
|
@ -95,7 +95,7 @@ export function applyConstraintEqualAngle({
|
|||||||
ast: kclManager.ast,
|
ast: kclManager.ast,
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
transformInfos: transforms,
|
transformInfos: transforms,
|
||||||
programMemory: kclManager.programMemory,
|
memVars: kclManager.variables,
|
||||||
})
|
})
|
||||||
if (err(transform)) return transform
|
if (err(transform)) return transform
|
||||||
const { modifiedAst, pathToNodeMap } = transform
|
const { modifiedAst, pathToNodeMap } = transform
|
||||||
|
@ -93,7 +93,7 @@ export function applyConstraintEqualLength({
|
|||||||
ast: kclManager.ast,
|
ast: kclManager.ast,
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
transformInfos: transforms,
|
transformInfos: transforms,
|
||||||
programMemory: kclManager.programMemory,
|
memVars: kclManager.variables,
|
||||||
})
|
})
|
||||||
if (err(transform)) return transform
|
if (err(transform)) return transform
|
||||||
const { modifiedAst, pathToNodeMap } = transform
|
const { modifiedAst, pathToNodeMap } = transform
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { toolTips } from 'lang/langHelpers'
|
import { toolTips } from 'lang/langHelpers'
|
||||||
import { Selections } from 'lib/selections'
|
import { Selections } from 'lib/selections'
|
||||||
import { Program, ProgramMemory, Expr } from '../../lang/wasm'
|
import { Program, Expr, VariableMap } from '../../lang/wasm'
|
||||||
import { getNodeFromPath } from '../../lang/queryAst'
|
import { getNodeFromPath } from '../../lang/queryAst'
|
||||||
import {
|
import {
|
||||||
PathToNodeMap,
|
PathToNodeMap,
|
||||||
@ -51,7 +51,7 @@ export function applyConstraintHorzVert(
|
|||||||
selectionRanges: Selections,
|
selectionRanges: Selections,
|
||||||
horOrVert: 'vertical' | 'horizontal',
|
horOrVert: 'vertical' | 'horizontal',
|
||||||
ast: Node<Program>,
|
ast: Node<Program>,
|
||||||
programMemory: ProgramMemory
|
memVars: VariableMap
|
||||||
):
|
):
|
||||||
| {
|
| {
|
||||||
modifiedAst: Node<Program>
|
modifiedAst: Node<Program>
|
||||||
@ -66,7 +66,7 @@ export function applyConstraintHorzVert(
|
|||||||
ast,
|
ast,
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
transformInfos,
|
transformInfos,
|
||||||
programMemory,
|
memVars,
|
||||||
referenceSegName: '',
|
referenceSegName: '',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ export function intersectInfo({
|
|||||||
isLinesParallelAndConstrained(
|
isLinesParallelAndConstrained(
|
||||||
kclManager.ast,
|
kclManager.ast,
|
||||||
engineCommandManager.artifactGraph,
|
engineCommandManager.artifactGraph,
|
||||||
kclManager.programMemory,
|
kclManager.variables,
|
||||||
selectionRanges.graphSelections[0],
|
selectionRanges.graphSelections[0],
|
||||||
selectionRanges.graphSelections[1]
|
selectionRanges.graphSelections[1]
|
||||||
)
|
)
|
||||||
@ -148,7 +148,7 @@ export async function applyConstraintIntersect({
|
|||||||
ast: structuredClone(kclManager.ast),
|
ast: structuredClone(kclManager.ast),
|
||||||
selectionRanges: forcedSelectionRanges,
|
selectionRanges: forcedSelectionRanges,
|
||||||
transformInfos: transforms,
|
transformInfos: transforms,
|
||||||
programMemory: kclManager.programMemory,
|
memVars: kclManager.variables,
|
||||||
})
|
})
|
||||||
if (err(transform1)) return Promise.reject(transform1)
|
if (err(transform1)) return Promise.reject(transform1)
|
||||||
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
|
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
|
||||||
@ -186,7 +186,7 @@ export async function applyConstraintIntersect({
|
|||||||
ast: kclManager.ast,
|
ast: kclManager.ast,
|
||||||
selectionRanges: forcedSelectionRanges,
|
selectionRanges: forcedSelectionRanges,
|
||||||
transformInfos: transforms,
|
transformInfos: transforms,
|
||||||
programMemory: kclManager.programMemory,
|
memVars: kclManager.variables,
|
||||||
forceSegName: segName,
|
forceSegName: segName,
|
||||||
forceValueUsedInTransform: finalValue,
|
forceValueUsedInTransform: finalValue,
|
||||||
})
|
})
|
||||||
|
@ -88,7 +88,7 @@ export function applyRemoveConstrainingValues({
|
|||||||
ast: kclManager.ast,
|
ast: kclManager.ast,
|
||||||
selectionRanges: updatedSelectionRanges,
|
selectionRanges: updatedSelectionRanges,
|
||||||
transformInfos: transforms,
|
transformInfos: transforms,
|
||||||
programMemory: kclManager.programMemory,
|
memVars: kclManager.variables,
|
||||||
referenceSegName: '',
|
referenceSegName: '',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -105,7 +105,7 @@ export async function applyConstraintAbsDistance({
|
|||||||
ast: structuredClone(kclManager.ast),
|
ast: structuredClone(kclManager.ast),
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
transformInfos,
|
transformInfos,
|
||||||
programMemory: kclManager.programMemory,
|
memVars: kclManager.variables,
|
||||||
referenceSegName: '',
|
referenceSegName: '',
|
||||||
})
|
})
|
||||||
if (err(transform1)) return Promise.reject(transform1)
|
if (err(transform1)) return Promise.reject(transform1)
|
||||||
@ -125,7 +125,7 @@ export async function applyConstraintAbsDistance({
|
|||||||
ast: structuredClone(kclManager.ast),
|
ast: structuredClone(kclManager.ast),
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
transformInfos,
|
transformInfos,
|
||||||
programMemory: kclManager.programMemory,
|
memVars: kclManager.variables,
|
||||||
referenceSegName: '',
|
referenceSegName: '',
|
||||||
forceValueUsedInTransform: finalValue,
|
forceValueUsedInTransform: finalValue,
|
||||||
})
|
})
|
||||||
@ -175,7 +175,7 @@ export function applyConstraintAxisAlign({
|
|||||||
ast: structuredClone(kclManager.ast),
|
ast: structuredClone(kclManager.ast),
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
transformInfos,
|
transformInfos,
|
||||||
programMemory: kclManager.programMemory,
|
memVars: kclManager.variables,
|
||||||
referenceSegName: '',
|
referenceSegName: '',
|
||||||
forceValueUsedInTransform: finalValue,
|
forceValueUsedInTransform: finalValue,
|
||||||
})
|
})
|
||||||
|
@ -96,7 +96,7 @@ export async function applyConstraintAngleBetween({
|
|||||||
ast: structuredClone(kclManager.ast),
|
ast: structuredClone(kclManager.ast),
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
transformInfos,
|
transformInfos,
|
||||||
programMemory: kclManager.programMemory,
|
memVars: kclManager.variables,
|
||||||
})
|
})
|
||||||
if (err(transformed1)) return Promise.reject(transformed1)
|
if (err(transformed1)) return Promise.reject(transformed1)
|
||||||
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
|
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
|
||||||
@ -135,7 +135,7 @@ export async function applyConstraintAngleBetween({
|
|||||||
ast: kclManager.ast,
|
ast: kclManager.ast,
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
transformInfos,
|
transformInfos,
|
||||||
programMemory: kclManager.programMemory,
|
memVars: kclManager.variables,
|
||||||
forceSegName: segName,
|
forceSegName: segName,
|
||||||
forceValueUsedInTransform: finalValue,
|
forceValueUsedInTransform: finalValue,
|
||||||
})
|
})
|
||||||
|
@ -105,7 +105,7 @@ export async function applyConstraintHorzVertDistance({
|
|||||||
ast: structuredClone(kclManager.ast),
|
ast: structuredClone(kclManager.ast),
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
transformInfos,
|
transformInfos,
|
||||||
programMemory: kclManager.programMemory,
|
memVars: kclManager.variables,
|
||||||
})
|
})
|
||||||
if (err(transformed)) return Promise.reject(transformed)
|
if (err(transformed)) return Promise.reject(transformed)
|
||||||
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
|
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
|
||||||
@ -142,7 +142,7 @@ export async function applyConstraintHorzVertDistance({
|
|||||||
ast: kclManager.ast,
|
ast: kclManager.ast,
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
transformInfos,
|
transformInfos,
|
||||||
programMemory: kclManager.programMemory,
|
memVars: kclManager.variables,
|
||||||
forceSegName: segName,
|
forceSegName: segName,
|
||||||
forceValueUsedInTransform: finalValue,
|
forceValueUsedInTransform: finalValue,
|
||||||
})
|
})
|
||||||
@ -195,7 +195,7 @@ export function applyConstraintHorzVertAlign({
|
|||||||
ast: kclManager.ast,
|
ast: kclManager.ast,
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
transformInfos,
|
transformInfos,
|
||||||
programMemory: kclManager.programMemory,
|
memVars: kclManager.variables,
|
||||||
forceValueUsedInTransform: finalValue,
|
forceValueUsedInTransform: finalValue,
|
||||||
})
|
})
|
||||||
if (err(retval)) return retval
|
if (err(retval)) return retval
|
||||||
|
@ -109,7 +109,7 @@ export async function applyConstraintLength({
|
|||||||
ast,
|
ast,
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
transformInfos: transforms,
|
transformInfos: transforms,
|
||||||
programMemory: kclManager.programMemory,
|
memVars: kclManager.variables,
|
||||||
referenceSegName: '',
|
referenceSegName: '',
|
||||||
forceValueUsedInTransform: distanceExpression,
|
forceValueUsedInTransform: distanceExpression,
|
||||||
})
|
})
|
||||||
@ -148,7 +148,7 @@ export async function applyConstraintAngleLength({
|
|||||||
ast: structuredClone(kclManager.ast),
|
ast: structuredClone(kclManager.ast),
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
transformInfos: transforms,
|
transformInfos: transforms,
|
||||||
programMemory: kclManager.programMemory,
|
memVars: kclManager.variables,
|
||||||
referenceSegName: '',
|
referenceSegName: '',
|
||||||
})
|
})
|
||||||
if (err(sketched)) return Promise.reject(sketched)
|
if (err(sketched)) return Promise.reject(sketched)
|
||||||
@ -200,7 +200,7 @@ export async function applyConstraintAngleLength({
|
|||||||
ast: structuredClone(kclManager.ast),
|
ast: structuredClone(kclManager.ast),
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
transformInfos: transforms,
|
transformInfos: transforms,
|
||||||
programMemory: kclManager.programMemory,
|
memVars: kclManager.variables,
|
||||||
referenceSegName: '',
|
referenceSegName: '',
|
||||||
forceValueUsedInTransform: finalValue,
|
forceValueUsedInTransform: finalValue,
|
||||||
})
|
})
|
||||||
|
@ -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
|
||||||
|
@ -55,7 +55,7 @@ export function useConvertToVariable(range?: SourceRange) {
|
|||||||
const { modifiedAst: _modifiedAst, pathToReplacedNode } =
|
const { modifiedAst: _modifiedAst, pathToReplacedNode } =
|
||||||
moveValueIntoNewVariable(
|
moveValueIntoNewVariable(
|
||||||
ast,
|
ast,
|
||||||
kclManager.programMemory,
|
kclManager.variables,
|
||||||
range || context.selectionRanges.graphSelections[0]?.codeRef?.range,
|
range || context.selectionRanges.graphSelections[0]?.codeRef?.range,
|
||||||
variableName
|
variableName
|
||||||
)
|
)
|
||||||
|
@ -7,7 +7,7 @@ import { KCLError } from './errors'
|
|||||||
|
|
||||||
const KclContext = createContext({
|
const KclContext = createContext({
|
||||||
code: codeManager?.code || '',
|
code: codeManager?.code || '',
|
||||||
programMemory: kclManager?.programMemory,
|
variables: kclManager?.variables,
|
||||||
ast: kclManager?.ast,
|
ast: kclManager?.ast,
|
||||||
isExecuting: kclManager?.isExecuting,
|
isExecuting: kclManager?.isExecuting,
|
||||||
diagnostics: kclManager?.diagnostics,
|
diagnostics: kclManager?.diagnostics,
|
||||||
@ -31,7 +31,7 @@ export function KclContextProvider({
|
|||||||
// Both the code state and the editor state start off with the same code.
|
// Both the code state and the editor state start off with the same code.
|
||||||
const [code, setCode] = useState(loadedCode || codeManager.code)
|
const [code, setCode] = useState(loadedCode || codeManager.code)
|
||||||
|
|
||||||
const [programMemory, setProgramMemory] = useState(kclManager.programMemory)
|
const [variables, setVariables] = useState(kclManager.variables)
|
||||||
const [ast, setAst] = useState(kclManager.ast)
|
const [ast, setAst] = useState(kclManager.ast)
|
||||||
const [isExecuting, setIsExecuting] = useState(false)
|
const [isExecuting, setIsExecuting] = useState(false)
|
||||||
const [diagnostics, setDiagnostics] = useState<Diagnostic[]>([])
|
const [diagnostics, setDiagnostics] = useState<Diagnostic[]>([])
|
||||||
@ -44,7 +44,7 @@ export function KclContextProvider({
|
|||||||
setCode,
|
setCode,
|
||||||
})
|
})
|
||||||
kclManager.registerCallBacks({
|
kclManager.registerCallBacks({
|
||||||
setProgramMemory,
|
setVariables,
|
||||||
setAst,
|
setAst,
|
||||||
setLogs,
|
setLogs,
|
||||||
setErrors,
|
setErrors,
|
||||||
@ -58,7 +58,7 @@ export function KclContextProvider({
|
|||||||
<KclContext.Provider
|
<KclContext.Provider
|
||||||
value={{
|
value={{
|
||||||
code,
|
code,
|
||||||
programMemory,
|
variables,
|
||||||
ast,
|
ast,
|
||||||
isExecuting,
|
isExecuting,
|
||||||
diagnostics,
|
diagnostics,
|
||||||
|
@ -17,13 +17,14 @@ import {
|
|||||||
emptyExecState,
|
emptyExecState,
|
||||||
ExecState,
|
ExecState,
|
||||||
initPromise,
|
initPromise,
|
||||||
|
KclValue,
|
||||||
parse,
|
parse,
|
||||||
PathToNode,
|
PathToNode,
|
||||||
Program,
|
Program,
|
||||||
ProgramMemory,
|
|
||||||
recast,
|
recast,
|
||||||
SourceRange,
|
SourceRange,
|
||||||
topLevelRange,
|
topLevelRange,
|
||||||
|
VariableMap,
|
||||||
} from 'lang/wasm'
|
} from 'lang/wasm'
|
||||||
import { getNodeFromPath, getSettingsAnnotation } from './queryAst'
|
import { getNodeFromPath, getSettingsAnnotation } from './queryAst'
|
||||||
import { codeManager, editorManager, sceneInfra } from 'lib/singletons'
|
import { codeManager, editorManager, sceneInfra } from 'lib/singletons'
|
||||||
@ -61,8 +62,8 @@ export class KclManager {
|
|||||||
trivia: [],
|
trivia: [],
|
||||||
}
|
}
|
||||||
private _execState: ExecState = emptyExecState()
|
private _execState: ExecState = emptyExecState()
|
||||||
private _programMemory: ProgramMemory = ProgramMemory.empty()
|
private _variables: VariableMap = {}
|
||||||
lastSuccessfulProgramMemory: ProgramMemory = ProgramMemory.empty()
|
lastSuccessfulVariables: VariableMap = {}
|
||||||
lastSuccessfulOperations: Operation[] = []
|
lastSuccessfulOperations: Operation[] = []
|
||||||
private _logs: string[] = []
|
private _logs: string[] = []
|
||||||
private _errors: KCLError[] = []
|
private _errors: KCLError[] = []
|
||||||
@ -78,7 +79,9 @@ export class KclManager {
|
|||||||
|
|
||||||
private _isExecutingCallback: (arg: boolean) => void = () => {}
|
private _isExecutingCallback: (arg: boolean) => void = () => {}
|
||||||
private _astCallBack: (arg: Node<Program>) => void = () => {}
|
private _astCallBack: (arg: Node<Program>) => void = () => {}
|
||||||
private _programMemoryCallBack: (arg: ProgramMemory) => void = () => {}
|
private _variablesCallBack: (arg: {
|
||||||
|
[key in string]?: KclValue | undefined
|
||||||
|
}) => void = () => {}
|
||||||
private _logsCallBack: (arg: string[]) => void = () => {}
|
private _logsCallBack: (arg: string[]) => void = () => {}
|
||||||
private _kclErrorsCallBack: (errors: KCLError[]) => void = () => {}
|
private _kclErrorsCallBack: (errors: KCLError[]) => void = () => {}
|
||||||
private _diagnosticsCallback: (errors: Diagnostic[]) => void = () => {}
|
private _diagnosticsCallback: (errors: Diagnostic[]) => void = () => {}
|
||||||
@ -97,18 +100,18 @@ export class KclManager {
|
|||||||
this._switchedFiles = switchedFiles
|
this._switchedFiles = switchedFiles
|
||||||
}
|
}
|
||||||
|
|
||||||
get programMemory() {
|
get variables() {
|
||||||
return this._programMemory
|
return this._variables
|
||||||
}
|
}
|
||||||
// This is private because callers should be setting the entire execState.
|
// This is private because callers should be setting the entire execState.
|
||||||
private set programMemory(programMemory) {
|
private set variables(variables) {
|
||||||
this._programMemory = programMemory
|
this._variables = variables
|
||||||
this._programMemoryCallBack(programMemory)
|
this._variablesCallBack(variables)
|
||||||
}
|
}
|
||||||
|
|
||||||
private set execState(execState) {
|
private set execState(execState) {
|
||||||
this._execState = execState
|
this._execState = execState
|
||||||
this.programMemory = execState.memory
|
this.variables = execState.variables
|
||||||
}
|
}
|
||||||
|
|
||||||
get execState() {
|
get execState() {
|
||||||
@ -201,7 +204,7 @@ export class KclManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
registerCallBacks({
|
registerCallBacks({
|
||||||
setProgramMemory,
|
setVariables,
|
||||||
setAst,
|
setAst,
|
||||||
setLogs,
|
setLogs,
|
||||||
setErrors,
|
setErrors,
|
||||||
@ -209,7 +212,7 @@ export class KclManager {
|
|||||||
setIsExecuting,
|
setIsExecuting,
|
||||||
setWasmInitFailed,
|
setWasmInitFailed,
|
||||||
}: {
|
}: {
|
||||||
setProgramMemory: (arg: ProgramMemory) => void
|
setVariables: (arg: VariableMap) => void
|
||||||
setAst: (arg: Node<Program>) => void
|
setAst: (arg: Node<Program>) => void
|
||||||
setLogs: (arg: string[]) => void
|
setLogs: (arg: string[]) => void
|
||||||
setErrors: (errors: KCLError[]) => void
|
setErrors: (errors: KCLError[]) => void
|
||||||
@ -217,7 +220,7 @@ export class KclManager {
|
|||||||
setIsExecuting: (arg: boolean) => void
|
setIsExecuting: (arg: boolean) => void
|
||||||
setWasmInitFailed: (arg: boolean) => void
|
setWasmInitFailed: (arg: boolean) => void
|
||||||
}) {
|
}) {
|
||||||
this._programMemoryCallBack = setProgramMemory
|
this._variablesCallBack = setVariables
|
||||||
this._astCallBack = setAst
|
this._astCallBack = setAst
|
||||||
this._logsCallBack = setLogs
|
this._logsCallBack = setLogs
|
||||||
this._kclErrorsCallBack = setErrors
|
this._kclErrorsCallBack = setErrors
|
||||||
@ -329,6 +332,7 @@ export class KclManager {
|
|||||||
ast,
|
ast,
|
||||||
path: codeManager.currentFilePath || undefined,
|
path: codeManager.currentFilePath || undefined,
|
||||||
engineCommandManager: this.engineCommandManager,
|
engineCommandManager: this.engineCommandManager,
|
||||||
|
isMock: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Program was not interrupted, setup the scene
|
// Program was not interrupted, setup the scene
|
||||||
@ -385,12 +389,12 @@ export class KclManager {
|
|||||||
this.addDiagnostics(isInterrupted ? [] : kclErrorsToDiagnostics(errors))
|
this.addDiagnostics(isInterrupted ? [] : kclErrorsToDiagnostics(errors))
|
||||||
this.execState = execState
|
this.execState = execState
|
||||||
if (!errors.length) {
|
if (!errors.length) {
|
||||||
this.lastSuccessfulProgramMemory = execState.memory
|
this.lastSuccessfulVariables = execState.variables
|
||||||
this.lastSuccessfulOperations = execState.operations
|
this.lastSuccessfulOperations = execState.operations
|
||||||
}
|
}
|
||||||
this.ast = { ...ast }
|
this.ast = { ...ast }
|
||||||
// updateArtifactGraph relies on updated executeState/programMemory
|
// updateArtifactGraph relies on updated executeState/variables
|
||||||
await this.engineCommandManager.updateArtifactGraph(execState.artifactGraph)
|
this.engineCommandManager.updateArtifactGraph(execState.artifactGraph)
|
||||||
this._executeCallback()
|
this._executeCallback()
|
||||||
if (!isInterrupted)
|
if (!isInterrupted)
|
||||||
sceneInfra.modelingSend({ type: 'code edit during sketch' })
|
sceneInfra.modelingSend({ type: 'code edit during sketch' })
|
||||||
@ -440,17 +444,16 @@ export class KclManager {
|
|||||||
const { logs, errors, execState } = await executeAst({
|
const { logs, errors, execState } = await executeAst({
|
||||||
ast: newAst,
|
ast: newAst,
|
||||||
engineCommandManager: this.engineCommandManager,
|
engineCommandManager: this.engineCommandManager,
|
||||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
isMock: true,
|
||||||
programMemoryOverride: ProgramMemory.empty(),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
this._logs = logs
|
this._logs = logs
|
||||||
this.addDiagnostics(kclErrorsToDiagnostics(errors))
|
this.addDiagnostics(kclErrorsToDiagnostics(errors))
|
||||||
|
|
||||||
this._execState = execState
|
this._execState = execState
|
||||||
this._programMemory = execState.memory
|
this._variables = execState.variables
|
||||||
if (!errors.length) {
|
if (!errors.length) {
|
||||||
this.lastSuccessfulProgramMemory = execState.memory
|
this.lastSuccessfulVariables = execState.variables
|
||||||
this.lastSuccessfulOperations = execState.operations
|
this.lastSuccessfulOperations = execState.operations
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,8 +15,7 @@ const mySketch001 = startSketchOn('XY')
|
|||||||
|> line(endAbsolute = [0.46, -5.82])
|
|> line(endAbsolute = [0.46, -5.82])
|
||||||
// |> rx(45, %)`
|
// |> rx(45, %)`
|
||||||
const execState = await enginelessExecutor(assertParse(code))
|
const execState = await enginelessExecutor(assertParse(code))
|
||||||
// @ts-ignore
|
const sketch001 = execState.variables['mySketch001']
|
||||||
const sketch001 = execState.memory.get('mySketch001')
|
|
||||||
expect(sketch001).toEqual({
|
expect(sketch001).toEqual({
|
||||||
type: 'Sketch',
|
type: 'Sketch',
|
||||||
value: {
|
value: {
|
||||||
@ -73,8 +72,7 @@ const mySketch001 = startSketchOn('XY')
|
|||||||
// |> rx(45, %)
|
// |> rx(45, %)
|
||||||
|> extrude(length = 2)`
|
|> extrude(length = 2)`
|
||||||
const execState = await enginelessExecutor(assertParse(code))
|
const execState = await enginelessExecutor(assertParse(code))
|
||||||
// @ts-ignore
|
const sketch001 = execState.variables['mySketch001']
|
||||||
const sketch001 = execState.memory.get('mySketch001')
|
|
||||||
expect(sketch001).toEqual({
|
expect(sketch001).toEqual({
|
||||||
type: 'Solid',
|
type: 'Solid',
|
||||||
value: {
|
value: {
|
||||||
@ -165,9 +163,9 @@ const sk2 = startSketchOn('XY')
|
|||||||
|
|
||||||
`
|
`
|
||||||
const execState = await enginelessExecutor(assertParse(code))
|
const execState = await enginelessExecutor(assertParse(code))
|
||||||
const programMemory = execState.memory
|
const variables = execState.variables
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const geos = [programMemory.get('theExtrude'), programMemory.get('sk2')]
|
const geos = [variables['theExtrude'], variables['sk2']]
|
||||||
expect(geos).toEqual([
|
expect(geos).toEqual([
|
||||||
{
|
{
|
||||||
type: 'Solid',
|
type: 'Solid',
|
||||||
|
@ -2,12 +2,12 @@ import fs from 'node:fs'
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
assertParse,
|
assertParse,
|
||||||
ProgramMemory,
|
|
||||||
Sketch,
|
Sketch,
|
||||||
initPromise,
|
initPromise,
|
||||||
sketchFromKclValue,
|
sketchFromKclValue,
|
||||||
defaultArtifactGraph,
|
defaultArtifactGraph,
|
||||||
topLevelRange,
|
topLevelRange,
|
||||||
|
VariableMap,
|
||||||
} from './wasm'
|
} from './wasm'
|
||||||
import { enginelessExecutor } from '../lib/testHelpers'
|
import { enginelessExecutor } from '../lib/testHelpers'
|
||||||
import { KCLError } from './errors'
|
import { KCLError } from './errors'
|
||||||
@ -21,13 +21,13 @@ describe('test executor', () => {
|
|||||||
const code = `const myVar = 5
|
const code = `const myVar = 5
|
||||||
const newVar = myVar + 1`
|
const newVar = myVar + 1`
|
||||||
const mem = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(mem.get('myVar')?.value).toBe(5)
|
expect(mem['myVar']?.value).toBe(5)
|
||||||
expect(mem.get('newVar')?.value).toBe(6)
|
expect(mem['newVar']?.value).toBe(6)
|
||||||
})
|
})
|
||||||
it('test assigning a var with a string', async () => {
|
it('test assigning a var with a string', async () => {
|
||||||
const code = `const myVar = "a str"`
|
const code = `const myVar = "a str"`
|
||||||
const mem = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(mem.get('myVar')?.value).toBe('a str')
|
expect(mem['myVar']?.value).toBe('a str')
|
||||||
})
|
})
|
||||||
it('test assigning a var by cont concatenating two strings string execute', async () => {
|
it('test assigning a var by cont concatenating two strings string execute', async () => {
|
||||||
const code = fs.readFileSync(
|
const code = fs.readFileSync(
|
||||||
@ -35,7 +35,7 @@ const newVar = myVar + 1`
|
|||||||
'utf-8'
|
'utf-8'
|
||||||
)
|
)
|
||||||
const mem = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(mem.get('myVar')?.value).toBe('a str another str')
|
expect(mem['myVar']?.value).toBe('a str another str')
|
||||||
})
|
})
|
||||||
it('fn funcN = () => {} execute', async () => {
|
it('fn funcN = () => {} execute', async () => {
|
||||||
const mem = await exe(
|
const mem = await exe(
|
||||||
@ -47,8 +47,8 @@ const newVar = myVar + 1`
|
|||||||
'const magicNum = funcN(9, theVar)',
|
'const magicNum = funcN(9, theVar)',
|
||||||
].join('\n')
|
].join('\n')
|
||||||
)
|
)
|
||||||
expect(mem.get('theVar')?.value).toBe(60)
|
expect(mem['theVar']?.value).toBe(60)
|
||||||
expect(mem.get('magicNum')?.value).toBe(69)
|
expect(mem['magicNum']?.value).toBe(69)
|
||||||
})
|
})
|
||||||
it('sketch declaration', async () => {
|
it('sketch declaration', async () => {
|
||||||
let code = `const mySketch = startSketchOn('XY')
|
let code = `const mySketch = startSketchOn('XY')
|
||||||
@ -60,7 +60,7 @@ const newVar = myVar + 1`
|
|||||||
`
|
`
|
||||||
const mem = await exe(code)
|
const mem = await exe(code)
|
||||||
// geo is three js buffer geometry and is very bloated to have in tests
|
// geo is three js buffer geometry and is very bloated to have in tests
|
||||||
const sk = mem.get('mySketch')
|
const sk = mem['mySketch']
|
||||||
expect(sk?.type).toEqual('Sketch')
|
expect(sk?.type).toEqual('Sketch')
|
||||||
if (sk?.type !== 'Sketch') {
|
if (sk?.type !== 'Sketch') {
|
||||||
return
|
return
|
||||||
@ -117,7 +117,7 @@ const newVar = myVar + 1`
|
|||||||
'const myVar = 5 + 1 |> myFn(%)',
|
'const myVar = 5 + 1 |> myFn(%)',
|
||||||
].join('\n')
|
].join('\n')
|
||||||
const mem = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(mem.get('myVar')?.value).toBe(7)
|
expect(mem['myVar']?.value).toBe(7)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Enable rotations #152
|
// Enable rotations #152
|
||||||
@ -130,15 +130,15 @@ const newVar = myVar + 1`
|
|||||||
// 'const rotated = rx(90, mySk1)',
|
// 'const rotated = rx(90, mySk1)',
|
||||||
// ].join('\n')
|
// ].join('\n')
|
||||||
// const mem = await exe(code)
|
// const mem = await exe(code)
|
||||||
// expect(mem.get('mySk1')?.value).toHaveLength(3)
|
// expect(mem['mySk1']?.value).toHaveLength(3)
|
||||||
// expect(mem.get('rotated')?.type).toBe('Sketch')
|
// expect(mem['rotated')?.type).toBe('Sketch']
|
||||||
// if (
|
// if (
|
||||||
// mem.get('mySk1')?.type !== 'Sketch' ||
|
// mem['mySk1']?.type !== 'Sketch' ||
|
||||||
// mem.get('rotated')?.type !== 'Sketch'
|
// mem['rotated']?.type !== 'Sketch'
|
||||||
// )
|
// )
|
||||||
// throw new Error('not a sketch')
|
// throw new Error('not a sketch')
|
||||||
// expect(mem.get('mySk1')?.rotation).toEqual([0, 0, 0, 1])
|
// expect(mem['mySk1']?.rotation).toEqual([0, 0, 0, 1])
|
||||||
// expect(mem.get('rotated')?.rotation.map((a) => a.toFixed(4))).toEqual([
|
// expect(mem['rotated']?.rotation.map((a) => a.toFixed(4))).toEqual([
|
||||||
// '0.7071',
|
// '0.7071',
|
||||||
// '0.0000',
|
// '0.0000',
|
||||||
// '0.0000',
|
// '0.0000',
|
||||||
@ -157,7 +157,7 @@ const newVar = myVar + 1`
|
|||||||
// ' |> rx(90, %)',
|
// ' |> rx(90, %)',
|
||||||
].join('\n')
|
].join('\n')
|
||||||
const mem = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(mem.get('mySk1')).toEqual({
|
expect(mem['mySk1']).toEqual({
|
||||||
type: 'Sketch',
|
type: 'Sketch',
|
||||||
value: {
|
value: {
|
||||||
type: 'Sketch',
|
type: 'Sketch',
|
||||||
@ -236,7 +236,7 @@ const newVar = myVar + 1`
|
|||||||
)
|
)
|
||||||
const mem = await exe(code)
|
const mem = await exe(code)
|
||||||
// TODO path to node is probably wrong here, zero indexes are not correct
|
// TODO path to node is probably wrong here, zero indexes are not correct
|
||||||
expect(mem.get('three')).toEqual({
|
expect(mem['three']).toEqual({
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
value: 3,
|
value: 3,
|
||||||
__meta: [
|
__meta: [
|
||||||
@ -245,7 +245,7 @@ const newVar = myVar + 1`
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
expect(mem.get('yo')).toEqual({
|
expect(mem['yo']).toEqual({
|
||||||
type: 'Array',
|
type: 'Array',
|
||||||
value: [
|
value: [
|
||||||
{ type: 'Number', value: 1, __meta: [{ sourceRange: [28, 29, 0] }] },
|
{ type: 'Number', value: 1, __meta: [{ sourceRange: [28, 29, 0] }] },
|
||||||
@ -263,9 +263,6 @@ const newVar = myVar + 1`
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
// Check that there are no other variables or environments.
|
|
||||||
expect(mem.numEnvironments()).toBe(1)
|
|
||||||
expect(mem.numVariables(0)).toBe(2)
|
|
||||||
})
|
})
|
||||||
it('execute object expression', async () => {
|
it('execute object expression', async () => {
|
||||||
const code = [
|
const code = [
|
||||||
@ -273,7 +270,7 @@ const newVar = myVar + 1`
|
|||||||
"const yo = {aStr: 'str', anum: 2, identifier: three, binExp: 4 + 5}",
|
"const yo = {aStr: 'str', anum: 2, identifier: three, binExp: 4 + 5}",
|
||||||
].join('\n')
|
].join('\n')
|
||||||
const mem = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(mem.get('yo')).toEqual({
|
expect(mem['yo']).toEqual({
|
||||||
type: 'Object',
|
type: 'Object',
|
||||||
value: {
|
value: {
|
||||||
aStr: {
|
aStr: {
|
||||||
@ -309,7 +306,7 @@ const newVar = myVar + 1`
|
|||||||
'\n'
|
'\n'
|
||||||
)
|
)
|
||||||
const mem = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(mem.get('myVar')).toEqual({
|
expect(mem['myVar']).toEqual({
|
||||||
type: 'String',
|
type: 'String',
|
||||||
value: '123',
|
value: '123',
|
||||||
__meta: [
|
__meta: [
|
||||||
@ -325,80 +322,80 @@ describe('testing math operators', () => {
|
|||||||
it('can sum', async () => {
|
it('can sum', async () => {
|
||||||
const code = ['const myVar = 1 + 2'].join('\n')
|
const code = ['const myVar = 1 + 2'].join('\n')
|
||||||
const mem = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(mem.get('myVar')?.value).toBe(3)
|
expect(mem['myVar']?.value).toBe(3)
|
||||||
})
|
})
|
||||||
it('can subtract', async () => {
|
it('can subtract', async () => {
|
||||||
const code = ['const myVar = 1 - 2'].join('\n')
|
const code = ['const myVar = 1 - 2'].join('\n')
|
||||||
const mem = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(mem.get('myVar')?.value).toBe(-1)
|
expect(mem['myVar']?.value).toBe(-1)
|
||||||
})
|
})
|
||||||
it('can multiply', async () => {
|
it('can multiply', async () => {
|
||||||
const code = ['const myVar = 1 * 2'].join('\n')
|
const code = ['const myVar = 1 * 2'].join('\n')
|
||||||
const mem = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(mem.get('myVar')?.value).toBe(2)
|
expect(mem['myVar']?.value).toBe(2)
|
||||||
})
|
})
|
||||||
it('can divide', async () => {
|
it('can divide', async () => {
|
||||||
const code = ['const myVar = 1 / 2'].join('\n')
|
const code = ['const myVar = 1 / 2'].join('\n')
|
||||||
const mem = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(mem.get('myVar')?.value).toBe(0.5)
|
expect(mem['myVar']?.value).toBe(0.5)
|
||||||
})
|
})
|
||||||
it('can modulus', async () => {
|
it('can modulus', async () => {
|
||||||
const code = ['const myVar = 5 % 2'].join('\n')
|
const code = ['const myVar = 5 % 2'].join('\n')
|
||||||
const mem = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(mem.get('myVar')?.value).toBe(1)
|
expect(mem['myVar']?.value).toBe(1)
|
||||||
})
|
})
|
||||||
it('can do multiple operations', async () => {
|
it('can do multiple operations', async () => {
|
||||||
const code = ['const myVar = 1 + 2 * 3'].join('\n')
|
const code = ['const myVar = 1 + 2 * 3'].join('\n')
|
||||||
const mem = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(mem.get('myVar')?.value).toBe(7)
|
expect(mem['myVar']?.value).toBe(7)
|
||||||
})
|
})
|
||||||
it('big example with parans', async () => {
|
it('big example with parans', async () => {
|
||||||
const code = ['const myVar = 1 + 2 * (3 - 4) / -5 + 6'].join('\n')
|
const code = ['const myVar = 1 + 2 * (3 - 4) / -5 + 6'].join('\n')
|
||||||
const mem = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(mem.get('myVar')?.value).toBe(7.4)
|
expect(mem['myVar']?.value).toBe(7.4)
|
||||||
})
|
})
|
||||||
it('with identifier', async () => {
|
it('with identifier', async () => {
|
||||||
const code = ['const yo = 6', 'const myVar = yo / 2'].join('\n')
|
const code = ['const yo = 6', 'const myVar = yo / 2'].join('\n')
|
||||||
const mem = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(mem.get('myVar')?.value).toBe(3)
|
expect(mem['myVar']?.value).toBe(3)
|
||||||
})
|
})
|
||||||
it('with lots of testing', async () => {
|
it('with lots of testing', async () => {
|
||||||
const code = ['const myVar = 2 * ((2 + 3 ) / 4 + 5)'].join('\n')
|
const code = ['const myVar = 2 * ((2 + 3 ) / 4 + 5)'].join('\n')
|
||||||
const mem = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(mem.get('myVar')?.value).toBe(12.5)
|
expect(mem['myVar']?.value).toBe(12.5)
|
||||||
})
|
})
|
||||||
it('with callExpression at start', async () => {
|
it('with callExpression at start', async () => {
|
||||||
const code = 'const myVar = min(4, 100) + 2'
|
const code = 'const myVar = min(4, 100) + 2'
|
||||||
const mem = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(mem.get('myVar')?.value).toBe(6)
|
expect(mem['myVar']?.value).toBe(6)
|
||||||
})
|
})
|
||||||
it('with callExpression at end', async () => {
|
it('with callExpression at end', async () => {
|
||||||
const code = 'const myVar = 2 + min(4, 100)'
|
const code = 'const myVar = 2 + min(4, 100)'
|
||||||
const mem = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(mem.get('myVar')?.value).toBe(6)
|
expect(mem['myVar']?.value).toBe(6)
|
||||||
})
|
})
|
||||||
it('with nested callExpression', async () => {
|
it('with nested callExpression', async () => {
|
||||||
const code = 'const myVar = 2 + min(100, legLen(5, 3))'
|
const code = 'const myVar = 2 + min(100, legLen(5, 3))'
|
||||||
const mem = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(mem.get('myVar')?.value).toBe(6)
|
expect(mem['myVar']?.value).toBe(6)
|
||||||
})
|
})
|
||||||
it('with unaryExpression', async () => {
|
it('with unaryExpression', async () => {
|
||||||
const code = 'const myVar = -min(100, 3)'
|
const code = 'const myVar = -min(100, 3)'
|
||||||
const mem = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(mem.get('myVar')?.value).toBe(-3)
|
expect(mem['myVar']?.value).toBe(-3)
|
||||||
})
|
})
|
||||||
it('with unaryExpression in callExpression', async () => {
|
it('with unaryExpression in callExpression', async () => {
|
||||||
const code = 'const myVar = min(-legLen(5, 4), 5)'
|
const code = 'const myVar = min(-legLen(5, 4), 5)'
|
||||||
const code2 = 'const myVar = min(5 , -legLen(5, 4))'
|
const code2 = 'const myVar = min(5 , -legLen(5, 4))'
|
||||||
const mem = await exe(code)
|
const mem = await exe(code)
|
||||||
const mem2 = await exe(code2)
|
const mem2 = await exe(code2)
|
||||||
expect(mem.get('myVar')?.value).toBe(-3)
|
expect(mem['myVar']?.value).toBe(-3)
|
||||||
expect(mem.get('myVar')?.value).toBe(mem2.get('myVar')?.value)
|
expect(mem['myVar']?.value).toBe(mem2['myVar']?.value)
|
||||||
})
|
})
|
||||||
it('with unaryExpression in ArrayExpression', async () => {
|
it('with unaryExpression in ArrayExpression', async () => {
|
||||||
const code = 'const myVar = [1,-legLen(5, 4)]'
|
const code = 'const myVar = [1,-legLen(5, 4)]'
|
||||||
const mem = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(mem.get('myVar')?.value).toEqual([
|
expect(mem['myVar']?.value).toEqual([
|
||||||
{
|
{
|
||||||
__meta: [
|
__meta: [
|
||||||
{
|
{
|
||||||
@ -426,7 +423,7 @@ describe('testing math operators', () => {
|
|||||||
'|> line(end = [-2.21, -legLen(5, min(3, 999))])',
|
'|> line(end = [-2.21, -legLen(5, min(3, 999))])',
|
||||||
].join('\n')
|
].join('\n')
|
||||||
const mem = await exe(code)
|
const mem = await exe(code)
|
||||||
const sketch = sketchFromKclValue(mem.get('part001'), 'part001')
|
const sketch = sketchFromKclValue(mem['part001'], 'part001')
|
||||||
// result of `-legLen(5, min(3, 999))` should be -4
|
// result of `-legLen(5, min(3, 999))` should be -4
|
||||||
const yVal = (sketch as Sketch).paths?.[0]?.to?.[1]
|
const yVal = (sketch as Sketch).paths?.[0]?.to?.[1]
|
||||||
expect(yVal).toBe(-4)
|
expect(yVal).toBe(-4)
|
||||||
@ -444,7 +441,7 @@ describe('testing math operators', () => {
|
|||||||
``,
|
``,
|
||||||
].join('\n')
|
].join('\n')
|
||||||
const mem = await exe(code)
|
const mem = await exe(code)
|
||||||
const sketch = sketchFromKclValue(mem.get('part001'), 'part001')
|
const sketch = sketchFromKclValue(mem['part001'], 'part001')
|
||||||
// expect -legLen(segLen('seg01'), myVar) to equal -4 setting the y value back to 0
|
// expect -legLen(segLen('seg01'), myVar) to equal -4 setting the y value back to 0
|
||||||
expect((sketch as Sketch).paths?.[1]?.from).toEqual([3, 4])
|
expect((sketch as Sketch).paths?.[1]?.from).toEqual([3, 4])
|
||||||
expect((sketch as Sketch).paths?.[1]?.to).toEqual([6, 0])
|
expect((sketch as Sketch).paths?.[1]?.to).toEqual([6, 0])
|
||||||
@ -454,7 +451,7 @@ describe('testing math operators', () => {
|
|||||||
)
|
)
|
||||||
const removedUnaryExpMem = await exe(removedUnaryExp)
|
const removedUnaryExpMem = await exe(removedUnaryExp)
|
||||||
const removedUnaryExpMemSketch = sketchFromKclValue(
|
const removedUnaryExpMemSketch = sketchFromKclValue(
|
||||||
removedUnaryExpMem.get('part001'),
|
removedUnaryExpMem['part001'],
|
||||||
'part001'
|
'part001'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -464,12 +461,12 @@ describe('testing math operators', () => {
|
|||||||
it('with nested callExpression and binaryExpression', async () => {
|
it('with nested callExpression and binaryExpression', async () => {
|
||||||
const code = 'const myVar = 2 + min(100, -1 + legLen(5, 3))'
|
const code = 'const myVar = 2 + min(100, -1 + legLen(5, 3))'
|
||||||
const mem = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(mem.get('myVar')?.value).toBe(5)
|
expect(mem['myVar']?.value).toBe(5)
|
||||||
})
|
})
|
||||||
it('can do power of math', async () => {
|
it('can do power of math', async () => {
|
||||||
const code = 'const myNeg2 = 4 ^ 2 - 3 ^ 2 * 2'
|
const code = 'const myNeg2 = 4 ^ 2 - 3 ^ 2 * 2'
|
||||||
const mem = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(mem.get('myNeg2')?.value).toBe(-2)
|
expect(mem['myNeg2']?.value).toBe(-2)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -498,12 +495,9 @@ const theExtrude = startSketchOn('XY')
|
|||||||
|
|
||||||
// helpers
|
// helpers
|
||||||
|
|
||||||
async function exe(
|
async function exe(code: string, variables: VariableMap = {}) {
|
||||||
code: string,
|
|
||||||
programMemory: ProgramMemory = ProgramMemory.empty()
|
|
||||||
) {
|
|
||||||
const ast = assertParse(code)
|
const ast = assertParse(code)
|
||||||
|
|
||||||
const execState = await enginelessExecutor(ast, programMemory)
|
const execState = await enginelessExecutor(ast, true, undefined, variables)
|
||||||
return execState.memory
|
return execState.variables
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { assertParse, initPromise, programMemoryInit } from './wasm'
|
import { assertParse, initPromise } from './wasm'
|
||||||
import { enginelessExecutor } from '../lib/testHelpers'
|
import { enginelessExecutor } from '../lib/testHelpers'
|
||||||
|
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
@ -32,7 +32,7 @@ child_process.spawnSync('git', [
|
|||||||
'clone',
|
'clone',
|
||||||
'--single-branch',
|
'--single-branch',
|
||||||
'--branch',
|
'--branch',
|
||||||
'achalmers/kw-appearance',
|
'achalmers/kw-pattern',
|
||||||
URL_GIT_KCL_SAMPLES,
|
URL_GIT_KCL_SAMPLES,
|
||||||
DIR_KCL_SAMPLES,
|
DIR_KCL_SAMPLES,
|
||||||
])
|
])
|
||||||
@ -72,7 +72,7 @@ describe('Test KCL Samples from public Github repository', () => {
|
|||||||
const ast = assertParse(code)
|
const ast = assertParse(code)
|
||||||
await enginelessExecutor(
|
await enginelessExecutor(
|
||||||
ast,
|
ast,
|
||||||
programMemoryInit(),
|
false,
|
||||||
file.pathFromProjectDirectoryToFirstFile
|
file.pathFromProjectDirectoryToFirstFile
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import {
|
import {
|
||||||
Program,
|
Program,
|
||||||
executor,
|
executeWithEngine,
|
||||||
ProgramMemory,
|
executeMock,
|
||||||
kclLint,
|
kclLint,
|
||||||
emptyExecState,
|
emptyExecState,
|
||||||
ExecState,
|
ExecState,
|
||||||
|
VariableMap,
|
||||||
} from 'lang/wasm'
|
} from 'lang/wasm'
|
||||||
import { enginelessExecutor } from 'lib/testHelpers'
|
|
||||||
import { EngineCommandManager } from 'lang/std/engineConnection'
|
import { EngineCommandManager } from 'lang/std/engineConnection'
|
||||||
import { KCLError } from 'lang/errors'
|
import { KCLError } from 'lang/errors'
|
||||||
import { Diagnostic } from '@codemirror/lint'
|
import { Diagnostic } from '@codemirror/lint'
|
||||||
@ -50,14 +50,16 @@ export async function executeAst({
|
|||||||
ast,
|
ast,
|
||||||
path,
|
path,
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
// If you set programMemoryOverride we assume you mean mock mode. Since that
|
isMock,
|
||||||
// is the only way to go about it.
|
usePrevMemory,
|
||||||
programMemoryOverride,
|
variables,
|
||||||
}: {
|
}: {
|
||||||
ast: Node<Program>
|
ast: Node<Program>
|
||||||
path?: string
|
path?: string
|
||||||
engineCommandManager: EngineCommandManager
|
engineCommandManager: EngineCommandManager
|
||||||
programMemoryOverride?: ProgramMemory
|
isMock: boolean
|
||||||
|
usePrevMemory?: boolean
|
||||||
|
variables?: VariableMap
|
||||||
isInterrupted?: boolean
|
isInterrupted?: boolean
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
logs: string[]
|
logs: string[]
|
||||||
@ -66,9 +68,9 @@ export async function executeAst({
|
|||||||
isInterrupted: boolean
|
isInterrupted: boolean
|
||||||
}> {
|
}> {
|
||||||
try {
|
try {
|
||||||
const execState = await (programMemoryOverride
|
const execState = await (isMock
|
||||||
? enginelessExecutor(ast, programMemoryOverride, path)
|
? executeMock(ast, usePrevMemory, path, variables)
|
||||||
: executor(ast, engineCommandManager, path))
|
: executeWithEngine(ast, engineCommandManager, path))
|
||||||
|
|
||||||
await engineCommandManager.waitForAllCommands()
|
await engineCommandManager.waitForAllCommands()
|
||||||
return {
|
return {
|
||||||
|
@ -315,7 +315,7 @@ yo2 = hmm([identifierGuy + 5])`
|
|||||||
const startIndex = code.indexOf('100 + 100') + 1
|
const startIndex = code.indexOf('100 + 100') + 1
|
||||||
const { modifiedAst } = moveValueIntoNewVariable(
|
const { modifiedAst } = moveValueIntoNewVariable(
|
||||||
ast,
|
ast,
|
||||||
execState.memory,
|
execState.variables,
|
||||||
topLevelRange(startIndex, startIndex),
|
topLevelRange(startIndex, startIndex),
|
||||||
'newVar'
|
'newVar'
|
||||||
)
|
)
|
||||||
@ -329,7 +329,7 @@ yo2 = hmm([identifierGuy + 5])`
|
|||||||
const startIndex = code.indexOf('2.8') + 1
|
const startIndex = code.indexOf('2.8') + 1
|
||||||
const { modifiedAst } = moveValueIntoNewVariable(
|
const { modifiedAst } = moveValueIntoNewVariable(
|
||||||
ast,
|
ast,
|
||||||
execState.memory,
|
execState.variables,
|
||||||
topLevelRange(startIndex, startIndex),
|
topLevelRange(startIndex, startIndex),
|
||||||
'newVar'
|
'newVar'
|
||||||
)
|
)
|
||||||
@ -343,7 +343,7 @@ yo2 = hmm([identifierGuy + 5])`
|
|||||||
const startIndex = code.indexOf('def(')
|
const startIndex = code.indexOf('def(')
|
||||||
const { modifiedAst } = moveValueIntoNewVariable(
|
const { modifiedAst } = moveValueIntoNewVariable(
|
||||||
ast,
|
ast,
|
||||||
execState.memory,
|
execState.variables,
|
||||||
topLevelRange(startIndex, startIndex),
|
topLevelRange(startIndex, startIndex),
|
||||||
'newVar'
|
'newVar'
|
||||||
)
|
)
|
||||||
@ -357,7 +357,7 @@ yo2 = hmm([identifierGuy + 5])`
|
|||||||
const startIndex = code.indexOf('jkl(') + 1
|
const startIndex = code.indexOf('jkl(') + 1
|
||||||
const { modifiedAst } = moveValueIntoNewVariable(
|
const { modifiedAst } = moveValueIntoNewVariable(
|
||||||
ast,
|
ast,
|
||||||
execState.memory,
|
execState.variables,
|
||||||
topLevelRange(startIndex, startIndex),
|
topLevelRange(startIndex, startIndex),
|
||||||
'newVar'
|
'newVar'
|
||||||
)
|
)
|
||||||
@ -371,7 +371,7 @@ yo2 = hmm([identifierGuy + 5])`
|
|||||||
const startIndex = code.indexOf('identifierGuy +') + 1
|
const startIndex = code.indexOf('identifierGuy +') + 1
|
||||||
const { modifiedAst } = moveValueIntoNewVariable(
|
const { modifiedAst } = moveValueIntoNewVariable(
|
||||||
ast,
|
ast,
|
||||||
execState.memory,
|
execState.variables,
|
||||||
topLevelRange(startIndex, startIndex),
|
topLevelRange(startIndex, startIndex),
|
||||||
'newVar'
|
'newVar'
|
||||||
)
|
)
|
||||||
@ -557,7 +557,7 @@ describe('Testing deleteSegmentFromPipeExpression', () => {
|
|||||||
const modifiedAst = deleteSegmentFromPipeExpression(
|
const modifiedAst = deleteSegmentFromPipeExpression(
|
||||||
[],
|
[],
|
||||||
ast,
|
ast,
|
||||||
execState.memory,
|
execState.variables,
|
||||||
code,
|
code,
|
||||||
pathToNode
|
pathToNode
|
||||||
)
|
)
|
||||||
@ -639,7 +639,7 @@ ${!replace1 ? ` |> ${line}\n` : ''} |> angledLine([-65, ${
|
|||||||
const modifiedAst = deleteSegmentFromPipeExpression(
|
const modifiedAst = deleteSegmentFromPipeExpression(
|
||||||
dependentSegments,
|
dependentSegments,
|
||||||
ast,
|
ast,
|
||||||
execState.memory,
|
execState.variables,
|
||||||
code,
|
code,
|
||||||
pathToNode
|
pathToNode
|
||||||
)
|
)
|
||||||
@ -745,7 +745,7 @@ describe('Testing removeSingleConstraintInfo', () => {
|
|||||||
pathToNode,
|
pathToNode,
|
||||||
argPosition,
|
argPosition,
|
||||||
ast,
|
ast,
|
||||||
execState.memory
|
execState.variables
|
||||||
)
|
)
|
||||||
if (!mod) return new Error('mod is undefined')
|
if (!mod) return new Error('mod is undefined')
|
||||||
const recastCode = recast(mod.modifiedAst)
|
const recastCode = recast(mod.modifiedAst)
|
||||||
@ -794,7 +794,7 @@ describe('Testing removeSingleConstraintInfo', () => {
|
|||||||
pathToNode,
|
pathToNode,
|
||||||
argPosition,
|
argPosition,
|
||||||
ast,
|
ast,
|
||||||
execState.memory
|
execState.variables
|
||||||
)
|
)
|
||||||
if (!mod) return new Error('mod is undefined')
|
if (!mod) return new Error('mod is undefined')
|
||||||
const recastCode = recast(mod.modifiedAst)
|
const recastCode = recast(mod.modifiedAst)
|
||||||
@ -978,7 +978,7 @@ sketch002 = startSketchOn({
|
|||||||
codeRef: codeRefFromRange(range, ast),
|
codeRef: codeRefFromRange(range, ast),
|
||||||
artifact,
|
artifact,
|
||||||
},
|
},
|
||||||
execState.memory,
|
execState.variables,
|
||||||
async () => {
|
async () => {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||||
return {
|
return {
|
||||||
|
@ -18,13 +18,13 @@ import {
|
|||||||
UnaryExpression,
|
UnaryExpression,
|
||||||
BinaryExpression,
|
BinaryExpression,
|
||||||
PathToNode,
|
PathToNode,
|
||||||
ProgramMemory,
|
|
||||||
SourceRange,
|
SourceRange,
|
||||||
sketchFromKclValue,
|
sketchFromKclValue,
|
||||||
isPathToNodeNumber,
|
isPathToNodeNumber,
|
||||||
parse,
|
parse,
|
||||||
formatNumber,
|
formatNumber,
|
||||||
ArtifactGraph,
|
ArtifactGraph,
|
||||||
|
VariableMap,
|
||||||
} from './wasm'
|
} from './wasm'
|
||||||
import {
|
import {
|
||||||
isNodeSafeToReplacePath,
|
isNodeSafeToReplacePath,
|
||||||
@ -1230,7 +1230,7 @@ export function replaceValueAtNodePath({
|
|||||||
|
|
||||||
export function moveValueIntoNewVariablePath(
|
export function moveValueIntoNewVariablePath(
|
||||||
ast: Node<Program>,
|
ast: Node<Program>,
|
||||||
programMemory: ProgramMemory,
|
memVars: VariableMap,
|
||||||
pathToNode: PathToNode,
|
pathToNode: PathToNode,
|
||||||
variableName: string
|
variableName: string
|
||||||
): {
|
): {
|
||||||
@ -1243,11 +1243,7 @@ export function moveValueIntoNewVariablePath(
|
|||||||
|
|
||||||
if (!isSafe || value.type === 'Identifier') return { modifiedAst: ast }
|
if (!isSafe || value.type === 'Identifier') return { modifiedAst: ast }
|
||||||
|
|
||||||
const { insertIndex } = findAllPreviousVariablesPath(
|
const { insertIndex } = findAllPreviousVariablesPath(ast, memVars, pathToNode)
|
||||||
ast,
|
|
||||||
programMemory,
|
|
||||||
pathToNode
|
|
||||||
)
|
|
||||||
let _node = structuredClone(ast)
|
let _node = structuredClone(ast)
|
||||||
const boop = replacer(_node, variableName)
|
const boop = replacer(_node, variableName)
|
||||||
if (trap(boop)) return { modifiedAst: ast }
|
if (trap(boop)) return { modifiedAst: ast }
|
||||||
@ -1263,7 +1259,7 @@ export function moveValueIntoNewVariablePath(
|
|||||||
|
|
||||||
export function moveValueIntoNewVariable(
|
export function moveValueIntoNewVariable(
|
||||||
ast: Node<Program>,
|
ast: Node<Program>,
|
||||||
programMemory: ProgramMemory,
|
memVars: VariableMap,
|
||||||
sourceRange: SourceRange,
|
sourceRange: SourceRange,
|
||||||
variableName: string
|
variableName: string
|
||||||
): {
|
): {
|
||||||
@ -1275,11 +1271,7 @@ export function moveValueIntoNewVariable(
|
|||||||
const { isSafe, value, replacer } = meta
|
const { isSafe, value, replacer } = meta
|
||||||
if (!isSafe || value.type === 'Identifier') return { modifiedAst: ast }
|
if (!isSafe || value.type === 'Identifier') return { modifiedAst: ast }
|
||||||
|
|
||||||
const { insertIndex } = findAllPreviousVariables(
|
const { insertIndex } = findAllPreviousVariables(ast, memVars, sourceRange)
|
||||||
ast,
|
|
||||||
programMemory,
|
|
||||||
sourceRange
|
|
||||||
)
|
|
||||||
let _node = structuredClone(ast)
|
let _node = structuredClone(ast)
|
||||||
const replaced = replacer(_node, variableName)
|
const replaced = replacer(_node, variableName)
|
||||||
if (trap(replaced)) return { modifiedAst: ast }
|
if (trap(replaced)) return { modifiedAst: ast }
|
||||||
@ -1301,7 +1293,7 @@ export function moveValueIntoNewVariable(
|
|||||||
export function deleteSegmentFromPipeExpression(
|
export function deleteSegmentFromPipeExpression(
|
||||||
dependentRanges: SourceRange[],
|
dependentRanges: SourceRange[],
|
||||||
modifiedAst: Node<Program>,
|
modifiedAst: Node<Program>,
|
||||||
programMemory: ProgramMemory,
|
memVars: VariableMap,
|
||||||
code: string,
|
code: string,
|
||||||
pathToNode: PathToNode
|
pathToNode: PathToNode
|
||||||
): Node<Program> | Error {
|
): Node<Program> | Error {
|
||||||
@ -1333,7 +1325,7 @@ export function deleteSegmentFromPipeExpression(
|
|||||||
callExp.shallowPath,
|
callExp.shallowPath,
|
||||||
constraintInfo.argPosition,
|
constraintInfo.argPosition,
|
||||||
_modifiedAst,
|
_modifiedAst,
|
||||||
programMemory
|
memVars
|
||||||
)
|
)
|
||||||
if (!transform) return
|
if (!transform) return
|
||||||
_modifiedAst = transform.modifiedAst
|
_modifiedAst = transform.modifiedAst
|
||||||
@ -1362,7 +1354,7 @@ export function removeSingleConstraintInfo(
|
|||||||
pathToCallExp: PathToNode,
|
pathToCallExp: PathToNode,
|
||||||
argDetails: SimplifiedArgDetails,
|
argDetails: SimplifiedArgDetails,
|
||||||
ast: Node<Program>,
|
ast: Node<Program>,
|
||||||
programMemory: ProgramMemory
|
memVars: VariableMap
|
||||||
):
|
):
|
||||||
| {
|
| {
|
||||||
modifiedAst: Node<Program>
|
modifiedAst: Node<Program>
|
||||||
@ -1379,7 +1371,7 @@ export function removeSingleConstraintInfo(
|
|||||||
ast,
|
ast,
|
||||||
selectionRanges: [pathToCallExp],
|
selectionRanges: [pathToCallExp],
|
||||||
transformInfos: [transform],
|
transformInfos: [transform],
|
||||||
programMemory,
|
memVars,
|
||||||
referenceSegName: '',
|
referenceSegName: '',
|
||||||
})
|
})
|
||||||
if (err(retval)) return false
|
if (err(retval)) return false
|
||||||
@ -1389,7 +1381,7 @@ export function removeSingleConstraintInfo(
|
|||||||
export async function deleteFromSelection(
|
export async function deleteFromSelection(
|
||||||
ast: Node<Program>,
|
ast: Node<Program>,
|
||||||
selection: Selection,
|
selection: Selection,
|
||||||
programMemory: ProgramMemory,
|
variables: VariableMap,
|
||||||
getFaceDetails: (id: string) => Promise<Models['FaceIsPlanar_type']> = () =>
|
getFaceDetails: (id: string) => Promise<Models['FaceIsPlanar_type']> = () =>
|
||||||
({} as any)
|
({} as any)
|
||||||
): Promise<Node<Program> | Error> {
|
): Promise<Node<Program> | Error> {
|
||||||
@ -1518,7 +1510,7 @@ export async function deleteFromSelection(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
const sketchToPreserve = sketchFromKclValue(
|
const sketchToPreserve = sketchFromKclValue(
|
||||||
programMemory.get(sketchName),
|
variables[sketchName],
|
||||||
sketchName
|
sketchName
|
||||||
)
|
)
|
||||||
if (err(sketchToPreserve)) return sketchToPreserve
|
if (err(sketchToPreserve)) return sketchToPreserve
|
||||||
|
@ -298,7 +298,7 @@ export function getPathToExtrudeForSegmentSelection(
|
|||||||
const sketchVar = varDecNode.node.declaration.id.name
|
const sketchVar = varDecNode.node.declaration.id.name
|
||||||
|
|
||||||
const sketch = sketchFromKclValue(
|
const sketch = sketchFromKclValue(
|
||||||
dependencies.kclManager.programMemory.get(sketchVar),
|
dependencies.kclManager.variables[sketchVar],
|
||||||
sketchVar
|
sketchVar
|
||||||
)
|
)
|
||||||
if (trap(sketch)) return sketch
|
if (trap(sketch)) return sketch
|
||||||
|
@ -9,7 +9,6 @@ import {
|
|||||||
CallExpression,
|
CallExpression,
|
||||||
VariableDeclarator,
|
VariableDeclarator,
|
||||||
} from './wasm'
|
} from './wasm'
|
||||||
import { ProgramMemory } from 'lang/wasm'
|
|
||||||
import {
|
import {
|
||||||
findAllPreviousVariables,
|
findAllPreviousVariables,
|
||||||
isNodeSafeToReplace,
|
isNodeSafeToReplace,
|
||||||
@ -63,7 +62,7 @@ variableBelowShouldNotBeIncluded = 3
|
|||||||
|
|
||||||
const { variables, bodyPath, insertIndex } = findAllPreviousVariables(
|
const { variables, bodyPath, insertIndex } = findAllPreviousVariables(
|
||||||
ast,
|
ast,
|
||||||
execState.memory,
|
execState.variables,
|
||||||
topLevelRange(rangeStart, rangeStart)
|
topLevelRange(rangeStart, rangeStart)
|
||||||
)
|
)
|
||||||
expect(variables).toEqual([
|
expect(variables).toEqual([
|
||||||
@ -398,7 +397,7 @@ part001 = startSketchAt([-1.41, 3.46])
|
|||||||
selection: {
|
selection: {
|
||||||
codeRef: codeRefFromRange(topLevelRange(100, 101), ast),
|
codeRef: codeRefFromRange(topLevelRange(100, 101), ast),
|
||||||
},
|
},
|
||||||
programMemory: execState.memory,
|
memVars: execState.variables,
|
||||||
})
|
})
|
||||||
expect(result).toEqual(true)
|
expect(result).toEqual(true)
|
||||||
})
|
})
|
||||||
@ -418,7 +417,7 @@ part001 = startSketchAt([-1.41, 3.46])
|
|||||||
selection: {
|
selection: {
|
||||||
codeRef: codeRefFromRange(topLevelRange(100, 101), ast),
|
codeRef: codeRefFromRange(topLevelRange(100, 101), ast),
|
||||||
},
|
},
|
||||||
programMemory: execState.memory,
|
memVars: execState.variables,
|
||||||
})
|
})
|
||||||
expect(result).toEqual(true)
|
expect(result).toEqual(true)
|
||||||
})
|
})
|
||||||
@ -432,7 +431,7 @@ part001 = startSketchAt([-1.41, 3.46])
|
|||||||
selection: {
|
selection: {
|
||||||
codeRef: codeRefFromRange(topLevelRange(10, 11), ast),
|
codeRef: codeRefFromRange(topLevelRange(10, 11), ast),
|
||||||
},
|
},
|
||||||
programMemory: execState.memory,
|
memVars: execState.variables,
|
||||||
})
|
})
|
||||||
expect(result).toEqual(false)
|
expect(result).toEqual(false)
|
||||||
})
|
})
|
||||||
@ -722,7 +721,7 @@ describe('Testing specific sketch getNodeFromPath workflow', () => {
|
|||||||
const sketchPathToNode = getNodePathFromSourceRange(ast, sketchRange)
|
const sketchPathToNode = getNodePathFromSourceRange(ast, sketchRange)
|
||||||
const modifiedAst = addCallExpressionsToPipe({
|
const modifiedAst = addCallExpressionsToPipe({
|
||||||
node: ast,
|
node: ast,
|
||||||
programMemory: ProgramMemory.empty(),
|
variables: {},
|
||||||
pathToNode: sketchPathToNode,
|
pathToNode: sketchPathToNode,
|
||||||
expressions: [
|
expressions: [
|
||||||
createCallExpressionStdLib(
|
createCallExpressionStdLib(
|
||||||
@ -777,7 +776,7 @@ describe('Testing specific sketch getNodeFromPath workflow', () => {
|
|||||||
const sketchPathToNode = getNodePathFromSourceRange(ast, sketchRange)
|
const sketchPathToNode = getNodePathFromSourceRange(ast, sketchRange)
|
||||||
const modifiedAst = addCloseToPipe({
|
const modifiedAst = addCloseToPipe({
|
||||||
node: ast,
|
node: ast,
|
||||||
programMemory: ProgramMemory.empty(),
|
variables: {},
|
||||||
pathToNode: sketchPathToNode,
|
pathToNode: sketchPathToNode,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -12,7 +12,6 @@ import {
|
|||||||
PathToNode,
|
PathToNode,
|
||||||
PipeExpression,
|
PipeExpression,
|
||||||
Program,
|
Program,
|
||||||
ProgramMemory,
|
|
||||||
ReturnStatement,
|
ReturnStatement,
|
||||||
sketchFromKclValue,
|
sketchFromKclValue,
|
||||||
sketchFromKclValueOptional,
|
sketchFromKclValueOptional,
|
||||||
@ -26,6 +25,7 @@ import {
|
|||||||
kclSettings,
|
kclSettings,
|
||||||
unitLenToUnitLength,
|
unitLenToUnitLength,
|
||||||
unitAngToUnitAngle,
|
unitAngToUnitAngle,
|
||||||
|
VariableMap,
|
||||||
} from './wasm'
|
} from './wasm'
|
||||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||||
import { createIdentifier, splitPathAtLastIndex } from './modifyAst'
|
import { createIdentifier, splitPathAtLastIndex } from './modifyAst'
|
||||||
@ -288,7 +288,7 @@ export interface PrevVariable<T> {
|
|||||||
|
|
||||||
export function findAllPreviousVariablesPath(
|
export function findAllPreviousVariablesPath(
|
||||||
ast: Program,
|
ast: Program,
|
||||||
programMemory: ProgramMemory,
|
memVars: VariableMap,
|
||||||
path: PathToNode,
|
path: PathToNode,
|
||||||
type: 'number' | 'string' = 'number'
|
type: 'number' | 'string' = 'number'
|
||||||
): {
|
): {
|
||||||
@ -326,7 +326,7 @@ export function findAllPreviousVariablesPath(
|
|||||||
bodyItems?.forEach?.((item) => {
|
bodyItems?.forEach?.((item) => {
|
||||||
if (item.type !== 'VariableDeclaration' || item.end > startRange) return
|
if (item.type !== 'VariableDeclaration' || item.end > startRange) return
|
||||||
const varName = item.declaration.id.name
|
const varName = item.declaration.id.name
|
||||||
const varValue = programMemory?.get(varName)
|
const varValue = memVars[varName]
|
||||||
if (!varValue || typeof varValue?.value !== type) return
|
if (!varValue || typeof varValue?.value !== type) return
|
||||||
variables.push({
|
variables.push({
|
||||||
key: varName,
|
key: varName,
|
||||||
@ -343,7 +343,7 @@ export function findAllPreviousVariablesPath(
|
|||||||
|
|
||||||
export function findAllPreviousVariables(
|
export function findAllPreviousVariables(
|
||||||
ast: Program,
|
ast: Program,
|
||||||
programMemory: ProgramMemory,
|
memVars: VariableMap,
|
||||||
sourceRange: SourceRange,
|
sourceRange: SourceRange,
|
||||||
type: 'number' | 'string' = 'number'
|
type: 'number' | 'string' = 'number'
|
||||||
): {
|
): {
|
||||||
@ -352,7 +352,7 @@ export function findAllPreviousVariables(
|
|||||||
insertIndex: number
|
insertIndex: number
|
||||||
} {
|
} {
|
||||||
const path = getNodePathFromSourceRange(ast, sourceRange)
|
const path = getNodePathFromSourceRange(ast, sourceRange)
|
||||||
return findAllPreviousVariablesPath(ast, programMemory, path, type)
|
return findAllPreviousVariablesPath(ast, memVars, path, type)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReplacerFn = (
|
type ReplacerFn = (
|
||||||
@ -486,7 +486,7 @@ function isTypeInArrayExp(
|
|||||||
export function isLinesParallelAndConstrained(
|
export function isLinesParallelAndConstrained(
|
||||||
ast: Program,
|
ast: Program,
|
||||||
artifactGraph: ArtifactGraph,
|
artifactGraph: ArtifactGraph,
|
||||||
programMemory: ProgramMemory,
|
memVars: VariableMap,
|
||||||
primaryLine: Selection,
|
primaryLine: Selection,
|
||||||
secondaryLine: Selection
|
secondaryLine: Selection
|
||||||
):
|
):
|
||||||
@ -516,7 +516,7 @@ export function isLinesParallelAndConstrained(
|
|||||||
if (err(_varDec)) return _varDec
|
if (err(_varDec)) return _varDec
|
||||||
const varDec = _varDec.node
|
const varDec = _varDec.node
|
||||||
const varName = (varDec as VariableDeclaration)?.declaration.id?.name
|
const varName = (varDec as VariableDeclaration)?.declaration.id?.name
|
||||||
const sg = sketchFromKclValue(programMemory?.get(varName), varName)
|
const sg = sketchFromKclValue(memVars[varName], varName)
|
||||||
if (err(sg)) return sg
|
if (err(sg)) return sg
|
||||||
const _primarySegment = getSketchSegmentFromSourceRange(
|
const _primarySegment = getSketchSegmentFromSourceRange(
|
||||||
sg,
|
sg,
|
||||||
@ -529,7 +529,7 @@ export function isLinesParallelAndConstrained(
|
|||||||
if (err(_varDec2)) return _varDec2
|
if (err(_varDec2)) return _varDec2
|
||||||
const varDec2 = _varDec2.node
|
const varDec2 = _varDec2.node
|
||||||
const varName2 = (varDec2 as VariableDeclaration)?.declaration.id?.name
|
const varName2 = (varDec2 as VariableDeclaration)?.declaration.id?.name
|
||||||
const sg2 = sketchFromKclValue(programMemory?.get(varName2), varName2)
|
const sg2 = sketchFromKclValue(memVars[varName2], varName2)
|
||||||
if (err(sg2)) return sg2
|
if (err(sg2)) return sg2
|
||||||
|
|
||||||
const _segment = getSketchSegmentFromSourceRange(
|
const _segment = getSketchSegmentFromSourceRange(
|
||||||
@ -603,11 +603,11 @@ export function isLinesParallelAndConstrained(
|
|||||||
export function hasExtrudeSketch({
|
export function hasExtrudeSketch({
|
||||||
ast,
|
ast,
|
||||||
selection,
|
selection,
|
||||||
programMemory,
|
memVars,
|
||||||
}: {
|
}: {
|
||||||
ast: Program
|
ast: Program
|
||||||
selection: Selection
|
selection: Selection
|
||||||
programMemory: ProgramMemory
|
memVars: VariableMap
|
||||||
}): boolean {
|
}): boolean {
|
||||||
const varDecMeta = getNodeFromPath<VariableDeclaration>(
|
const varDecMeta = getNodeFromPath<VariableDeclaration>(
|
||||||
ast,
|
ast,
|
||||||
@ -621,7 +621,7 @@ export function hasExtrudeSketch({
|
|||||||
const varDec = varDecMeta.node
|
const varDec = varDecMeta.node
|
||||||
if (varDec.type !== 'VariableDeclaration') return false
|
if (varDec.type !== 'VariableDeclaration') return false
|
||||||
const varName = varDec.declaration.id.name
|
const varName = varDec.declaration.id.name
|
||||||
const varValue = programMemory?.get(varName)
|
const varValue = memVars[varName]
|
||||||
return (
|
return (
|
||||||
varValue?.type === 'Solid' ||
|
varValue?.type === 'Solid' ||
|
||||||
!(sketchFromKclValueOptional(varValue, varName) instanceof Reason)
|
!(sketchFromKclValueOptional(varValue, varName) instanceof Reason)
|
||||||
|
@ -124,7 +124,7 @@ describe('testing changeSketchArguments', () => {
|
|||||||
const sourceStart = code.indexOf(lineToChange)
|
const sourceStart = code.indexOf(lineToChange)
|
||||||
const changeSketchArgsRetVal = changeSketchArguments(
|
const changeSketchArgsRetVal = changeSketchArguments(
|
||||||
ast,
|
ast,
|
||||||
execState.memory,
|
execState.variables,
|
||||||
{
|
{
|
||||||
type: 'sourceRange',
|
type: 'sourceRange',
|
||||||
sourceRange: topLevelRange(
|
sourceRange: topLevelRange(
|
||||||
@ -160,7 +160,7 @@ mySketch001 = startSketchOn('XY')
|
|||||||
expect(sourceStart).toBe(89)
|
expect(sourceStart).toBe(89)
|
||||||
const newSketchLnRetVal = addNewSketchLn({
|
const newSketchLnRetVal = addNewSketchLn({
|
||||||
node: ast,
|
node: ast,
|
||||||
programMemory: execState.memory,
|
variables: execState.variables,
|
||||||
input: {
|
input: {
|
||||||
type: 'straight-segment',
|
type: 'straight-segment',
|
||||||
from: [0, 0],
|
from: [0, 0],
|
||||||
@ -190,7 +190,7 @@ mySketch001 = startSketchOn('XY')
|
|||||||
|
|
||||||
const modifiedAst2 = addCloseToPipe({
|
const modifiedAst2 = addCloseToPipe({
|
||||||
node: ast,
|
node: ast,
|
||||||
programMemory: execState.memory,
|
variables: execState.variables,
|
||||||
pathToNode: [
|
pathToNode: [
|
||||||
['body', ''],
|
['body', ''],
|
||||||
[0, 'index'],
|
[0, 'index'],
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import {
|
import {
|
||||||
ProgramMemory,
|
|
||||||
Path,
|
Path,
|
||||||
Sketch,
|
Sketch,
|
||||||
SourceRange,
|
SourceRange,
|
||||||
@ -14,6 +13,7 @@ import {
|
|||||||
Identifier,
|
Identifier,
|
||||||
sketchFromKclValue,
|
sketchFromKclValue,
|
||||||
topLevelRange,
|
topLevelRange,
|
||||||
|
VariableMap,
|
||||||
} from 'lang/wasm'
|
} from 'lang/wasm'
|
||||||
import {
|
import {
|
||||||
getNodeFromPath,
|
getNodeFromPath,
|
||||||
@ -365,7 +365,7 @@ function getTagKwArg(): SketchLineHelperKw['getTag'] {
|
|||||||
export const line: SketchLineHelperKw = {
|
export const line: SketchLineHelperKw = {
|
||||||
add: ({
|
add: ({
|
||||||
node,
|
node,
|
||||||
previousProgramMemory,
|
variables,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
segmentInput,
|
segmentInput,
|
||||||
replaceExistingCallback,
|
replaceExistingCallback,
|
||||||
@ -504,7 +504,7 @@ export const line: SketchLineHelperKw = {
|
|||||||
export const lineTo: SketchLineHelperKw = {
|
export const lineTo: SketchLineHelperKw = {
|
||||||
add: ({
|
add: ({
|
||||||
node,
|
node,
|
||||||
previousProgramMemory,
|
variables,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
segmentInput,
|
segmentInput,
|
||||||
replaceExistingCallback,
|
replaceExistingCallback,
|
||||||
@ -1629,7 +1629,7 @@ export const angledLine: SketchLineHelper = {
|
|||||||
export const angledLineOfXLength: SketchLineHelper = {
|
export const angledLineOfXLength: SketchLineHelper = {
|
||||||
add: ({
|
add: ({
|
||||||
node,
|
node,
|
||||||
previousProgramMemory,
|
variables,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
segmentInput,
|
segmentInput,
|
||||||
replaceExistingCallback,
|
replaceExistingCallback,
|
||||||
@ -1653,10 +1653,7 @@ export const angledLineOfXLength: SketchLineHelper = {
|
|||||||
const { node: varDec } = nodeMeta2
|
const { node: varDec } = nodeMeta2
|
||||||
|
|
||||||
const variableName = varDec.id.name
|
const variableName = varDec.id.name
|
||||||
const sketch = sketchFromKclValue(
|
const sketch = sketchFromKclValue(variables[variableName], variableName)
|
||||||
previousProgramMemory?.get(variableName),
|
|
||||||
variableName
|
|
||||||
)
|
|
||||||
if (err(sketch)) {
|
if (err(sketch)) {
|
||||||
return sketch
|
return sketch
|
||||||
}
|
}
|
||||||
@ -1745,7 +1742,7 @@ export const angledLineOfXLength: SketchLineHelper = {
|
|||||||
export const angledLineOfYLength: SketchLineHelper = {
|
export const angledLineOfYLength: SketchLineHelper = {
|
||||||
add: ({
|
add: ({
|
||||||
node,
|
node,
|
||||||
previousProgramMemory,
|
variables,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
segmentInput,
|
segmentInput,
|
||||||
replaceExistingCallback,
|
replaceExistingCallback,
|
||||||
@ -1768,10 +1765,7 @@ export const angledLineOfYLength: SketchLineHelper = {
|
|||||||
if (err(nodeMeta2)) return nodeMeta2
|
if (err(nodeMeta2)) return nodeMeta2
|
||||||
const { node: varDec } = nodeMeta2
|
const { node: varDec } = nodeMeta2
|
||||||
const variableName = varDec.id.name
|
const variableName = varDec.id.name
|
||||||
const sketch = sketchFromKclValue(
|
const sketch = sketchFromKclValue(variables[variableName], variableName)
|
||||||
previousProgramMemory?.get(variableName),
|
|
||||||
variableName
|
|
||||||
)
|
|
||||||
if (err(sketch)) return sketch
|
if (err(sketch)) return sketch
|
||||||
|
|
||||||
const angle = createLiteral(roundOff(getAngle(from, to), 0))
|
const angle = createLiteral(roundOff(getAngle(from, to), 0))
|
||||||
@ -2109,7 +2103,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
|
|||||||
}
|
}
|
||||||
return new Error('not implemented')
|
return new Error('not implemented')
|
||||||
},
|
},
|
||||||
updateArgs: ({ node, pathToNode, input, previousProgramMemory }) => {
|
updateArgs: ({ node, pathToNode, input, variables }) => {
|
||||||
if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
|
if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
|
||||||
const { to, from } = input
|
const { to, from } = input
|
||||||
const _node = { ...node }
|
const _node = { ...node }
|
||||||
@ -2136,10 +2130,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
|
|||||||
|
|
||||||
const { node: varDec } = nodeMeta2
|
const { node: varDec } = nodeMeta2
|
||||||
const varName = varDec.declaration.id.name
|
const varName = varDec.declaration.id.name
|
||||||
const sketch = sketchFromKclValue(
|
const sketch = sketchFromKclValue(variables[varName], varName)
|
||||||
previousProgramMemory.get(varName),
|
|
||||||
varName
|
|
||||||
)
|
|
||||||
if (err(sketch)) return sketch
|
if (err(sketch)) return sketch
|
||||||
const intersectPath = sketch.paths.find(
|
const intersectPath = sketch.paths.find(
|
||||||
({ tag }: Path) => tag && tag.value === intersectTagName
|
({ tag }: Path) => tag && tag.value === intersectTagName
|
||||||
@ -2313,7 +2304,7 @@ export const sketchLineHelperMapKw: { [key: string]: SketchLineHelperKw } = {
|
|||||||
|
|
||||||
export function changeSketchArguments(
|
export function changeSketchArguments(
|
||||||
node: Node<Program>,
|
node: Node<Program>,
|
||||||
programMemory: ProgramMemory,
|
variables: VariableMap,
|
||||||
sourceRangeOrPath:
|
sourceRangeOrPath:
|
||||||
| {
|
| {
|
||||||
type: 'sourceRange'
|
type: 'sourceRange'
|
||||||
@ -2347,7 +2338,7 @@ export function changeSketchArguments(
|
|||||||
|
|
||||||
return updateArgs({
|
return updateArgs({
|
||||||
node: _node,
|
node: _node,
|
||||||
previousProgramMemory: programMemory,
|
variables,
|
||||||
pathToNode: shallowPath,
|
pathToNode: shallowPath,
|
||||||
input,
|
input,
|
||||||
})
|
})
|
||||||
@ -2364,7 +2355,7 @@ export function changeSketchArguments(
|
|||||||
|
|
||||||
return updateArgs({
|
return updateArgs({
|
||||||
node: _node,
|
node: _node,
|
||||||
previousProgramMemory: programMemory,
|
variables,
|
||||||
pathToNode: shallowPath,
|
pathToNode: shallowPath,
|
||||||
input,
|
input,
|
||||||
})
|
})
|
||||||
@ -2435,7 +2426,7 @@ export function compareVec2Epsilon2(
|
|||||||
|
|
||||||
interface CreateLineFnCallArgs {
|
interface CreateLineFnCallArgs {
|
||||||
node: Node<Program>
|
node: Node<Program>
|
||||||
programMemory: ProgramMemory
|
variables: VariableMap
|
||||||
input: SegmentInputs
|
input: SegmentInputs
|
||||||
fnName: ToolTip
|
fnName: ToolTip
|
||||||
pathToNode: PathToNode
|
pathToNode: PathToNode
|
||||||
@ -2444,7 +2435,7 @@ interface CreateLineFnCallArgs {
|
|||||||
|
|
||||||
export function addNewSketchLn({
|
export function addNewSketchLn({
|
||||||
node: _node,
|
node: _node,
|
||||||
programMemory: previousProgramMemory,
|
variables,
|
||||||
fnName,
|
fnName,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
input: segmentInput,
|
input: segmentInput,
|
||||||
@ -2474,7 +2465,7 @@ export function addNewSketchLn({
|
|||||||
)
|
)
|
||||||
return add({
|
return add({
|
||||||
node,
|
node,
|
||||||
previousProgramMemory,
|
variables,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
segmentInput,
|
segmentInput,
|
||||||
spliceBetween,
|
spliceBetween,
|
||||||
@ -2487,7 +2478,7 @@ export function addCallExpressionsToPipe({
|
|||||||
expressions,
|
expressions,
|
||||||
}: {
|
}: {
|
||||||
node: Node<Program>
|
node: Node<Program>
|
||||||
programMemory: ProgramMemory
|
variables: VariableMap
|
||||||
pathToNode: PathToNode
|
pathToNode: PathToNode
|
||||||
expressions: Node<CallExpression | CallExpressionKw>[]
|
expressions: Node<CallExpression | CallExpressionKw>[]
|
||||||
}) {
|
}) {
|
||||||
@ -2511,7 +2502,7 @@ export function addCloseToPipe({
|
|||||||
pathToNode,
|
pathToNode,
|
||||||
}: {
|
}: {
|
||||||
node: Program
|
node: Program
|
||||||
programMemory: ProgramMemory
|
variables: VariableMap
|
||||||
pathToNode: PathToNode
|
pathToNode: PathToNode
|
||||||
}) {
|
}) {
|
||||||
const _node = { ...node }
|
const _node = { ...node }
|
||||||
@ -2532,7 +2523,7 @@ export function addCloseToPipe({
|
|||||||
|
|
||||||
export function replaceSketchLine({
|
export function replaceSketchLine({
|
||||||
node,
|
node,
|
||||||
programMemory,
|
variables,
|
||||||
pathToNode: _pathToNode,
|
pathToNode: _pathToNode,
|
||||||
fnName,
|
fnName,
|
||||||
segmentInput,
|
segmentInput,
|
||||||
@ -2540,7 +2531,7 @@ export function replaceSketchLine({
|
|||||||
referencedSegment,
|
referencedSegment,
|
||||||
}: {
|
}: {
|
||||||
node: Node<Program>
|
node: Node<Program>
|
||||||
programMemory: ProgramMemory
|
variables: VariableMap
|
||||||
pathToNode: PathToNode
|
pathToNode: PathToNode
|
||||||
fnName: ToolTip
|
fnName: ToolTip
|
||||||
segmentInput: SegmentInputs
|
segmentInput: SegmentInputs
|
||||||
@ -2564,7 +2555,7 @@ export function replaceSketchLine({
|
|||||||
: sketchLineHelperMap[fnName]
|
: sketchLineHelperMap[fnName]
|
||||||
const addRetVal = add({
|
const addRetVal = add({
|
||||||
node: _node,
|
node: _node,
|
||||||
previousProgramMemory: programMemory,
|
variables,
|
||||||
pathToNode: _pathToNode,
|
pathToNode: _pathToNode,
|
||||||
referencedSegment,
|
referencedSegment,
|
||||||
segmentInput,
|
segmentInput,
|
||||||
|
@ -53,7 +53,7 @@ async function testingSwapSketchFnCall({
|
|||||||
return Promise.reject(new Error('transformInfos undefined'))
|
return Promise.reject(new Error('transformInfos undefined'))
|
||||||
const ast2 = transformAstSketchLines({
|
const ast2 = transformAstSketchLines({
|
||||||
ast,
|
ast,
|
||||||
programMemory: execState.memory,
|
memVars: execState.variables,
|
||||||
selectionRanges: selections,
|
selectionRanges: selections,
|
||||||
transformInfos,
|
transformInfos,
|
||||||
referenceSegName: '',
|
referenceSegName: '',
|
||||||
@ -373,7 +373,7 @@ part001 = startSketchOn('XY')
|
|||||||
const execState = await enginelessExecutor(assertParse(code))
|
const execState = await enginelessExecutor(assertParse(code))
|
||||||
const index = code.indexOf('// normal-segment') - 7
|
const index = code.indexOf('// normal-segment') - 7
|
||||||
const sg = sketchFromKclValue(
|
const sg = sketchFromKclValue(
|
||||||
execState.memory.get('part001'),
|
execState.variables['part001'],
|
||||||
'part001'
|
'part001'
|
||||||
) as Sketch
|
) as Sketch
|
||||||
const _segment = getSketchSegmentFromSourceRange(
|
const _segment = getSketchSegmentFromSourceRange(
|
||||||
@ -393,7 +393,7 @@ part001 = startSketchOn('XY')
|
|||||||
const execState = await enginelessExecutor(assertParse(code))
|
const execState = await enginelessExecutor(assertParse(code))
|
||||||
const index = code.indexOf('// segment-in-start') - 7
|
const index = code.indexOf('// segment-in-start') - 7
|
||||||
const _segment = getSketchSegmentFromSourceRange(
|
const _segment = getSketchSegmentFromSourceRange(
|
||||||
sketchFromKclValue(execState.memory.get('part001'), 'part001') as Sketch,
|
sketchFromKclValue(execState.variables['part001'], 'part001') as Sketch,
|
||||||
topLevelRange(index, index)
|
topLevelRange(index, index)
|
||||||
)
|
)
|
||||||
if (err(_segment)) throw _segment
|
if (err(_segment)) throw _segment
|
||||||
|
@ -193,7 +193,7 @@ describe('testing transformAstForSketchLines for equal length constraint', () =>
|
|||||||
ast,
|
ast,
|
||||||
selectionRanges: transformedSelection,
|
selectionRanges: transformedSelection,
|
||||||
transformInfos,
|
transformInfos,
|
||||||
programMemory: execState.memory,
|
memVars: execState.variables,
|
||||||
})
|
})
|
||||||
if (err(newAst)) return Promise.reject(newAst)
|
if (err(newAst)) return Promise.reject(newAst)
|
||||||
|
|
||||||
@ -356,7 +356,7 @@ part001 = startSketchOn('XY')
|
|||||||
ast,
|
ast,
|
||||||
selectionRanges: makeSelections(selectionRanges),
|
selectionRanges: makeSelections(selectionRanges),
|
||||||
transformInfos,
|
transformInfos,
|
||||||
programMemory: execState.memory,
|
memVars: execState.variables,
|
||||||
})
|
})
|
||||||
if (err(newAst)) return Promise.reject(newAst)
|
if (err(newAst)) return Promise.reject(newAst)
|
||||||
|
|
||||||
@ -445,7 +445,7 @@ part001 = startSketchOn('XY')
|
|||||||
ast,
|
ast,
|
||||||
selectionRanges: makeSelections(selectionRanges),
|
selectionRanges: makeSelections(selectionRanges),
|
||||||
transformInfos,
|
transformInfos,
|
||||||
programMemory: execState.memory,
|
memVars: execState.variables,
|
||||||
referenceSegName: '',
|
referenceSegName: '',
|
||||||
})
|
})
|
||||||
if (err(newAst)) return Promise.reject(newAst)
|
if (err(newAst)) return Promise.reject(newAst)
|
||||||
@ -505,7 +505,7 @@ part001 = startSketchOn('XY')
|
|||||||
ast,
|
ast,
|
||||||
selectionRanges: makeSelections(selectionRanges),
|
selectionRanges: makeSelections(selectionRanges),
|
||||||
transformInfos,
|
transformInfos,
|
||||||
programMemory: execState.memory,
|
memVars: execState.variables,
|
||||||
referenceSegName: '',
|
referenceSegName: '',
|
||||||
})
|
})
|
||||||
if (err(newAst)) return Promise.reject(newAst)
|
if (err(newAst)) return Promise.reject(newAst)
|
||||||
@ -600,7 +600,7 @@ async function helperThing(
|
|||||||
ast,
|
ast,
|
||||||
selectionRanges: makeSelections(selectionRanges),
|
selectionRanges: makeSelections(selectionRanges),
|
||||||
transformInfos,
|
transformInfos,
|
||||||
programMemory: execState.memory,
|
memVars: execState.variables,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (err(newAst)) return Promise.reject(newAst)
|
if (err(newAst)) return Promise.reject(newAst)
|
||||||
|
@ -17,12 +17,12 @@ import {
|
|||||||
BinaryPart,
|
BinaryPart,
|
||||||
VariableDeclarator,
|
VariableDeclarator,
|
||||||
PathToNode,
|
PathToNode,
|
||||||
ProgramMemory,
|
|
||||||
sketchFromKclValue,
|
sketchFromKclValue,
|
||||||
Literal,
|
Literal,
|
||||||
SourceRange,
|
SourceRange,
|
||||||
LiteralValue,
|
LiteralValue,
|
||||||
LabeledArg,
|
LabeledArg,
|
||||||
|
VariableMap,
|
||||||
} from '../wasm'
|
} from '../wasm'
|
||||||
import { getNodeFromPath, getNodeFromPathCurry } from '../queryAst'
|
import { getNodeFromPath, getNodeFromPathCurry } from '../queryAst'
|
||||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||||
@ -1808,14 +1808,14 @@ export function transformSecondarySketchLinesTagFirst({
|
|||||||
ast,
|
ast,
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
transformInfos,
|
transformInfos,
|
||||||
programMemory,
|
memVars,
|
||||||
forceSegName,
|
forceSegName,
|
||||||
forceValueUsedInTransform,
|
forceValueUsedInTransform,
|
||||||
}: {
|
}: {
|
||||||
ast: Node<Program>
|
ast: Node<Program>
|
||||||
selectionRanges: Selections
|
selectionRanges: Selections
|
||||||
transformInfos: TransformInfo[]
|
transformInfos: TransformInfo[]
|
||||||
programMemory: ProgramMemory
|
memVars: VariableMap
|
||||||
forceSegName?: string
|
forceSegName?: string
|
||||||
forceValueUsedInTransform?: BinaryPart
|
forceValueUsedInTransform?: BinaryPart
|
||||||
}):
|
}):
|
||||||
@ -1851,7 +1851,7 @@ export function transformSecondarySketchLinesTagFirst({
|
|||||||
},
|
},
|
||||||
referencedSegmentRange: primarySelection,
|
referencedSegmentRange: primarySelection,
|
||||||
transformInfos,
|
transformInfos,
|
||||||
programMemory,
|
memVars,
|
||||||
referenceSegName: tag,
|
referenceSegName: tag,
|
||||||
forceValueUsedInTransform,
|
forceValueUsedInTransform,
|
||||||
})
|
})
|
||||||
@ -1885,7 +1885,7 @@ export function transformAstSketchLines({
|
|||||||
ast,
|
ast,
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
transformInfos,
|
transformInfos,
|
||||||
programMemory,
|
memVars,
|
||||||
referenceSegName,
|
referenceSegName,
|
||||||
forceValueUsedInTransform,
|
forceValueUsedInTransform,
|
||||||
referencedSegmentRange,
|
referencedSegmentRange,
|
||||||
@ -1893,7 +1893,7 @@ export function transformAstSketchLines({
|
|||||||
ast: Node<Program>
|
ast: Node<Program>
|
||||||
selectionRanges: Selections | PathToNode[]
|
selectionRanges: Selections | PathToNode[]
|
||||||
transformInfos: TransformInfo[]
|
transformInfos: TransformInfo[]
|
||||||
programMemory: ProgramMemory
|
memVars: VariableMap
|
||||||
referenceSegName: string
|
referenceSegName: string
|
||||||
referencedSegmentRange?: SourceRange
|
referencedSegmentRange?: SourceRange
|
||||||
forceValueUsedInTransform?: BinaryPart
|
forceValueUsedInTransform?: BinaryPart
|
||||||
@ -2009,7 +2009,7 @@ export function transformAstSketchLines({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const varName = varDec.node.id.name
|
const varName = varDec.node.id.name
|
||||||
let kclVal = programMemory.get(varName)
|
let kclVal = memVars[varName]
|
||||||
let sketch
|
let sketch
|
||||||
if (kclVal?.type === 'Solid') {
|
if (kclVal?.type === 'Solid') {
|
||||||
sketch = kclVal.value.sketch
|
sketch = kclVal.value.sketch
|
||||||
@ -2040,7 +2040,7 @@ export function transformAstSketchLines({
|
|||||||
// Note to ADAM: Here is where the replaceExisting call gets sent.
|
// Note to ADAM: Here is where the replaceExisting call gets sent.
|
||||||
const replacedSketchLine = replaceSketchLine({
|
const replacedSketchLine = replaceSketchLine({
|
||||||
node: node,
|
node: node,
|
||||||
programMemory,
|
variables: memVars,
|
||||||
pathToNode: _pathToNode,
|
pathToNode: _pathToNode,
|
||||||
referencedSegment,
|
referencedSegment,
|
||||||
fnName: transformTo || (call.node.callee.name as ToolTip),
|
fnName: transformTo || (call.node.callee.name as ToolTip),
|
||||||
|
@ -18,8 +18,8 @@ describe('testing angledLineThatIntersects', () => {
|
|||||||
}, %, $yo2)
|
}, %, $yo2)
|
||||||
intersect = segEndX(yo2)`
|
intersect = segEndX(yo2)`
|
||||||
const execState = await enginelessExecutor(assertParse(code('-1')))
|
const execState = await enginelessExecutor(assertParse(code('-1')))
|
||||||
expect(execState.memory.get('intersect')?.value).toBe(1 + Math.sqrt(2))
|
expect(execState.variables['intersect']?.value).toBe(1 + Math.sqrt(2))
|
||||||
const noOffset = await enginelessExecutor(assertParse(code('0')))
|
const noOffset = await enginelessExecutor(assertParse(code('0')))
|
||||||
expect(noOffset.memory.get('intersect')?.value).toBeCloseTo(1)
|
expect(noOffset.variables['intersect']?.value).toBeCloseTo(1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { ToolTip } from 'lang/langHelpers'
|
import { ToolTip } from 'lang/langHelpers'
|
||||||
import {
|
import {
|
||||||
ProgramMemory,
|
|
||||||
Path,
|
Path,
|
||||||
SourceRange,
|
SourceRange,
|
||||||
Program,
|
Program,
|
||||||
@ -10,14 +9,15 @@ import {
|
|||||||
Literal,
|
Literal,
|
||||||
BinaryPart,
|
BinaryPart,
|
||||||
CallExpressionKw,
|
CallExpressionKw,
|
||||||
|
VariableMap,
|
||||||
} from '../wasm'
|
} from '../wasm'
|
||||||
import { LineInputsType } from './sketchcombos'
|
import { LineInputsType } from './sketchcombos'
|
||||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||||
|
|
||||||
export interface ModifyAstBase {
|
export interface ModifyAstBase {
|
||||||
node: Node<Program>
|
node: Node<Program>
|
||||||
// TODO #896: Remove ProgramMemory from this interface
|
// TODO #896: Remove memory variables from this interface
|
||||||
previousProgramMemory: ProgramMemory
|
variables: VariableMap
|
||||||
pathToNode: PathToNode
|
pathToNode: PathToNode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ it('can execute parsed AST', async () => {
|
|||||||
expect(pResult.program).not.toEqual(null)
|
expect(pResult.program).not.toEqual(null)
|
||||||
const execState = await enginelessExecutor(pResult.program as Node<Program>)
|
const execState = await enginelessExecutor(pResult.program as Node<Program>)
|
||||||
expect(err(execState)).toEqual(false)
|
expect(err(execState)).toEqual(false)
|
||||||
expect(execState.memory.get('x')?.value).toEqual(1)
|
expect(execState.variables['x']?.value).toEqual(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('formats numbers with units', () => {
|
it('formats numbers with units', () => {
|
||||||
|
339
src/lang/wasm.ts
339
src/lang/wasm.ts
@ -3,7 +3,8 @@ import {
|
|||||||
parse_wasm,
|
parse_wasm,
|
||||||
recast_wasm,
|
recast_wasm,
|
||||||
format_number,
|
format_number,
|
||||||
execute,
|
execute_with_engine,
|
||||||
|
execute_mock,
|
||||||
kcl_lint,
|
kcl_lint,
|
||||||
modify_ast_for_sketch_wasm,
|
modify_ast_for_sketch_wasm,
|
||||||
is_points_ccw,
|
is_points_ccw,
|
||||||
@ -281,6 +282,8 @@ export const assertParse = (code: string): Node<Program> => {
|
|||||||
return result.program
|
return result.program
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type VariableMap = { [key in string]?: KclValue }
|
||||||
|
|
||||||
export type PathToNode = [string | number, string][]
|
export type PathToNode = [string | number, string][]
|
||||||
|
|
||||||
export const isPathToNodeNumber = (
|
export const isPathToNodeNumber = (
|
||||||
@ -290,7 +293,7 @@ export const isPathToNodeNumber = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ExecState {
|
export interface ExecState {
|
||||||
memory: ProgramMemory
|
variables: { [key in string]?: KclValue }
|
||||||
operations: Operation[]
|
operations: Operation[]
|
||||||
artifacts: { [key in ArtifactId]?: RustArtifact }
|
artifacts: { [key in ArtifactId]?: RustArtifact }
|
||||||
artifactCommands: ArtifactCommand[]
|
artifactCommands: ArtifactCommand[]
|
||||||
@ -303,7 +306,7 @@ export interface ExecState {
|
|||||||
*/
|
*/
|
||||||
export function emptyExecState(): ExecState {
|
export function emptyExecState(): ExecState {
|
||||||
return {
|
return {
|
||||||
memory: ProgramMemory.empty(),
|
variables: {},
|
||||||
operations: [],
|
operations: [],
|
||||||
artifacts: {},
|
artifacts: {},
|
||||||
artifactCommands: [],
|
artifactCommands: [],
|
||||||
@ -328,7 +331,7 @@ function execStateFromRust(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
memory: ProgramMemory.fromRaw(execOutcome.memory),
|
variables: execOutcome.variables,
|
||||||
operations: execOutcome.operations,
|
operations: execOutcome.operations,
|
||||||
artifacts: execOutcome.artifacts,
|
artifacts: execOutcome.artifacts,
|
||||||
artifactCommands: execOutcome.artifactCommands,
|
artifactCommands: execOutcome.artifactCommands,
|
||||||
@ -336,6 +339,16 @@ function execStateFromRust(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mockExecStateFromRust(execOutcome: RustExecOutcome): ExecState {
|
||||||
|
return {
|
||||||
|
variables: execOutcome.variables,
|
||||||
|
operations: execOutcome.operations,
|
||||||
|
artifacts: execOutcome.artifacts,
|
||||||
|
artifactCommands: execOutcome.artifactCommands,
|
||||||
|
artifactGraph: new Map<ArtifactId, Artifact>(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export type ArtifactGraph = Map<ArtifactId, Artifact>
|
export type ArtifactGraph = Map<ArtifactId, Artifact>
|
||||||
|
|
||||||
function rustArtifactGraphToMap(
|
function rustArtifactGraphToMap(
|
||||||
@ -354,203 +367,6 @@ export function defaultArtifactGraph(): ArtifactGraph {
|
|||||||
return new Map()
|
return new Map()
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Memory {
|
|
||||||
[key: string]: KclValue | undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
const ROOT_ENVIRONMENT_REF: EnvironmentRef = 0
|
|
||||||
|
|
||||||
function emptyEnvironment(): Environment {
|
|
||||||
return { bindings: {}, parent: null }
|
|
||||||
}
|
|
||||||
|
|
||||||
function emptyRootEnvironment(): Environment {
|
|
||||||
return {
|
|
||||||
// This is dumb this is copied from rust.
|
|
||||||
bindings: {
|
|
||||||
ZERO: { type: 'Number', value: 0.0, __meta: [] },
|
|
||||||
QUARTER_TURN: { type: 'Number', value: 90.0, __meta: [] },
|
|
||||||
HALF_TURN: { type: 'Number', value: 180.0, __meta: [] },
|
|
||||||
THREE_QUARTER_TURN: { type: 'Number', value: 270.0, __meta: [] },
|
|
||||||
},
|
|
||||||
parent: null,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This duplicates logic in Rust. The hope is to keep ProgramMemory internals
|
|
||||||
* isolated from the rest of the TypeScript code so that we can move it to Rust
|
|
||||||
* in the future.
|
|
||||||
*/
|
|
||||||
export class ProgramMemory {
|
|
||||||
private environments: Environment[]
|
|
||||||
private currentEnv: EnvironmentRef
|
|
||||||
private return: KclValue | null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Empty memory doesn't include prelude definitions.
|
|
||||||
*/
|
|
||||||
static empty(): ProgramMemory {
|
|
||||||
return new ProgramMemory()
|
|
||||||
}
|
|
||||||
|
|
||||||
static fromRaw(raw: RawProgramMemory): ProgramMemory {
|
|
||||||
return new ProgramMemory(raw.environments, raw.currentEnv, raw.return)
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
environments: Environment[] = [emptyRootEnvironment()],
|
|
||||||
currentEnv: EnvironmentRef = ROOT_ENVIRONMENT_REF,
|
|
||||||
returnVal: KclValue | null = null
|
|
||||||
) {
|
|
||||||
this.environments = environments
|
|
||||||
this.currentEnv = currentEnv
|
|
||||||
this.return = returnVal
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a deep copy.
|
|
||||||
*/
|
|
||||||
clone(): ProgramMemory {
|
|
||||||
return ProgramMemory.fromRaw(structuredClone(this.toRaw()))
|
|
||||||
}
|
|
||||||
|
|
||||||
has(name: string): boolean {
|
|
||||||
let envRef = this.currentEnv
|
|
||||||
while (true) {
|
|
||||||
const env = this.environments[envRef]
|
|
||||||
if (env.bindings.hasOwnProperty(name)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if (!env.parent) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
envRef = env.parent
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
get(name: string): KclValue | null {
|
|
||||||
let envRef = this.currentEnv
|
|
||||||
while (true) {
|
|
||||||
const env = this.environments[envRef]
|
|
||||||
if (env.bindings.hasOwnProperty(name)) {
|
|
||||||
return env.bindings[name] ?? null
|
|
||||||
}
|
|
||||||
if (!env.parent) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
envRef = env.parent
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
set(name: string, value: KclValue): Error | null {
|
|
||||||
if (this.environments.length === 0) {
|
|
||||||
return new Error('No environment to set memory in')
|
|
||||||
}
|
|
||||||
const env = this.environments[this.currentEnv]
|
|
||||||
env.bindings[name] = value
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new ProgramMemory with only `KclValue`s that pass the
|
|
||||||
* predicate. Values are deep copied.
|
|
||||||
*
|
|
||||||
* Note: Return value of the returned ProgramMemory is always null.
|
|
||||||
*/
|
|
||||||
filterVariables(
|
|
||||||
keepPrelude: boolean,
|
|
||||||
predicate: (value: KclValue) => boolean
|
|
||||||
): ProgramMemory | Error {
|
|
||||||
const environments: Environment[] = []
|
|
||||||
for (const [i, env] of this.environments.entries()) {
|
|
||||||
let bindings: Memory
|
|
||||||
if (i === ROOT_ENVIRONMENT_REF && keepPrelude) {
|
|
||||||
// Get prelude definitions. Create these first so that they're always
|
|
||||||
// first in iteration order.
|
|
||||||
const memoryOrError = programMemoryInit()
|
|
||||||
if (err(memoryOrError)) return memoryOrError
|
|
||||||
bindings = memoryOrError.environments[0].bindings
|
|
||||||
} else {
|
|
||||||
bindings = emptyEnvironment().bindings
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [name, value] of Object.entries(env.bindings)) {
|
|
||||||
if (value === undefined) continue
|
|
||||||
// Check the predicate.
|
|
||||||
if (!predicate(value)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Deep copy.
|
|
||||||
bindings[name] = structuredClone(value)
|
|
||||||
}
|
|
||||||
environments.push({ bindings, parent: env.parent })
|
|
||||||
}
|
|
||||||
return new ProgramMemory(environments, this.currentEnv, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
numEnvironments(): number {
|
|
||||||
return this.environments.length
|
|
||||||
}
|
|
||||||
|
|
||||||
numVariables(envRef: EnvironmentRef): number {
|
|
||||||
return Object.keys(this.environments[envRef]).length
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all variable entries in memory that are visible, in a flat
|
|
||||||
* structure. If variables are shadowed, they're not visible, and therefore,
|
|
||||||
* not included.
|
|
||||||
*
|
|
||||||
* This should only be used to display in the MemoryPane UI.
|
|
||||||
*/
|
|
||||||
visibleEntries(): Map<string, KclValue> {
|
|
||||||
const map = new Map<string, KclValue>()
|
|
||||||
let envRef = this.currentEnv
|
|
||||||
while (true) {
|
|
||||||
const env = this.environments[envRef]
|
|
||||||
for (const [name, value] of Object.entries(env.bindings)) {
|
|
||||||
if (value === undefined) continue
|
|
||||||
// Don't include shadowed variables.
|
|
||||||
if (!map.has(name)) {
|
|
||||||
map.set(name, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!env.parent) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
envRef = env.parent
|
|
||||||
}
|
|
||||||
return map
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if any visible variables are a Sketch or Solid.
|
|
||||||
*/
|
|
||||||
hasSketchOrSolid(): boolean {
|
|
||||||
for (const node of this.visibleEntries().values()) {
|
|
||||||
if (node.type === 'Solid' || node.type === 'Sketch') {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the representation that can be serialized to JSON. This should only
|
|
||||||
* be used within this module.
|
|
||||||
*/
|
|
||||||
toRaw(): RawProgramMemory {
|
|
||||||
return {
|
|
||||||
environments: this.environments,
|
|
||||||
currentEnv: this.currentEnv,
|
|
||||||
return: this.return,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: In the future, make the parameter be a KclValue.
|
// TODO: In the future, make the parameter be a KclValue.
|
||||||
export function sketchFromKclValueOptional(
|
export function sketchFromKclValueOptional(
|
||||||
obj: any,
|
obj: any,
|
||||||
@ -590,54 +406,85 @@ export function sketchFromKclValue(
|
|||||||
* @param node The AST of the program to execute.
|
* @param node The AST of the program to execute.
|
||||||
* @param path The full path of the file being executed. Use `null` for
|
* @param path The full path of the file being executed. Use `null` for
|
||||||
* expressions that don't have a file, like expressions in the command bar.
|
* expressions that don't have a file, like expressions in the command bar.
|
||||||
* @param programMemoryOverride If this is not `null`, this will be used as the
|
|
||||||
* initial program memory, and the execution will be engineless (AKA mock
|
|
||||||
* execution).
|
|
||||||
*/
|
*/
|
||||||
export const executor = async (
|
export const executeMock = async (
|
||||||
node: Node<Program>,
|
node: Node<Program>,
|
||||||
engineCommandManager: EngineCommandManager,
|
usePrevMemory?: boolean,
|
||||||
path?: string,
|
path?: string,
|
||||||
programMemoryOverride: ProgramMemory | Error | null = null
|
variables?: { [key in string]?: KclValue }
|
||||||
): Promise<ExecState> => {
|
): Promise<ExecState> => {
|
||||||
if (programMemoryOverride !== null && err(programMemoryOverride))
|
|
||||||
return Promise.reject(programMemoryOverride)
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let jsAppSettings = default_app_settings()
|
if (!variables) {
|
||||||
if (!TEST) {
|
variables = {}
|
||||||
const lastSettingsSnapshot = await import(
|
|
||||||
'components/SettingsAuthProvider'
|
|
||||||
).then((module) => module.lastSettingsContextSnapshot)
|
|
||||||
if (lastSettingsSnapshot) {
|
|
||||||
jsAppSettings = getAllCurrentSettings(lastSettingsSnapshot)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const execOutcome: RustExecOutcome = await execute(
|
if (usePrevMemory === undefined) {
|
||||||
|
usePrevMemory = true
|
||||||
|
}
|
||||||
|
const execOutcome: RustExecOutcome = await execute_mock(
|
||||||
JSON.stringify(node),
|
JSON.stringify(node),
|
||||||
path,
|
path,
|
||||||
JSON.stringify(programMemoryOverride?.toRaw() || null),
|
JSON.stringify({ settings: await jsAppSettings() }),
|
||||||
JSON.stringify({ settings: jsAppSettings }),
|
usePrevMemory,
|
||||||
|
JSON.stringify(variables),
|
||||||
|
fileSystemManager
|
||||||
|
)
|
||||||
|
return mockExecStateFromRust(execOutcome)
|
||||||
|
} catch (e: any) {
|
||||||
|
return Promise.reject(errFromErrWithOutputs(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a KCL program.
|
||||||
|
* @param node The AST of the program to execute.
|
||||||
|
* @param path The full path of the file being executed. Use `null` for
|
||||||
|
* expressions that don't have a file, like expressions in the command bar.
|
||||||
|
*/
|
||||||
|
export const executeWithEngine = async (
|
||||||
|
node: Node<Program>,
|
||||||
|
engineCommandManager: EngineCommandManager,
|
||||||
|
path?: string
|
||||||
|
): Promise<ExecState> => {
|
||||||
|
try {
|
||||||
|
const execOutcome: RustExecOutcome = await execute_with_engine(
|
||||||
|
JSON.stringify(node),
|
||||||
|
path,
|
||||||
|
JSON.stringify({ settings: await jsAppSettings() }),
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
fileSystemManager
|
fileSystemManager
|
||||||
)
|
)
|
||||||
return execStateFromRust(execOutcome, node)
|
return execStateFromRust(execOutcome, node)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.log(e)
|
return Promise.reject(errFromErrWithOutputs(e))
|
||||||
const parsed: KclErrorWithOutputs = JSON.parse(e.toString())
|
|
||||||
const kclError = new KCLError(
|
|
||||||
parsed.error.kind,
|
|
||||||
parsed.error.msg,
|
|
||||||
firstSourceRange(parsed.error),
|
|
||||||
parsed.operations,
|
|
||||||
parsed.artifactCommands,
|
|
||||||
rustArtifactGraphToMap(parsed.artifactGraph)
|
|
||||||
)
|
|
||||||
|
|
||||||
return Promise.reject(kclError)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const jsAppSettings = async () => {
|
||||||
|
let jsAppSettings = default_app_settings()
|
||||||
|
if (!TEST) {
|
||||||
|
const lastSettingsSnapshot = await import(
|
||||||
|
'components/SettingsAuthProvider'
|
||||||
|
).then((module) => module.lastSettingsContextSnapshot)
|
||||||
|
if (lastSettingsSnapshot) {
|
||||||
|
jsAppSettings = getAllCurrentSettings(lastSettingsSnapshot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return jsAppSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
const errFromErrWithOutputs = (e: any): KCLError => {
|
||||||
|
console.log('execute error', e)
|
||||||
|
const parsed: KclErrorWithOutputs = JSON.parse(e.toString())
|
||||||
|
return new KCLError(
|
||||||
|
parsed.error.kind,
|
||||||
|
parsed.error.msg,
|
||||||
|
firstSourceRange(parsed.error),
|
||||||
|
parsed.operations,
|
||||||
|
parsed.artifactCommands,
|
||||||
|
rustArtifactGraphToMap(parsed.artifactGraph)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export const kclLint = async (ast: Program): Promise<Array<Discovered>> => {
|
export const kclLint = async (ast: Program): Promise<Array<Discovered>> => {
|
||||||
try {
|
try {
|
||||||
const discovered_findings: Array<Discovered> = await kcl_lint(
|
const discovered_findings: Array<Discovered> = await kcl_lint(
|
||||||
@ -703,7 +550,6 @@ export const modifyAstForSketch = async (
|
|||||||
defaultArtifactGraph()
|
defaultArtifactGraph()
|
||||||
)
|
)
|
||||||
|
|
||||||
console.log(kclError)
|
|
||||||
return Promise.reject(kclError)
|
return Promise.reject(kclError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -751,31 +597,6 @@ export function getTangentialArcToInfo({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns new ProgramMemory with prelude definitions.
|
|
||||||
*/
|
|
||||||
export function programMemoryInit(): ProgramMemory | Error {
|
|
||||||
try {
|
|
||||||
const memory: RawProgramMemory = program_memory_init()
|
|
||||||
return new ProgramMemory(
|
|
||||||
memory.environments,
|
|
||||||
memory.currentEnv,
|
|
||||||
memory.return
|
|
||||||
)
|
|
||||||
} catch (e: any) {
|
|
||||||
console.log(e)
|
|
||||||
const parsed: RustKclError = JSON.parse(e.toString())
|
|
||||||
return new KCLError(
|
|
||||||
parsed.kind,
|
|
||||||
parsed.msg,
|
|
||||||
firstSourceRange(parsed),
|
|
||||||
[],
|
|
||||||
[],
|
|
||||||
defaultArtifactGraph()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function coreDump(
|
export async function coreDump(
|
||||||
coreDumpManager: CoreDumpManager,
|
coreDumpManager: CoreDumpManager,
|
||||||
openGithubIssue: boolean = false
|
openGithubIssue: boolean = false
|
||||||
|
@ -576,7 +576,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
|||||||
ast: structuredClone(kclManager.ast),
|
ast: structuredClone(kclManager.ast),
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
transformInfos: transforms,
|
transformInfos: transforms,
|
||||||
programMemory: kclManager.programMemory,
|
memVars: kclManager.variables,
|
||||||
referenceSegName: '',
|
referenceSegName: '',
|
||||||
})
|
})
|
||||||
if (err(sketched)) return KCL_DEFAULT_LENGTH
|
if (err(sketched)) return KCL_DEFAULT_LENGTH
|
||||||
|
@ -51,16 +51,16 @@ sketch002 = startSketchOn(sketch001, seg03)
|
|||||||
center = [-1.25, 1],
|
center = [-1.25, 1],
|
||||||
radius = mountingHoleDiameter / 2,
|
radius = mountingHoleDiameter / 2,
|
||||||
}, %)
|
}, %)
|
||||||
|> patternLinear2d({
|
|> patternLinear2d(
|
||||||
instances = 2,
|
instances = 2,
|
||||||
distance = 2.5,
|
distance = 2.5,
|
||||||
axis = [-1, 0],
|
axis = [-1, 0],
|
||||||
}, %)
|
)
|
||||||
|> patternLinear2d({
|
|> patternLinear2d(
|
||||||
instances = 2,
|
instances = 2,
|
||||||
distance = 4,
|
distance = 4,
|
||||||
axis = [0, 1],
|
axis = [0, 1],
|
||||||
}, %)
|
)
|
||||||
|> extrude(%, length = -thickness-.01)
|
|> extrude(%, length = -thickness-.01)
|
||||||
|
|
||||||
sketch003 = startSketchOn(sketch001, seg04)
|
sketch003 = startSketchOn(sketch001, seg04)
|
||||||
@ -68,11 +68,11 @@ sketch003 = startSketchOn(sketch001, seg04)
|
|||||||
center = [1, -1],
|
center = [1, -1],
|
||||||
radius = mountingHoleDiameter / 2,
|
radius = mountingHoleDiameter / 2,
|
||||||
}, %)
|
}, %)
|
||||||
|> patternLinear2d({
|
|> patternLinear2d(
|
||||||
instances = 2,
|
instances = 2,
|
||||||
distance = 4,
|
distance = 4,
|
||||||
axis = [1, 0],
|
axis = [1, 0],
|
||||||
}, %)
|
)
|
||||||
|> extrude(%, length = -thickness-0.1)
|
|> extrude(%, length = -thickness-0.1)
|
||||||
`
|
`
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@ import { FILE_EXT } from './constants'
|
|||||||
import { UnitLength_type } from '@kittycad/lib/dist/types/src/models'
|
import { UnitLength_type } from '@kittycad/lib/dist/types/src/models'
|
||||||
import { reportRejection } from './trap'
|
import { reportRejection } from './trap'
|
||||||
import { IndexLoaderData } from './types'
|
import { IndexLoaderData } from './types'
|
||||||
import { IS_NIGHTLY_OR_DEBUG } from 'routes/Settings'
|
|
||||||
import { copyFileShareLink } from './links'
|
import { copyFileShareLink } from './links'
|
||||||
|
|
||||||
interface OnSubmitProps {
|
interface OnSubmitProps {
|
||||||
@ -137,7 +136,6 @@ export function kclCommands(commandProps: KclCommandConfig): Command[] {
|
|||||||
{
|
{
|
||||||
name: 'share-file-link',
|
name: 'share-file-link',
|
||||||
displayName: 'Share current part (via Zoo link)',
|
displayName: 'Share current part (via Zoo link)',
|
||||||
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',
|
||||||
needsReview: false,
|
needsReview: false,
|
||||||
|
@ -1,67 +1,55 @@
|
|||||||
import { ParseResult, ProgramMemory } from 'lang/wasm'
|
import { ParseResult, VariableMap } from 'lang/wasm'
|
||||||
import { getCalculatedKclExpressionValue } from './kclHelpers'
|
import { getCalculatedKclExpressionValue } from './kclHelpers'
|
||||||
|
|
||||||
describe('KCL expression calculations', () => {
|
describe('KCL expression calculations', () => {
|
||||||
it('calculates a simple expression', async () => {
|
it('calculates a simple expression', async () => {
|
||||||
const actual = await getCalculatedKclExpressionValue({
|
const actual = await getCalculatedKclExpressionValue('1 + 2', {})
|
||||||
value: '1 + 2',
|
|
||||||
programMemory: ProgramMemory.empty(),
|
|
||||||
})
|
|
||||||
const coercedActual = actual as Exclude<typeof actual, Error | ParseResult>
|
const coercedActual = actual as Exclude<typeof actual, Error | ParseResult>
|
||||||
expect(coercedActual).not.toHaveProperty('errors')
|
expect(coercedActual).not.toHaveProperty('errors')
|
||||||
expect(coercedActual.valueAsString).toEqual('3')
|
expect(coercedActual.valueAsString).toEqual('3')
|
||||||
expect(coercedActual?.astNode).toBeDefined()
|
expect(coercedActual?.astNode).toBeDefined()
|
||||||
})
|
})
|
||||||
it('calculates a simple expression with a variable', async () => {
|
it('calculates a simple expression with a variable', async () => {
|
||||||
const programMemory = ProgramMemory.empty()
|
const variables: VariableMap = {}
|
||||||
programMemory.set('x', {
|
variables['x'] = {
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
value: 2,
|
value: 2,
|
||||||
__meta: [],
|
__meta: [],
|
||||||
})
|
}
|
||||||
const actual = await getCalculatedKclExpressionValue({
|
const actual = await getCalculatedKclExpressionValue('1 + x', variables)
|
||||||
value: '1 + x',
|
|
||||||
programMemory,
|
|
||||||
})
|
|
||||||
const coercedActual = actual as Exclude<typeof actual, Error | ParseResult>
|
const coercedActual = actual as Exclude<typeof actual, Error | ParseResult>
|
||||||
expect(coercedActual.valueAsString).toEqual('3')
|
expect(coercedActual.valueAsString).toEqual('3')
|
||||||
expect(coercedActual.astNode).toBeDefined()
|
expect(coercedActual.astNode).toBeDefined()
|
||||||
})
|
})
|
||||||
it('returns NAN for an invalid expression', async () => {
|
it('returns NAN for an invalid expression', async () => {
|
||||||
const actual = await getCalculatedKclExpressionValue({
|
const actual = await getCalculatedKclExpressionValue('1 + x', {})
|
||||||
value: '1 + x',
|
|
||||||
programMemory: ProgramMemory.empty(),
|
|
||||||
})
|
|
||||||
const coercedActual = actual as Exclude<typeof actual, Error | ParseResult>
|
const coercedActual = actual as Exclude<typeof actual, Error | ParseResult>
|
||||||
expect(coercedActual.valueAsString).toEqual('NAN')
|
expect(coercedActual.valueAsString).toEqual('NAN')
|
||||||
expect(coercedActual.astNode).toBeDefined()
|
expect(coercedActual.astNode).toBeDefined()
|
||||||
})
|
})
|
||||||
it('returns NAN for an expression with an invalid variable', async () => {
|
it('returns NAN for an expression with an invalid variable', async () => {
|
||||||
const programMemory = ProgramMemory.empty()
|
const variables: VariableMap = {}
|
||||||
programMemory.set('y', {
|
variables['y'] = {
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
value: 2,
|
value: 2,
|
||||||
__meta: [],
|
__meta: [],
|
||||||
})
|
}
|
||||||
const actual = await getCalculatedKclExpressionValue({
|
const actual = await getCalculatedKclExpressionValue('1 + x', variables)
|
||||||
value: '1 + x',
|
|
||||||
programMemory,
|
|
||||||
})
|
|
||||||
const coercedActual = actual as Exclude<typeof actual, Error | ParseResult>
|
const coercedActual = actual as Exclude<typeof actual, Error | ParseResult>
|
||||||
expect(coercedActual.valueAsString).toEqual('NAN')
|
expect(coercedActual.valueAsString).toEqual('NAN')
|
||||||
expect(coercedActual.astNode).toBeDefined()
|
expect(coercedActual.astNode).toBeDefined()
|
||||||
})
|
})
|
||||||
it('calculates a more complex expression with a variable', async () => {
|
it('calculates a more complex expression with a variable', async () => {
|
||||||
const programMemory = ProgramMemory.empty()
|
const variables: VariableMap = {}
|
||||||
programMemory.set('x', {
|
variables['x'] = {
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
value: 2,
|
value: 2,
|
||||||
__meta: [],
|
__meta: [],
|
||||||
})
|
}
|
||||||
const actual = await getCalculatedKclExpressionValue({
|
const actual = await getCalculatedKclExpressionValue(
|
||||||
value: '(1 + x * x) * 2',
|
'(1 + x * x) * 2',
|
||||||
programMemory,
|
variables
|
||||||
})
|
)
|
||||||
const coercedActual = actual as Exclude<typeof actual, Error | ParseResult>
|
const coercedActual = actual as Exclude<typeof actual, Error | ParseResult>
|
||||||
expect(coercedActual.valueAsString).toEqual('10')
|
expect(coercedActual.valueAsString).toEqual('10')
|
||||||
expect(coercedActual.astNode).toBeDefined()
|
expect(coercedActual.astNode).toBeDefined()
|
||||||
|
@ -1,48 +1,20 @@
|
|||||||
import { err } from './trap'
|
import { err } from './trap'
|
||||||
import { engineCommandManager } from 'lib/singletons'
|
import { engineCommandManager } from 'lib/singletons'
|
||||||
import { parse, ProgramMemory, programMemoryInit, resultIsOk } from 'lang/wasm'
|
import { parse, resultIsOk, VariableMap } from 'lang/wasm'
|
||||||
import { PrevVariable } from 'lang/queryAst'
|
import { PrevVariable } from 'lang/queryAst'
|
||||||
import { executeAst } from 'lang/langHelpers'
|
import { executeAst } from 'lang/langHelpers'
|
||||||
import { KclExpression } from './commandTypes'
|
import { KclExpression } from './commandTypes'
|
||||||
|
|
||||||
const DUMMY_VARIABLE_NAME = '__result__'
|
const DUMMY_VARIABLE_NAME = '__result__'
|
||||||
|
|
||||||
export function programMemoryFromVariables(
|
|
||||||
variables: PrevVariable<string | number>[]
|
|
||||||
): ProgramMemory | Error {
|
|
||||||
const memory = programMemoryInit()
|
|
||||||
if (err(memory)) return memory
|
|
||||||
for (const { key, value } of variables) {
|
|
||||||
const error = memory.set(
|
|
||||||
key,
|
|
||||||
typeof value === 'number'
|
|
||||||
? {
|
|
||||||
type: 'Number',
|
|
||||||
value,
|
|
||||||
__meta: [],
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
type: 'String',
|
|
||||||
value,
|
|
||||||
__meta: [],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if (err(error)) return error
|
|
||||||
}
|
|
||||||
return memory
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate the value of the KCL expression,
|
* Calculate the value of the KCL expression,
|
||||||
* given the value and the variables that are available
|
* given the value and the variables that are available
|
||||||
*/
|
*/
|
||||||
export async function getCalculatedKclExpressionValue({
|
export async function getCalculatedKclExpressionValue(
|
||||||
value,
|
value: string,
|
||||||
programMemory,
|
variables: VariableMap
|
||||||
}: {
|
) {
|
||||||
value: string
|
|
||||||
programMemory: ProgramMemory
|
|
||||||
}) {
|
|
||||||
// Create a one-line program that assigns the value to a variable
|
// Create a one-line program that assigns the value to a variable
|
||||||
const dummyProgramCode = `const ${DUMMY_VARIABLE_NAME} = ${value}`
|
const dummyProgramCode = `const ${DUMMY_VARIABLE_NAME} = ${value}`
|
||||||
const pResult = parse(dummyProgramCode)
|
const pResult = parse(dummyProgramCode)
|
||||||
@ -53,7 +25,8 @@ export async function getCalculatedKclExpressionValue({
|
|||||||
const { execState } = await executeAst({
|
const { execState } = await executeAst({
|
||||||
ast,
|
ast,
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
programMemoryOverride: programMemory,
|
isMock: true,
|
||||||
|
variables,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Find the variable declaration for the result
|
// Find the variable declaration for the result
|
||||||
@ -65,7 +38,7 @@ export async function getCalculatedKclExpressionValue({
|
|||||||
const variableDeclaratorAstNode =
|
const variableDeclaratorAstNode =
|
||||||
resultDeclaration?.type === 'VariableDeclaration' &&
|
resultDeclaration?.type === 'VariableDeclaration' &&
|
||||||
resultDeclaration?.declaration.init
|
resultDeclaration?.declaration.init
|
||||||
const resultRawValue = execState.memory?.get(DUMMY_VARIABLE_NAME)?.value
|
const resultRawValue = execState.variables[DUMMY_VARIABLE_NAME]?.value
|
||||||
|
|
||||||
return {
|
return {
|
||||||
astNode: variableDeclaratorAstNode,
|
astNode: variableDeclaratorAstNode,
|
||||||
@ -74,17 +47,14 @@ export async function getCalculatedKclExpressionValue({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function stringToKclExpression({
|
export async function stringToKclExpression(
|
||||||
value,
|
value: string,
|
||||||
programMemory,
|
variables: VariableMap
|
||||||
}: {
|
) {
|
||||||
value: string
|
const calculatedResult = await getCalculatedKclExpressionValue(
|
||||||
programMemory: ProgramMemory
|
|
||||||
}) {
|
|
||||||
const calculatedResult = await getCalculatedKclExpressionValue({
|
|
||||||
value,
|
value,
|
||||||
programMemory,
|
variables
|
||||||
})
|
)
|
||||||
if (err(calculatedResult) || 'errors' in calculatedResult) {
|
if (err(calculatedResult) || 'errors' in calculatedResult) {
|
||||||
return calculatedResult
|
return calculatedResult
|
||||||
} else if (!calculatedResult.astNode) {
|
} else if (!calculatedResult.astNode) {
|
||||||
|
@ -80,13 +80,13 @@ const prepareToEditExtrude: PrepareToEditCallback =
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Convert the length argument from a string to a KCL expression
|
// Convert the length argument from a string to a KCL expression
|
||||||
const distanceResult = await stringToKclExpression({
|
const distanceResult = await stringToKclExpression(
|
||||||
value: codeManager.code.slice(
|
codeManager.code.slice(
|
||||||
operation.labeledArgs?.['length']?.sourceRange[0],
|
operation.labeledArgs?.['length']?.sourceRange[0],
|
||||||
operation.labeledArgs?.['length']?.sourceRange[1]
|
operation.labeledArgs?.['length']?.sourceRange[1]
|
||||||
),
|
),
|
||||||
programMemory: kclManager.programMemory.clone(),
|
{}
|
||||||
})
|
)
|
||||||
if (err(distanceResult) || 'errors' in distanceResult) {
|
if (err(distanceResult) || 'errors' in distanceResult) {
|
||||||
return baseCommand
|
return baseCommand
|
||||||
}
|
}
|
||||||
@ -163,13 +163,13 @@ const prepareToEditOffsetPlane: PrepareToEditCallback = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Convert the distance argument from a string to a KCL expression
|
// Convert the distance argument from a string to a KCL expression
|
||||||
const distanceResult = await stringToKclExpression({
|
const distanceResult = await stringToKclExpression(
|
||||||
value: codeManager.code.slice(
|
codeManager.code.slice(
|
||||||
operation.labeledArgs.offset.sourceRange[0],
|
operation.labeledArgs.offset.sourceRange[0],
|
||||||
operation.labeledArgs.offset.sourceRange[1]
|
operation.labeledArgs.offset.sourceRange[1]
|
||||||
),
|
),
|
||||||
programMemory: kclManager.programMemory.clone(),
|
{}
|
||||||
})
|
)
|
||||||
|
|
||||||
if (err(distanceResult) || 'errors' in distanceResult) {
|
if (err(distanceResult) || 'errors' in distanceResult) {
|
||||||
return baseCommand
|
return baseCommand
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
Program,
|
Program,
|
||||||
ProgramMemory,
|
executeMock,
|
||||||
executor,
|
|
||||||
SourceRange,
|
SourceRange,
|
||||||
ExecState,
|
ExecState,
|
||||||
|
VariableMap,
|
||||||
} from '../lang/wasm'
|
} from '../lang/wasm'
|
||||||
import { EngineCommandManager } from 'lang/std/engineConnection'
|
import { EngineCommandManager } from 'lang/std/engineConnection'
|
||||||
import { EngineCommand } from 'lang/std/artifactGraph'
|
import { EngineCommand } from 'lang/std/artifactGraph'
|
||||||
@ -80,18 +80,9 @@ class MockEngineCommandManager {
|
|||||||
|
|
||||||
export async function enginelessExecutor(
|
export async function enginelessExecutor(
|
||||||
ast: Node<Program>,
|
ast: Node<Program>,
|
||||||
pmo: ProgramMemory | Error = ProgramMemory.empty(),
|
usePrevMemory?: boolean,
|
||||||
path?: string
|
path?: string,
|
||||||
|
variables?: VariableMap
|
||||||
): Promise<ExecState> {
|
): Promise<ExecState> {
|
||||||
if (pmo !== null && err(pmo)) return Promise.reject(pmo)
|
return await executeMock(ast, usePrevMemory, path, variables)
|
||||||
|
|
||||||
const mockEngineCommandManager = new MockEngineCommandManager({
|
|
||||||
setIsStreamReady: () => {},
|
|
||||||
setMediaStream: () => {},
|
|
||||||
}) as any as EngineCommandManager
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
mockEngineCommandManager.startNewSession()
|
|
||||||
const execState = await executor(ast, mockEngineCommandManager, path, pmo)
|
|
||||||
await mockEngineCommandManager.waitForAllCommands()
|
|
||||||
return execState
|
|
||||||
}
|
}
|
||||||
|
@ -5,10 +5,9 @@ import { findUniqueName } from 'lang/modifyAst'
|
|||||||
import { PrevVariable, findAllPreviousVariables } from 'lang/queryAst'
|
import { PrevVariable, findAllPreviousVariables } from 'lang/queryAst'
|
||||||
import { Expr } from 'lang/wasm'
|
import { Expr } from 'lang/wasm'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import {
|
import { getCalculatedKclExpressionValue } from './kclHelpers'
|
||||||
getCalculatedKclExpressionValue,
|
import { parse, resultIsOk } from 'lang/wasm'
|
||||||
programMemoryFromVariables,
|
import { err } from 'lib/trap'
|
||||||
} from './kclHelpers'
|
|
||||||
|
|
||||||
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)
|
||||||
@ -34,7 +33,7 @@ export function useCalculateKclExpression({
|
|||||||
newVariableInsertIndex: number
|
newVariableInsertIndex: number
|
||||||
setNewVariableName: (a: string) => void
|
setNewVariableName: (a: string) => void
|
||||||
} {
|
} {
|
||||||
const { programMemory, code } = useKclContext()
|
const { variables, code } = useKclContext()
|
||||||
const { context } = useModelingContext()
|
const { context } = useModelingContext()
|
||||||
// If there is no selection, use the end of the code
|
// If there is no selection, use the end of the code
|
||||||
// so all variables are available
|
// so all variables are available
|
||||||
@ -50,7 +49,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)
|
||||||
|
|
||||||
@ -65,7 +77,7 @@ export function useCalculateKclExpression({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
programMemory.has(newVariableName) ||
|
variables[newVariableName] ||
|
||||||
newVariableName === '' ||
|
newVariableName === '' ||
|
||||||
!isValidVariableName(newVariableName)
|
!isValidVariableName(newVariableName)
|
||||||
) {
|
) {
|
||||||
@ -73,33 +85,22 @@ export function useCalculateKclExpression({
|
|||||||
} else {
|
} else {
|
||||||
setIsNewVariableNameUnique(true)
|
setIsNewVariableNameUnique(true)
|
||||||
}
|
}
|
||||||
}, [programMemory, newVariableName])
|
}, [variables, newVariableName])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!programMemory) return
|
if (!variables) return
|
||||||
const varInfo = findAllPreviousVariables(
|
const varInfo = findAllPreviousVariables(
|
||||||
kclManager.ast,
|
kclManager.ast,
|
||||||
kclManager.programMemory,
|
kclManager.variables,
|
||||||
// If there is no selection, use the end of the code
|
// If there is no selection, use the end of the code
|
||||||
selectionRange || [code.length, code.length]
|
selectionRange || [code.length, code.length]
|
||||||
)
|
)
|
||||||
setAvailableVarInfo(varInfo)
|
setAvailableVarInfo(varInfo)
|
||||||
}, [kclManager.ast, kclManager.programMemory, selectionRange])
|
}, [kclManager.ast, kclManager.variables, selectionRange])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const execAstAndSetResult = async () => {
|
const execAstAndSetResult = async () => {
|
||||||
const programMemory = programMemoryFromVariables(
|
const result = await getCalculatedKclExpressionValue(value, {})
|
||||||
availableVarInfo.variables
|
|
||||||
)
|
|
||||||
if (programMemory instanceof Error) {
|
|
||||||
setCalcResult('NAN')
|
|
||||||
setValueNode(null)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const result = await getCalculatedKclExpressionValue({
|
|
||||||
value,
|
|
||||||
programMemory,
|
|
||||||
})
|
|
||||||
if (result instanceof Error || 'errors' in result) {
|
if (result instanceof Error || 'errors' in result) {
|
||||||
setCalcResult('NAN')
|
setCalcResult('NAN')
|
||||||
setValueNode(null)
|
setValueNode(null)
|
||||||
@ -113,7 +114,7 @@ export function useCalculateKclExpression({
|
|||||||
setCalcResult('NAN')
|
setCalcResult('NAN')
|
||||||
setValueNode(null)
|
setValueNode(null)
|
||||||
})
|
})
|
||||||
}, [value, availableVarInfo, code, kclManager.programMemory])
|
}, [value, availableVarInfo, code, kclManager.variables])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
valueNode,
|
valueNode,
|
||||||
|
@ -5,7 +5,7 @@ import { findAllPreviousVariables } from 'lang/queryAst'
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
export function usePreviousVariables() {
|
export function usePreviousVariables() {
|
||||||
const { programMemory, code } = useKclContext()
|
const { variables, code } = useKclContext()
|
||||||
const { context } = useModelingContext()
|
const { context } = useModelingContext()
|
||||||
const selectionRange = context.selectionRanges.graphSelections[0]?.codeRef
|
const selectionRange = context.selectionRanges.graphSelections[0]?.codeRef
|
||||||
?.range || [code.length, code.length]
|
?.range || [code.length, code.length]
|
||||||
@ -18,14 +18,14 @@ export function usePreviousVariables() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!programMemory || !selectionRange) return
|
if (!variables || !selectionRange) return
|
||||||
const varInfo = findAllPreviousVariables(
|
const varInfo = findAllPreviousVariables(
|
||||||
kclManager.ast,
|
kclManager.ast,
|
||||||
kclManager.programMemory,
|
kclManager.variables,
|
||||||
selectionRange
|
selectionRange
|
||||||
)
|
)
|
||||||
setPreviousVariablesInfo(varInfo)
|
setPreviousVariablesInfo(varInfo)
|
||||||
}, [kclManager.ast, kclManager.programMemory, selectionRange])
|
}, [kclManager.ast, kclManager.variables, selectionRange])
|
||||||
|
|
||||||
return previousVariablesInfo
|
return previousVariablesInfo
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,8 @@ import {
|
|||||||
parse_wasm as ParseWasm,
|
parse_wasm as ParseWasm,
|
||||||
recast_wasm as RecastWasm,
|
recast_wasm as RecastWasm,
|
||||||
format_number as FormatNumber,
|
format_number as FormatNumber,
|
||||||
execute as Execute,
|
execute_with_engine as ExecuteWithEngine,
|
||||||
|
execute_mock as ExecuteMock,
|
||||||
kcl_lint as KclLint,
|
kcl_lint as KclLint,
|
||||||
modify_ast_for_sketch_wasm as ModifyAstForSketch,
|
modify_ast_for_sketch_wasm as ModifyAstForSketch,
|
||||||
is_points_ccw as IsPointsCcw,
|
is_points_ccw as IsPointsCcw,
|
||||||
@ -57,8 +58,11 @@ export const recast_wasm: typeof RecastWasm = (...args) => {
|
|||||||
export const format_number: typeof FormatNumber = (...args) => {
|
export const format_number: typeof FormatNumber = (...args) => {
|
||||||
return getModule().format_number(...args)
|
return getModule().format_number(...args)
|
||||||
}
|
}
|
||||||
export const execute: typeof Execute = (...args) => {
|
export const execute_with_engine: typeof ExecuteWithEngine = (...args) => {
|
||||||
return getModule().execute(...args)
|
return getModule().execute_with_engine(...args)
|
||||||
|
}
|
||||||
|
export const execute_mock: typeof ExecuteMock = (...args) => {
|
||||||
|
return getModule().execute_mock(...args)
|
||||||
}
|
}
|
||||||
export const kcl_lint: typeof KclLint = (...args) => {
|
export const kcl_lint: typeof KclLint = (...args) => {
|
||||||
return getModule().kcl_lint(...args)
|
return getModule().kcl_lint(...args)
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
PathToNode,
|
PathToNode,
|
||||||
ProgramMemory,
|
|
||||||
VariableDeclaration,
|
VariableDeclaration,
|
||||||
VariableDeclarator,
|
VariableDeclarator,
|
||||||
parse,
|
parse,
|
||||||
@ -735,7 +734,7 @@ export const modelingMachine = setup({
|
|||||||
const modifiedAst = await deleteFromSelection(
|
const modifiedAst = await deleteFromSelection(
|
||||||
ast,
|
ast,
|
||||||
selectionRanges.graphSelections[0],
|
selectionRanges.graphSelections[0],
|
||||||
kclManager.programMemory,
|
kclManager.variables,
|
||||||
getFaceDetails
|
getFaceDetails
|
||||||
)
|
)
|
||||||
if (err(modifiedAst)) {
|
if (err(modifiedAst)) {
|
||||||
@ -746,8 +745,7 @@ export const modelingMachine = setup({
|
|||||||
const testExecute = await executeAst({
|
const testExecute = await executeAst({
|
||||||
ast: modifiedAst,
|
ast: modifiedAst,
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
isMock: true,
|
||||||
programMemoryOverride: ProgramMemory.empty(),
|
|
||||||
})
|
})
|
||||||
if (testExecute.errors.length) {
|
if (testExecute.errors.length) {
|
||||||
toast.error(errorMessage)
|
toast.error(errorMessage)
|
||||||
@ -1256,7 +1254,7 @@ export const modelingMachine = setup({
|
|||||||
selectionRanges,
|
selectionRanges,
|
||||||
'horizontal',
|
'horizontal',
|
||||||
kclManager.ast,
|
kclManager.ast,
|
||||||
kclManager.programMemory
|
kclManager.variables
|
||||||
)
|
)
|
||||||
if (trap(constraint)) return false
|
if (trap(constraint)) return false
|
||||||
const { modifiedAst, pathToNodeMap } = constraint
|
const { modifiedAst, pathToNodeMap } = constraint
|
||||||
@ -1293,7 +1291,7 @@ export const modelingMachine = setup({
|
|||||||
selectionRanges,
|
selectionRanges,
|
||||||
'vertical',
|
'vertical',
|
||||||
kclManager.ast,
|
kclManager.ast,
|
||||||
kclManager.programMemory
|
kclManager.variables
|
||||||
)
|
)
|
||||||
if (trap(constraint)) return false
|
if (trap(constraint)) return false
|
||||||
const { modifiedAst, pathToNodeMap } = constraint
|
const { modifiedAst, pathToNodeMap } = constraint
|
||||||
|
12
src/main.ts
12
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
6
src/wasm-lib/Cargo.lock
generated
6
src/wasm-lib/Cargo.lock
generated
@ -730,7 +730,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive-docs"
|
name = "derive-docs"
|
||||||
version = "0.1.35"
|
version = "0.1.36"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"Inflector",
|
"Inflector",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
@ -1712,7 +1712,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kcl-lib"
|
name = "kcl-lib"
|
||||||
version = "0.2.34"
|
version = "0.2.35"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"approx 0.5.1",
|
"approx 0.5.1",
|
||||||
@ -1779,7 +1779,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kcl-test-server"
|
name = "kcl-test-server"
|
||||||
version = "0.1.20"
|
version = "0.1.21"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"hyper 0.14.32",
|
"hyper 0.14.32",
|
||||||
|
@ -33,6 +33,9 @@ tokio = { version = "1.41.1", features = ["rt-multi-thread", "macros", "time"] }
|
|||||||
twenty-twenty = "0.8"
|
twenty-twenty = "0.8"
|
||||||
uuid = { version = "1.11.0", features = ["v4", "js", "serde"] }
|
uuid = { version = "1.11.0", features = ["v4", "js", "serde"] }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
dhat-heap = ["kcl-lib/dhat-heap"]
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
console_error_panic_hook = "0.1.7"
|
console_error_panic_hook = "0.1.7"
|
||||||
futures = "0.3.31"
|
futures = "0.3.31"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "derive-docs"
|
name = "derive-docs"
|
||||||
description = "A tool for generating documentation from Rust derive macros"
|
description = "A tool for generating documentation from Rust derive macros"
|
||||||
version = "0.1.35"
|
version = "0.1.36"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/KittyCAD/modeling-app"
|
repository = "https://github.com/KittyCAD/modeling-app"
|
||||||
|
@ -36,5 +36,11 @@ run-sim-test test_name:
|
|||||||
{{cita}} -p kcl-lib -- simulation_tests::{{test_name}}::unparse
|
{{cita}} -p kcl-lib -- simulation_tests::{{test_name}}::unparse
|
||||||
TWENTY_TWENTY=overwrite {{cita}} -p kcl-lib -- tests::{{test_name}}::kcl_test_execute
|
TWENTY_TWENTY=overwrite {{cita}} -p kcl-lib -- tests::{{test_name}}::kcl_test_execute
|
||||||
|
|
||||||
|
overwrite-sim-test test_name:
|
||||||
|
EXPECTORATE=overwrite {{cita}} -p kcl-lib -- simulation_tests::{{test_name}}::parse
|
||||||
|
EXPECTORATE=overwrite {{cita}} -p kcl-lib -- simulation_tests::{{test_name}}::unparse
|
||||||
|
{{cita}} -p kcl-lib -- tests::{{test_name}}::kcl_test_execute
|
||||||
|
|
||||||
|
|
||||||
test:
|
test:
|
||||||
export RUST_BRACKTRACE="full" && cargo nextest run --workspace --test-threads=1
|
export RUST_BRACKTRACE="full" && cargo nextest run --workspace --test-threads=1
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "kcl-test-server"
|
name = "kcl-test-server"
|
||||||
description = "A test server for KCL"
|
description = "A test server for KCL"
|
||||||
version = "0.1.20"
|
version = "0.1.21"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "kcl-lib"
|
name = "kcl-lib"
|
||||||
description = "KittyCAD Language implementation and tools"
|
description = "KittyCAD Language implementation and tools"
|
||||||
version = "0.2.34"
|
version = "0.2.35"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/KittyCAD/modeling-app"
|
repository = "https://github.com/KittyCAD/modeling-app"
|
||||||
|
@ -944,13 +944,7 @@ mod tests {
|
|||||||
let snippet = pattern_fn.to_autocomplete_snippet().unwrap();
|
let snippet = pattern_fn.to_autocomplete_snippet().unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
snippet,
|
snippet,
|
||||||
r#"patternCircular3d({
|
r#"patternCircular3d(${0:%}, instances = ${1:10}, axis = [${2:3.14}, ${3:3.14}, ${4:3.14}], center = [${5:3.14}, ${6:3.14}, ${7:3.14}], arc_degrees = ${8:3.14}, rotate_duplicates = ${9:false})${}"#
|
||||||
instances = ${0:10},
|
|
||||||
axis = [${1:3.14}, ${2:3.14}, ${3:3.14}],
|
|
||||||
center = [${4:3.14}, ${5:3.14}, ${6:3.14}],
|
|
||||||
arcDegrees = ${7:3.14},
|
|
||||||
rotateDuplicates = ${8:false},
|
|
||||||
}, ${9:%})${}"#
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1006,11 +1000,7 @@ mod tests {
|
|||||||
let snippet = pattern_fn.to_autocomplete_snippet().unwrap();
|
let snippet = pattern_fn.to_autocomplete_snippet().unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
snippet,
|
snippet,
|
||||||
r#"patternLinear2d({
|
r#"patternLinear2d(${0:%}, instances = ${1:10}, distance = ${2:3.14}, axis = [${3:3.14}, ${4:3.14}])${}"#
|
||||||
instances = ${0:10},
|
|
||||||
distance = ${1:3.14},
|
|
||||||
axis = [${2:3.14}, ${3:3.14}],
|
|
||||||
}, ${4:%})${}"#
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,28 +11,46 @@ use crate::{
|
|||||||
walk::Node as WalkNode,
|
walk::Node as WalkNode,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::ProgramMemory;
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
/// A static mutable lock for updating the last successful execution state for the cache.
|
/// A static mutable lock for updating the last successful execution state for the cache.
|
||||||
static ref OLD_AST_MEMORY: Arc<RwLock<Option<OldAstState>>> = Default::default();
|
static ref OLD_AST: Arc<RwLock<Option<OldAstState>>> = Default::default();
|
||||||
|
// The last successful run's memory. Not cleared after an unssuccessful run.
|
||||||
|
static ref PREV_MEMORY: Arc<RwLock<Option<ProgramMemory>>> = Default::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read the old ast memory from the lock.
|
/// Read the old ast memory from the lock.
|
||||||
pub(super) async fn read_old_ast_memory() -> Option<OldAstState> {
|
pub(super) async fn read_old_ast() -> Option<OldAstState> {
|
||||||
let old_ast = OLD_AST_MEMORY.read().await;
|
let old_ast = OLD_AST.read().await;
|
||||||
old_ast.clone()
|
old_ast.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn write_old_ast_memory(old_state: OldAstState) {
|
pub(super) async fn write_old_ast(old_state: OldAstState) {
|
||||||
let mut old_ast = OLD_AST_MEMORY.write().await;
|
let mut old_ast = OLD_AST.write().await;
|
||||||
*old_ast = Some(old_state);
|
*old_ast = Some(old_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) async fn read_old_memory() -> Option<ProgramMemory> {
|
||||||
|
let old_mem = PREV_MEMORY.read().await;
|
||||||
|
old_mem.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn write_old_memory(mem: ProgramMemory) {
|
||||||
|
let mut old_mem = PREV_MEMORY.write().await;
|
||||||
|
*old_mem = Some(mem);
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn bust_cache() {
|
pub async fn bust_cache() {
|
||||||
let mut old_ast = OLD_AST_MEMORY.write().await;
|
let mut old_ast = OLD_AST.write().await;
|
||||||
// Set the cache to None.
|
|
||||||
*old_ast = None;
|
*old_ast = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn clear_mem_cache() {
|
||||||
|
let mut old_mem = PREV_MEMORY.write().await;
|
||||||
|
*old_mem = None;
|
||||||
|
}
|
||||||
|
|
||||||
/// Information for the caching an AST and smartly re-executing it if we can.
|
/// Information for the caching an AST and smartly re-executing it if we can.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct CacheInformation<'a> {
|
pub struct CacheInformation<'a> {
|
||||||
|
@ -9,16 +9,17 @@ use crate::{
|
|||||||
execution::{
|
execution::{
|
||||||
annotations,
|
annotations,
|
||||||
cad_op::{OpArg, Operation},
|
cad_op::{OpArg, Operation},
|
||||||
|
memory,
|
||||||
state::ModuleState,
|
state::ModuleState,
|
||||||
BodyType, ExecState, ExecutorContext, KclValue, MemoryFunction, Metadata, ProgramMemory, TagEngineInfo,
|
BodyType, EnvironmentRef, ExecState, ExecutorContext, KclValue, MemoryFunction, Metadata, ProgramMemory,
|
||||||
TagIdentifier,
|
TagEngineInfo, TagIdentifier,
|
||||||
},
|
},
|
||||||
modules::{ModuleId, ModulePath, ModuleRepr},
|
modules::{ModuleId, ModulePath, ModuleRepr},
|
||||||
parsing::ast::types::{
|
parsing::ast::types::{
|
||||||
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, CallExpression,
|
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, CallExpression,
|
||||||
CallExpressionKw, Expr, FunctionExpression, IfExpression, ImportPath, ImportSelector, ItemVisibility,
|
CallExpressionKw, Expr, FunctionExpression, IfExpression, ImportPath, ImportSelector, ItemVisibility,
|
||||||
LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Node, NodeRef, NonCodeValue, ObjectExpression,
|
LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Node, NodeRef, NonCodeValue, ObjectExpression,
|
||||||
PipeExpression, TagDeclarator, UnaryExpression, UnaryOperator,
|
PipeExpression, Program, TagDeclarator, UnaryExpression, UnaryOperator,
|
||||||
},
|
},
|
||||||
source_range::SourceRange,
|
source_range::SourceRange,
|
||||||
std::{
|
std::{
|
||||||
@ -113,20 +114,21 @@ impl ExecutorContext {
|
|||||||
|
|
||||||
match &import_stmt.selector {
|
match &import_stmt.selector {
|
||||||
ImportSelector::List { items } => {
|
ImportSelector::List { items } => {
|
||||||
let (_, module_memory, module_exports) = self
|
let (env_ref, module_exports) = self
|
||||||
.exec_module(module_id, exec_state, ExecutionKind::Isolated, source_range)
|
.exec_module_for_items(module_id, exec_state, ExecutionKind::Isolated, source_range)
|
||||||
.await?;
|
.await?;
|
||||||
for import_item in items {
|
for import_item in items {
|
||||||
// Extract the item from the module.
|
// Extract the item from the module.
|
||||||
let item =
|
let item = exec_state
|
||||||
module_memory
|
.memory()
|
||||||
.get(&import_item.name.name, import_item.into())
|
.get_from(&import_item.name.name, env_ref, import_item.into())
|
||||||
.map_err(|_err| {
|
.map_err(|_err| {
|
||||||
KclError::UndefinedValue(KclErrorDetails {
|
KclError::UndefinedValue(KclErrorDetails {
|
||||||
message: format!("{} is not defined in module", import_item.name.name),
|
message: format!("{} is not defined in module", import_item.name.name),
|
||||||
source_ranges: vec![SourceRange::from(&import_item.name)],
|
source_ranges: vec![SourceRange::from(&import_item.name)],
|
||||||
})
|
})
|
||||||
})?;
|
})?
|
||||||
|
.clone();
|
||||||
// Check that the item is allowed to be imported.
|
// Check that the item is allowed to be imported.
|
||||||
if !module_exports.contains(&import_item.name.name) {
|
if !module_exports.contains(&import_item.name.name) {
|
||||||
return Err(KclError::Semantic(KclErrorDetails {
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
@ -140,8 +142,8 @@ impl ExecutorContext {
|
|||||||
|
|
||||||
// Add the item to the current module.
|
// Add the item to the current module.
|
||||||
exec_state.mut_memory().add(
|
exec_state.mut_memory().add(
|
||||||
import_item.identifier(),
|
import_item.identifier().to_owned(),
|
||||||
item.clone(),
|
item,
|
||||||
SourceRange::from(&import_item.name),
|
SourceRange::from(&import_item.name),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@ -154,17 +156,21 @@ impl ExecutorContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImportSelector::Glob(_) => {
|
ImportSelector::Glob(_) => {
|
||||||
let (_, module_memory, module_exports) = self
|
let (env_ref, module_exports) = self
|
||||||
.exec_module(module_id, exec_state, ExecutionKind::Isolated, source_range)
|
.exec_module_for_items(module_id, exec_state, ExecutionKind::Isolated, source_range)
|
||||||
.await?;
|
.await?;
|
||||||
for name in module_exports.iter() {
|
for name in module_exports.iter() {
|
||||||
let item = module_memory.get(name, source_range).map_err(|_err| {
|
let item = exec_state
|
||||||
KclError::Internal(KclErrorDetails {
|
.memory()
|
||||||
message: format!("{} is not defined in module (but was exported?)", name),
|
.get_from(name, env_ref, source_range)
|
||||||
source_ranges: vec![source_range],
|
.map_err(|_err| {
|
||||||
})
|
KclError::Internal(KclErrorDetails {
|
||||||
})?;
|
message: format!("{} is not defined in module (but was exported?)", name),
|
||||||
exec_state.mut_memory().add(name, item.clone(), source_range)?;
|
source_ranges: vec![source_range],
|
||||||
|
})
|
||||||
|
})?
|
||||||
|
.clone();
|
||||||
|
exec_state.mut_memory().add(name.to_owned(), item, source_range)?;
|
||||||
|
|
||||||
if let ItemVisibility::Export = import_stmt.visibility {
|
if let ItemVisibility::Export = import_stmt.visibility {
|
||||||
exec_state.mod_local.module_exports.push(name.clone());
|
exec_state.mod_local.module_exports.push(name.clone());
|
||||||
@ -177,7 +183,7 @@ impl ExecutorContext {
|
|||||||
value: module_id,
|
value: module_id,
|
||||||
meta: vec![source_range.into()],
|
meta: vec![source_range.into()],
|
||||||
};
|
};
|
||||||
exec_state.mut_memory().add(&name, item, source_range)?;
|
exec_state.mut_memory().add(name, item, source_range)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
last_expr = None;
|
last_expr = None;
|
||||||
@ -215,7 +221,9 @@ impl ExecutorContext {
|
|||||||
StatementKind::Declaration { name: &var_name },
|
StatementKind::Declaration { name: &var_name },
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
exec_state.mut_memory().add(&var_name, memory_item, source_range)?;
|
exec_state
|
||||||
|
.mut_memory()
|
||||||
|
.add(var_name.clone(), memory_item, source_range)?;
|
||||||
|
|
||||||
// Track exports.
|
// Track exports.
|
||||||
if let ItemVisibility::Export = variable_declaration.visibility {
|
if let ItemVisibility::Export = variable_declaration.visibility {
|
||||||
@ -225,6 +233,14 @@ impl ExecutorContext {
|
|||||||
}
|
}
|
||||||
BodyItem::ReturnStatement(return_statement) => {
|
BodyItem::ReturnStatement(return_statement) => {
|
||||||
let metadata = Metadata::from(return_statement);
|
let metadata = Metadata::from(return_statement);
|
||||||
|
|
||||||
|
if body_type == BodyType::Root {
|
||||||
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
message: "Cannot return from outside a function.".to_owned(),
|
||||||
|
source_ranges: vec![metadata.source_range],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
let value = self
|
let value = self
|
||||||
.execute_expr(
|
.execute_expr(
|
||||||
&return_statement.argument,
|
&return_statement.argument,
|
||||||
@ -233,7 +249,15 @@ impl ExecutorContext {
|
|||||||
StatementKind::Expression,
|
StatementKind::Expression,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
exec_state.mut_memory().return_ = Some(value);
|
exec_state
|
||||||
|
.mut_memory()
|
||||||
|
.add(memory::RETURN_NAME.to_owned(), value, metadata.source_range)
|
||||||
|
.map_err(|_| {
|
||||||
|
KclError::Semantic(KclErrorDetails {
|
||||||
|
message: "Multiple returns from a single function.".to_owned(),
|
||||||
|
source_ranges: vec![metadata.source_range],
|
||||||
|
})
|
||||||
|
})?;
|
||||||
last_expr = None;
|
last_expr = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -273,7 +297,8 @@ impl ExecutorContext {
|
|||||||
let source = resolved_path.source(&self.fs, source_range).await?;
|
let source = resolved_path.source(&self.fs, source_range).await?;
|
||||||
// TODO handle parsing errors properly
|
// TODO handle parsing errors properly
|
||||||
let parsed = crate::parsing::parse_str(&source, id).parse_errs_as_err()?;
|
let parsed = crate::parsing::parse_str(&source, id).parse_errs_as_err()?;
|
||||||
exec_state.add_module(id, resolved_path, ModuleRepr::Kcl(parsed));
|
exec_state.add_module(id, resolved_path, ModuleRepr::Kcl(parsed, None));
|
||||||
|
|
||||||
Ok(id)
|
Ok(id)
|
||||||
}
|
}
|
||||||
ImportPath::Foreign { .. } => {
|
ImportPath::Foreign { .. } => {
|
||||||
@ -296,80 +321,116 @@ impl ExecutorContext {
|
|||||||
let id = exec_state.next_module_id();
|
let id = exec_state.next_module_id();
|
||||||
let source = resolved_path.source(&self.fs, source_range).await?;
|
let source = resolved_path.source(&self.fs, source_range).await?;
|
||||||
let parsed = crate::parsing::parse_str(&source, id).parse_errs_as_err().unwrap();
|
let parsed = crate::parsing::parse_str(&source, id).parse_errs_as_err().unwrap();
|
||||||
exec_state.add_module(id, resolved_path, ModuleRepr::Kcl(parsed));
|
exec_state.add_module(id, resolved_path, ModuleRepr::Kcl(parsed, None));
|
||||||
Ok(id)
|
Ok(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn exec_module(
|
async fn exec_module_for_items(
|
||||||
&self,
|
&self,
|
||||||
module_id: ModuleId,
|
module_id: ModuleId,
|
||||||
exec_state: &mut ExecState,
|
exec_state: &mut ExecState,
|
||||||
exec_kind: ExecutionKind,
|
exec_kind: ExecutionKind,
|
||||||
source_range: SourceRange,
|
source_range: SourceRange,
|
||||||
) -> Result<(Option<KclValue>, ProgramMemory, Vec<String>), KclError> {
|
) -> Result<(EnvironmentRef, Vec<String>), KclError> {
|
||||||
let old_units = exec_state.length_unit();
|
let path = exec_state.global.module_infos[&module_id].path.clone();
|
||||||
// TODO It sucks that we have to clone the whole module AST here
|
let mut repr = exec_state.global.module_infos[&module_id].take_repr();
|
||||||
let info = exec_state.global.module_infos[&module_id].clone();
|
// DON'T EARLY RETURN! We need to restore the module repr
|
||||||
|
|
||||||
match &info.repr {
|
let result = match &mut repr {
|
||||||
ModuleRepr::Root => Err(KclError::ImportCycle(KclErrorDetails {
|
ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
|
||||||
message: format!(
|
ModuleRepr::Kcl(_, Some((env_ref, items))) => Ok((*env_ref, items.clone())),
|
||||||
"circular import of modules is not allowed: {} -> {}",
|
ModuleRepr::Kcl(program, cache) => self
|
||||||
exec_state
|
.exec_module_from_ast(program, &path, exec_state, exec_kind, source_range)
|
||||||
.global
|
.await
|
||||||
.mod_loader
|
.map(|(_, er, items)| {
|
||||||
.import_stack
|
*cache = Some((er, items.clone()));
|
||||||
.iter()
|
(er, items)
|
||||||
.map(|p| p.as_path().to_string_lossy())
|
}),
|
||||||
.collect::<Vec<_>>()
|
ModuleRepr::Foreign(geom) => Err(KclError::Semantic(KclErrorDetails {
|
||||||
.join(" -> "),
|
message: "Cannot import items from foreign modules".to_owned(),
|
||||||
info.path
|
source_ranges: vec![geom.source_range],
|
||||||
),
|
|
||||||
source_ranges: vec![source_range],
|
|
||||||
})),
|
})),
|
||||||
ModuleRepr::Kcl(program) => {
|
ModuleRepr::Dummy => unreachable!(),
|
||||||
let mut local_state = ModuleState::new(&self.settings);
|
};
|
||||||
exec_state.global.mod_loader.enter_module(&info.path);
|
|
||||||
std::mem::swap(&mut exec_state.mod_local, &mut local_state);
|
|
||||||
let original_execution = self.engine.replace_execution_kind(exec_kind);
|
|
||||||
|
|
||||||
let result = self
|
exec_state.global.module_infos[&module_id].restore_repr(repr);
|
||||||
.exec_program(program, exec_state, crate::execution::BodyType::Root)
|
result
|
||||||
.await;
|
}
|
||||||
|
|
||||||
let new_units = exec_state.length_unit();
|
async fn exec_module_for_result(
|
||||||
std::mem::swap(&mut exec_state.mod_local, &mut local_state);
|
&self,
|
||||||
exec_state.global.mod_loader.leave_module(&info.path);
|
module_id: ModuleId,
|
||||||
if !exec_kind.is_isolated() && new_units != old_units {
|
exec_state: &mut ExecState,
|
||||||
self.engine.set_units(old_units.into(), Default::default()).await?;
|
exec_kind: ExecutionKind,
|
||||||
}
|
source_range: SourceRange,
|
||||||
self.engine.replace_execution_kind(original_execution);
|
) -> Result<Option<KclValue>, KclError> {
|
||||||
|
let path = exec_state.global.module_infos[&module_id].path.clone();
|
||||||
|
let repr = exec_state.global.module_infos[&module_id].take_repr();
|
||||||
|
// DON'T EARLY RETURN! We need to restore the module repr
|
||||||
|
|
||||||
let result = result.map_err(|err| {
|
let result = match &repr {
|
||||||
if let KclError::ImportCycle(_) = err {
|
ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
|
||||||
// It was an import cycle. Keep the original message.
|
ModuleRepr::Kcl(program, _) => self
|
||||||
err.override_source_ranges(vec![source_range])
|
.exec_module_from_ast(program, &path, exec_state, exec_kind, source_range)
|
||||||
} else {
|
.await
|
||||||
KclError::Semantic(KclErrorDetails {
|
.map(|(val, _, _)| val),
|
||||||
message: format!(
|
ModuleRepr::Foreign(geom) => super::import::send_to_engine(geom.clone(), self)
|
||||||
"Error loading imported file. Open it to view more details. {}: {}",
|
.await
|
||||||
info.path,
|
.map(|geom| Some(KclValue::ImportedGeometry(geom))),
|
||||||
err.message()
|
ModuleRepr::Dummy => unreachable!(),
|
||||||
),
|
};
|
||||||
source_ranges: vec![source_range],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok((result, local_state.memory, local_state.module_exports))
|
exec_state.global.module_infos[&module_id].restore_repr(repr);
|
||||||
}
|
result
|
||||||
ModuleRepr::Foreign(geom) => {
|
}
|
||||||
let geom = super::import::send_to_engine(geom.clone(), self).await?;
|
|
||||||
Ok((Some(KclValue::ImportedGeometry(geom)), ProgramMemory::new(), Vec::new()))
|
async fn exec_module_from_ast(
|
||||||
}
|
&self,
|
||||||
|
program: &Node<Program>,
|
||||||
|
path: &ModulePath,
|
||||||
|
exec_state: &mut ExecState,
|
||||||
|
exec_kind: ExecutionKind,
|
||||||
|
source_range: SourceRange,
|
||||||
|
) -> Result<(Option<KclValue>, EnvironmentRef, Vec<String>), KclError> {
|
||||||
|
let old_units = exec_state.length_unit();
|
||||||
|
let mut local_state = ModuleState::new(&self.settings);
|
||||||
|
exec_state.global.mod_loader.enter_module(path);
|
||||||
|
std::mem::swap(&mut exec_state.mod_local, &mut local_state);
|
||||||
|
exec_state.mut_memory().push_new_root_env();
|
||||||
|
let original_execution = self.engine.replace_execution_kind(exec_kind);
|
||||||
|
|
||||||
|
let result = self
|
||||||
|
.exec_program(program, exec_state, crate::execution::BodyType::Root)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let new_units = exec_state.length_unit();
|
||||||
|
std::mem::swap(&mut exec_state.mod_local, &mut local_state);
|
||||||
|
let env_ref = exec_state.mut_memory().pop_env();
|
||||||
|
exec_state.global.mod_loader.leave_module(path);
|
||||||
|
if !exec_kind.is_isolated() && new_units != old_units {
|
||||||
|
self.engine.set_units(old_units.into(), Default::default()).await?;
|
||||||
}
|
}
|
||||||
|
self.engine.replace_execution_kind(original_execution);
|
||||||
|
|
||||||
|
result
|
||||||
|
.map_err(|err| {
|
||||||
|
if let KclError::ImportCycle(_) = err {
|
||||||
|
// It was an import cycle. Keep the original message.
|
||||||
|
err.override_source_ranges(vec![source_range])
|
||||||
|
} else {
|
||||||
|
KclError::Semantic(KclErrorDetails {
|
||||||
|
message: format!(
|
||||||
|
"Error loading imported file. Open it to view more details. {}: {}",
|
||||||
|
path,
|
||||||
|
err.message()
|
||||||
|
),
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(|result| (result, env_ref, local_state.module_exports))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_recursion]
|
#[async_recursion]
|
||||||
@ -387,39 +448,33 @@ impl ExecutorContext {
|
|||||||
Expr::Identifier(identifier) => {
|
Expr::Identifier(identifier) => {
|
||||||
let value = exec_state.memory().get(&identifier.name, identifier.into())?.clone();
|
let value = exec_state.memory().get(&identifier.name, identifier.into())?.clone();
|
||||||
if let KclValue::Module { value: module_id, meta } = value {
|
if let KclValue::Module { value: module_id, meta } = value {
|
||||||
let (result, _, _) = self
|
self.exec_module_for_result(module_id, exec_state, ExecutionKind::Normal, metadata.source_range)
|
||||||
.exec_module(module_id, exec_state, ExecutionKind::Normal, metadata.source_range)
|
.await?
|
||||||
.await?;
|
.unwrap_or_else(|| {
|
||||||
result.unwrap_or_else(|| {
|
// The module didn't have a return value. Currently,
|
||||||
// The module didn't have a return value. Currently,
|
// the only way to have a return value is with the final
|
||||||
// the only way to have a return value is with the final
|
// statement being an expression statement.
|
||||||
// statement being an expression statement.
|
//
|
||||||
//
|
// TODO: Make a warning when we support them in the
|
||||||
// TODO: Make a warning when we support them in the
|
// execution phase.
|
||||||
// execution phase.
|
let mut new_meta = vec![metadata.to_owned()];
|
||||||
let mut new_meta = vec![metadata.to_owned()];
|
new_meta.extend(meta);
|
||||||
new_meta.extend(meta);
|
KclValue::KclNone {
|
||||||
KclValue::KclNone {
|
value: Default::default(),
|
||||||
value: Default::default(),
|
meta: new_meta,
|
||||||
meta: new_meta,
|
}
|
||||||
}
|
})
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
value
|
value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expr::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, self).await?,
|
Expr::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, self).await?,
|
||||||
Expr::FunctionExpression(function_expression) => {
|
Expr::FunctionExpression(function_expression) => KclValue::Function {
|
||||||
// Cloning memory here is crucial for semantics so that we close
|
expression: function_expression.clone(),
|
||||||
// over variables. Variables defined lexically later shouldn't
|
meta: vec![metadata.to_owned()],
|
||||||
// be available to the function body.
|
func: None,
|
||||||
KclValue::Function {
|
memory: exec_state.mut_memory().snapshot(),
|
||||||
expression: function_expression.clone(),
|
},
|
||||||
meta: vec![metadata.to_owned()],
|
|
||||||
func: None,
|
|
||||||
memory: Box::new(exec_state.memory().clone()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Expr::CallExpression(call_expression) => call_expression.execute(exec_state, self).await?,
|
Expr::CallExpression(call_expression) => call_expression.execute(exec_state, self).await?,
|
||||||
Expr::CallExpressionKw(call_expression) => call_expression.execute(exec_state, self).await?,
|
Expr::CallExpressionKw(call_expression) => call_expression.execute(exec_state, self).await?,
|
||||||
Expr::PipeExpression(pipe_expression) => pipe_expression.get_result(exec_state, self).await?,
|
Expr::PipeExpression(pipe_expression) => pipe_expression.get_result(exec_state, self).await?,
|
||||||
@ -456,7 +511,7 @@ impl ExecutorContext {
|
|||||||
.await?;
|
.await?;
|
||||||
exec_state
|
exec_state
|
||||||
.mut_memory()
|
.mut_memory()
|
||||||
.add(&expr.label.name, result.clone(), init.into())?;
|
.add(expr.label.name.clone(), result.clone(), init.into())?;
|
||||||
// TODO this lets us use the label as a variable name, but not as a tag in most cases
|
// TODO this lets us use the label as a variable name, but not as a tag in most cases
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
@ -886,7 +941,7 @@ impl Node<CallExpressionKw> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Attempt to call the function.
|
// Attempt to call the function.
|
||||||
let result = {
|
let mut return_value = {
|
||||||
// Don't early-return in this block.
|
// Don't early-return in this block.
|
||||||
let result = func.std_lib_fn()(exec_state, args).await;
|
let result = func.std_lib_fn()(exec_state, args).await;
|
||||||
|
|
||||||
@ -900,9 +955,8 @@ impl Node<CallExpressionKw> {
|
|||||||
exec_state.mod_local.operations.push(op);
|
exec_state.mod_local.operations.push(op);
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
};
|
}?;
|
||||||
|
|
||||||
let mut return_value = result?;
|
|
||||||
update_memory_for_tags_of_geometry(&mut return_value, exec_state)?;
|
update_memory_for_tags_of_geometry(&mut return_value, exec_state)?;
|
||||||
|
|
||||||
Ok(return_value)
|
Ok(return_value)
|
||||||
@ -912,7 +966,6 @@ impl Node<CallExpressionKw> {
|
|||||||
// Clone the function so that we can use a mutable reference to
|
// Clone the function so that we can use a mutable reference to
|
||||||
// exec_state.
|
// exec_state.
|
||||||
let func = exec_state.memory().get(fn_name, source_range)?.clone();
|
let func = exec_state.memory().get(fn_name, source_range)?.clone();
|
||||||
let fn_dynamic_state = exec_state.mod_local.dynamic_state.merge(exec_state.memory());
|
|
||||||
|
|
||||||
// Track call operation.
|
// Track call operation.
|
||||||
let op_labeled_args = args
|
let op_labeled_args = args
|
||||||
@ -932,20 +985,14 @@ impl Node<CallExpressionKw> {
|
|||||||
source_range: callsite,
|
source_range: callsite,
|
||||||
});
|
});
|
||||||
|
|
||||||
let return_value = {
|
let return_value = func
|
||||||
let previous_dynamic_state =
|
.call_fn_kw(args, exec_state, ctx.clone(), callsite)
|
||||||
std::mem::replace(&mut exec_state.mod_local.dynamic_state, fn_dynamic_state);
|
.await
|
||||||
let result = func
|
.map_err(|e| {
|
||||||
.call_fn_kw(args, exec_state, ctx.clone(), callsite)
|
// Add the call expression to the source ranges.
|
||||||
.await
|
// TODO currently ignored by the frontend
|
||||||
.map_err(|e| {
|
e.add_source_ranges(vec![source_range])
|
||||||
// Add the call expression to the source ranges.
|
})?;
|
||||||
// TODO currently ignored by the frontend
|
|
||||||
e.add_source_ranges(vec![source_range])
|
|
||||||
});
|
|
||||||
exec_state.mod_local.dynamic_state = previous_dynamic_state;
|
|
||||||
result?
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = return_value.ok_or_else(move || {
|
let result = return_value.ok_or_else(move || {
|
||||||
let mut source_ranges: Vec<SourceRange> = vec![source_range];
|
let mut source_ranges: Vec<SourceRange> = vec![source_range];
|
||||||
@ -1018,9 +1065,11 @@ impl Node<CallExpression> {
|
|||||||
ctx.clone(),
|
ctx.clone(),
|
||||||
exec_state.mod_local.pipe_value.clone().map(Arg::synthetic),
|
exec_state.mod_local.pipe_value.clone().map(Arg::synthetic),
|
||||||
);
|
);
|
||||||
let result = {
|
let mut return_value = {
|
||||||
// Don't early-return in this block.
|
// Don't early-return in this block.
|
||||||
|
exec_state.mut_memory().push_new_env_for_rust_call();
|
||||||
let result = func.std_lib_fn()(exec_state, args).await;
|
let result = func.std_lib_fn()(exec_state, args).await;
|
||||||
|
exec_state.mut_memory().pop_env();
|
||||||
|
|
||||||
if let Some(mut op) = op {
|
if let Some(mut op) = op {
|
||||||
op.set_std_lib_call_is_error(result.is_err());
|
op.set_std_lib_call_is_error(result.is_err());
|
||||||
@ -1032,9 +1081,8 @@ impl Node<CallExpression> {
|
|||||||
exec_state.mod_local.operations.push(op);
|
exec_state.mod_local.operations.push(op);
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
};
|
}?;
|
||||||
|
|
||||||
let mut return_value = result?;
|
|
||||||
update_memory_for_tags_of_geometry(&mut return_value, exec_state)?;
|
update_memory_for_tags_of_geometry(&mut return_value, exec_state)?;
|
||||||
|
|
||||||
Ok(return_value)
|
Ok(return_value)
|
||||||
@ -1044,7 +1092,6 @@ impl Node<CallExpression> {
|
|||||||
// Clone the function so that we can use a mutable reference to
|
// Clone the function so that we can use a mutable reference to
|
||||||
// exec_state.
|
// exec_state.
|
||||||
let func = exec_state.memory().get(fn_name, source_range)?.clone();
|
let func = exec_state.memory().get(fn_name, source_range)?.clone();
|
||||||
let fn_dynamic_state = exec_state.mod_local.dynamic_state.merge(exec_state.memory());
|
|
||||||
|
|
||||||
// Track call operation.
|
// Track call operation.
|
||||||
exec_state
|
exec_state
|
||||||
@ -1059,17 +1106,11 @@ impl Node<CallExpression> {
|
|||||||
source_range: callsite,
|
source_range: callsite,
|
||||||
});
|
});
|
||||||
|
|
||||||
let return_value = {
|
let return_value = func.call_fn(fn_args, exec_state, ctx.clone()).await.map_err(|e| {
|
||||||
let previous_dynamic_state =
|
// Add the call expression to the source ranges.
|
||||||
std::mem::replace(&mut exec_state.mod_local.dynamic_state, fn_dynamic_state);
|
// TODO currently ignored by the frontend
|
||||||
let result = func.call_fn(fn_args, exec_state, ctx.clone()).await.map_err(|e| {
|
e.add_source_ranges(vec![source_range])
|
||||||
// Add the call expression to the source ranges.
|
})?;
|
||||||
// TODO currently ignored by the frontend
|
|
||||||
e.add_source_ranges(vec![source_range])
|
|
||||||
});
|
|
||||||
exec_state.mod_local.dynamic_state = previous_dynamic_state;
|
|
||||||
result?
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = return_value.ok_or_else(move || {
|
let result = return_value.ok_or_else(move || {
|
||||||
let mut source_ranges: Vec<SourceRange> = vec![source_range];
|
let mut source_ranges: Vec<SourceRange> = vec![source_range];
|
||||||
@ -1103,15 +1144,29 @@ fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut Ex
|
|||||||
match result {
|
match result {
|
||||||
KclValue::Sketch { value: ref mut sketch } => {
|
KclValue::Sketch { value: ref mut sketch } => {
|
||||||
for (_, tag) in sketch.tags.iter() {
|
for (_, tag) in sketch.tags.iter() {
|
||||||
exec_state.mut_memory().update_tag(&tag.value, tag.clone())?;
|
exec_state
|
||||||
|
.mut_memory()
|
||||||
|
.insert_or_update(tag.value.clone(), KclValue::TagIdentifier(Box::new(tag.clone())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KclValue::Solid { ref mut value } => {
|
KclValue::Solid { ref mut value } => {
|
||||||
for v in &value.value {
|
for v in &value.value {
|
||||||
if let Some(tag) = v.get_tag() {
|
if let Some(tag) = v.get_tag() {
|
||||||
// Get the past tag and update it.
|
// Get the past tag and update it.
|
||||||
let mut t = if let Some(t) = value.sketch.tags.get(&tag.name) {
|
let tag_id = if let Some(t) = value.sketch.tags.get(&tag.name) {
|
||||||
t.clone()
|
let mut t = t.clone();
|
||||||
|
let Some(ref info) = t.info else {
|
||||||
|
return Err(KclError::Internal(KclErrorDetails {
|
||||||
|
message: format!("Tag {} does not have path info", tag.name),
|
||||||
|
source_ranges: vec![tag.into()],
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut info = info.clone();
|
||||||
|
info.surface = Some(v.clone());
|
||||||
|
info.sketch = value.id;
|
||||||
|
t.info = Some(info);
|
||||||
|
t
|
||||||
} else {
|
} else {
|
||||||
// It's probably a fillet or a chamfer.
|
// It's probably a fillet or a chamfer.
|
||||||
// Initialize it.
|
// Initialize it.
|
||||||
@ -1129,29 +1184,40 @@ fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut Ex
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(ref info) = t.info else {
|
exec_state
|
||||||
return Err(KclError::Semantic(KclErrorDetails {
|
.mut_memory()
|
||||||
message: format!("Tag {} does not have path info", tag.name),
|
.insert_or_update(tag.name.clone(), KclValue::TagIdentifier(Box::new(tag_id.clone())));
|
||||||
source_ranges: vec![tag.into()],
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut info = info.clone();
|
|
||||||
info.surface = Some(v.clone());
|
|
||||||
info.sketch = value.id;
|
|
||||||
t.info = Some(info);
|
|
||||||
|
|
||||||
exec_state.mut_memory().update_tag(&tag.name, t.clone())?;
|
|
||||||
|
|
||||||
// update the sketch tags.
|
// update the sketch tags.
|
||||||
value.sketch.tags.insert(tag.name.clone(), t);
|
value.sketch.tags.insert(tag.name.clone(), tag_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the stale sketch in memory and update it.
|
// Find the stale sketch in memory and update it.
|
||||||
let cur_env_index = exec_state.memory().current_env.index();
|
if !value.sketch.tags.is_empty() {
|
||||||
if let Some(current_env) = exec_state.mut_memory().environments.get_mut(cur_env_index) {
|
let updates: Vec<_> = exec_state
|
||||||
current_env.update_sketch_tags(&value.sketch);
|
.memory()
|
||||||
|
.find_all_in_current_env(|v| match v {
|
||||||
|
KclValue::Sketch { value: sk } => sk.artifact_id == value.sketch.artifact_id,
|
||||||
|
_ => false,
|
||||||
|
})
|
||||||
|
.map(|(k, v)| {
|
||||||
|
let mut sketch = v.as_sketch().unwrap().clone();
|
||||||
|
for (tag_name, tag_id) in value.sketch.tags.iter() {
|
||||||
|
sketch.tags.insert(tag_name.clone(), tag_id.clone());
|
||||||
|
}
|
||||||
|
(
|
||||||
|
k.clone(),
|
||||||
|
KclValue::Sketch {
|
||||||
|
value: Box::new(sketch),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
updates
|
||||||
|
.into_iter()
|
||||||
|
.for_each(|(k, v)| exec_state.mut_memory().insert_or_update(k, v))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@ -1171,7 +1237,7 @@ impl Node<TagDeclarator> {
|
|||||||
|
|
||||||
exec_state
|
exec_state
|
||||||
.mut_memory()
|
.mut_memory()
|
||||||
.add(&self.name, memory_item.clone(), self.into())?;
|
.add(self.name.clone(), memory_item.clone(), self.into())?;
|
||||||
|
|
||||||
Ok(self.into())
|
Ok(self.into())
|
||||||
}
|
}
|
||||||
@ -1450,8 +1516,8 @@ impl Node<PipeExpression> {
|
|||||||
fn assign_args_to_params(
|
fn assign_args_to_params(
|
||||||
function_expression: NodeRef<'_, FunctionExpression>,
|
function_expression: NodeRef<'_, FunctionExpression>,
|
||||||
args: Vec<Arg>,
|
args: Vec<Arg>,
|
||||||
mut fn_memory: ProgramMemory,
|
fn_memory: &mut ProgramMemory,
|
||||||
) -> Result<ProgramMemory, KclError> {
|
) -> Result<(), KclError> {
|
||||||
let num_args = function_expression.number_of_args();
|
let num_args = function_expression.number_of_args();
|
||||||
let (min_params, max_params) = num_args.into_inner();
|
let (min_params, max_params) = num_args.into_inner();
|
||||||
let n = args.len();
|
let n = args.len();
|
||||||
@ -1475,14 +1541,18 @@ fn assign_args_to_params(
|
|||||||
for (index, param) in function_expression.params.iter().enumerate() {
|
for (index, param) in function_expression.params.iter().enumerate() {
|
||||||
if let Some(arg) = args.get(index) {
|
if let Some(arg) = args.get(index) {
|
||||||
// Argument was provided.
|
// Argument was provided.
|
||||||
fn_memory.add(¶m.identifier.name, arg.value.clone(), (¶m.identifier).into())?;
|
fn_memory.add(
|
||||||
|
param.identifier.name.clone(),
|
||||||
|
arg.value.clone(),
|
||||||
|
(¶m.identifier).into(),
|
||||||
|
)?;
|
||||||
} else {
|
} else {
|
||||||
// Argument was not provided.
|
// Argument was not provided.
|
||||||
if let Some(ref default_val) = param.default_value {
|
if let Some(ref default_val) = param.default_value {
|
||||||
// If the corresponding parameter is optional,
|
// If the corresponding parameter is optional,
|
||||||
// then it's fine, the user doesn't need to supply it.
|
// then it's fine, the user doesn't need to supply it.
|
||||||
fn_memory.add(
|
fn_memory.add(
|
||||||
¶m.identifier.name,
|
param.identifier.name.clone(),
|
||||||
default_val.clone().into(),
|
default_val.clone().into(),
|
||||||
(¶m.identifier).into(),
|
(¶m.identifier).into(),
|
||||||
)?;
|
)?;
|
||||||
@ -1493,14 +1563,14 @@ fn assign_args_to_params(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(fn_memory)
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assign_args_to_params_kw(
|
fn assign_args_to_params_kw(
|
||||||
function_expression: NodeRef<'_, FunctionExpression>,
|
function_expression: NodeRef<'_, FunctionExpression>,
|
||||||
mut args: crate::std::args::KwArgs,
|
mut args: crate::std::args::KwArgs,
|
||||||
mut fn_memory: ProgramMemory,
|
fn_memory: &mut ProgramMemory,
|
||||||
) -> Result<ProgramMemory, KclError> {
|
) -> Result<(), KclError> {
|
||||||
// Add the arguments to the memory. A new call frame should have already
|
// Add the arguments to the memory. A new call frame should have already
|
||||||
// been created.
|
// been created.
|
||||||
let source_ranges = vec![function_expression.into()];
|
let source_ranges = vec![function_expression.into()];
|
||||||
@ -1522,7 +1592,7 @@ fn assign_args_to_params_kw(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
fn_memory.add(¶m.identifier.name, arg_val, (¶m.identifier).into())?;
|
fn_memory.add(param.identifier.name.clone(), arg_val, (¶m.identifier).into())?;
|
||||||
} else {
|
} else {
|
||||||
let Some(unlabeled) = args.unlabeled.take() else {
|
let Some(unlabeled) = args.unlabeled.take() else {
|
||||||
let param_name = ¶m.identifier.name;
|
let param_name = ¶m.identifier.name;
|
||||||
@ -1540,18 +1610,18 @@ fn assign_args_to_params_kw(
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
fn_memory.add(
|
fn_memory.add(
|
||||||
¶m.identifier.name,
|
param.identifier.name.clone(),
|
||||||
unlabeled.value.clone(),
|
unlabeled.value.clone(),
|
||||||
(¶m.identifier).into(),
|
(¶m.identifier).into(),
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(fn_memory)
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn call_user_defined_function(
|
pub(crate) async fn call_user_defined_function(
|
||||||
args: Vec<Arg>,
|
args: Vec<Arg>,
|
||||||
memory: &ProgramMemory,
|
memory: EnvironmentRef,
|
||||||
function_expression: NodeRef<'_, FunctionExpression>,
|
function_expression: NodeRef<'_, FunctionExpression>,
|
||||||
exec_state: &mut ExecState,
|
exec_state: &mut ExecState,
|
||||||
ctx: &ExecutorContext,
|
ctx: &ExecutorContext,
|
||||||
@ -1559,29 +1629,32 @@ pub(crate) async fn call_user_defined_function(
|
|||||||
// Create a new environment to execute the function body in so that local
|
// Create a new environment to execute the function body in so that local
|
||||||
// variables shadow variables in the parent scope. The new environment's
|
// variables shadow variables in the parent scope. The new environment's
|
||||||
// parent should be the environment of the closure.
|
// parent should be the environment of the closure.
|
||||||
let mut body_memory = memory.clone();
|
exec_state.mut_memory().push_new_env_for_call(memory);
|
||||||
let body_env = body_memory.new_env_for_call(memory.current_env);
|
if let Err(e) = assign_args_to_params(function_expression, args, exec_state.mut_memory()) {
|
||||||
body_memory.current_env = body_env;
|
exec_state.mut_memory().pop_env();
|
||||||
let fn_memory = assign_args_to_params(function_expression, args, body_memory)?;
|
return Err(e);
|
||||||
|
}
|
||||||
|
|
||||||
// Execute the function body using the memory we just created.
|
// Execute the function body using the memory we just created.
|
||||||
let (result, fn_memory) = {
|
let result = ctx
|
||||||
let previous_memory = std::mem::replace(&mut exec_state.mod_local.memory, fn_memory);
|
.exec_program(&function_expression.body, exec_state, BodyType::Block)
|
||||||
let result = ctx
|
.await;
|
||||||
.exec_program(&function_expression.body, exec_state, BodyType::Block)
|
let result = result.map(|_| {
|
||||||
.await;
|
exec_state
|
||||||
// Restore the previous memory.
|
.memory()
|
||||||
let fn_memory = std::mem::replace(&mut exec_state.mod_local.memory, previous_memory);
|
.get(memory::RETURN_NAME, function_expression.as_source_range())
|
||||||
|
.ok()
|
||||||
|
.cloned()
|
||||||
|
});
|
||||||
|
// Restore the previous memory.
|
||||||
|
exec_state.mut_memory().pop_env();
|
||||||
|
|
||||||
(result, fn_memory)
|
result
|
||||||
};
|
|
||||||
|
|
||||||
result.map(|_| fn_memory.return_)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn call_user_defined_function_kw(
|
pub(crate) async fn call_user_defined_function_kw(
|
||||||
args: crate::std::args::KwArgs,
|
args: crate::std::args::KwArgs,
|
||||||
memory: &ProgramMemory,
|
memory: EnvironmentRef,
|
||||||
function_expression: NodeRef<'_, FunctionExpression>,
|
function_expression: NodeRef<'_, FunctionExpression>,
|
||||||
exec_state: &mut ExecState,
|
exec_state: &mut ExecState,
|
||||||
ctx: &ExecutorContext,
|
ctx: &ExecutorContext,
|
||||||
@ -1589,31 +1662,34 @@ pub(crate) async fn call_user_defined_function_kw(
|
|||||||
// Create a new environment to execute the function body in so that local
|
// Create a new environment to execute the function body in so that local
|
||||||
// variables shadow variables in the parent scope. The new environment's
|
// variables shadow variables in the parent scope. The new environment's
|
||||||
// parent should be the environment of the closure.
|
// parent should be the environment of the closure.
|
||||||
let mut body_memory = memory.clone();
|
exec_state.mut_memory().push_new_env_for_call(memory);
|
||||||
let body_env = body_memory.new_env_for_call(memory.current_env);
|
if let Err(e) = assign_args_to_params_kw(function_expression, args, exec_state.mut_memory()) {
|
||||||
body_memory.current_env = body_env;
|
exec_state.mut_memory().pop_env();
|
||||||
let fn_memory = assign_args_to_params_kw(function_expression, args, body_memory)?;
|
return Err(e);
|
||||||
|
}
|
||||||
|
|
||||||
// Execute the function body using the memory we just created.
|
// Execute the function body using the memory we just created.
|
||||||
let (result, fn_memory) = {
|
let result = ctx
|
||||||
let previous_memory = std::mem::replace(&mut exec_state.mod_local.memory, fn_memory);
|
.exec_program(&function_expression.body, exec_state, BodyType::Block)
|
||||||
let result = ctx
|
.await;
|
||||||
.exec_program(&function_expression.body, exec_state, BodyType::Block)
|
let result = result.map(|_| {
|
||||||
.await;
|
exec_state
|
||||||
// Restore the previous memory.
|
.memory()
|
||||||
let fn_memory = std::mem::replace(&mut exec_state.mod_local.memory, previous_memory);
|
.get(memory::RETURN_NAME, function_expression.as_source_range())
|
||||||
|
.ok()
|
||||||
|
.cloned()
|
||||||
|
});
|
||||||
|
// Restore the previous memory.
|
||||||
|
exec_state.mut_memory().pop_env();
|
||||||
|
|
||||||
(result, fn_memory)
|
result
|
||||||
};
|
|
||||||
|
|
||||||
result.map(|_| fn_memory.return_)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A function being used as a parameter into a stdlib function. This is a
|
/// A function being used as a parameter into a stdlib function. This is a
|
||||||
/// closure, plus everything needed to execute it.
|
/// closure, plus everything needed to execute it.
|
||||||
pub struct FunctionParam<'a> {
|
pub struct FunctionParam<'a> {
|
||||||
pub inner: Option<&'a MemoryFunction>,
|
pub inner: Option<&'a MemoryFunction>,
|
||||||
pub memory: ProgramMemory,
|
pub memory: EnvironmentRef,
|
||||||
pub fn_expr: crate::parsing::ast::types::BoxNode<FunctionExpression>,
|
pub fn_expr: crate::parsing::ast::types::BoxNode<FunctionExpression>,
|
||||||
pub meta: Vec<Metadata>,
|
pub meta: Vec<Metadata>,
|
||||||
pub ctx: ExecutorContext,
|
pub ctx: ExecutorContext,
|
||||||
@ -1624,7 +1700,7 @@ impl FunctionParam<'_> {
|
|||||||
if let Some(inner) = self.inner {
|
if let Some(inner) = self.inner {
|
||||||
inner(
|
inner(
|
||||||
args,
|
args,
|
||||||
self.memory.clone(),
|
self.memory,
|
||||||
self.fn_expr.clone(),
|
self.fn_expr.clone(),
|
||||||
self.meta.clone(),
|
self.meta.clone(),
|
||||||
exec_state,
|
exec_state,
|
||||||
@ -1632,7 +1708,7 @@ impl FunctionParam<'_> {
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
} else {
|
} else {
|
||||||
call_user_defined_function(args, &self.memory, self.fn_expr.as_ref(), exec_state, &self.ctx).await
|
call_user_defined_function(args, self.memory, self.fn_expr.as_ref(), exec_state, &self.ctx).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1650,8 +1726,12 @@ impl JsonSchema for FunctionParam<'_> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
use crate::{
|
||||||
|
execution::parse_execute,
|
||||||
|
parsing::ast::types::{DefaultParamVal, Identifier, Parameter},
|
||||||
|
};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::parsing::ast::types::{DefaultParamVal, Identifier, Parameter};
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_assign_args_to_params() {
|
fn test_assign_args_to_params() {
|
||||||
@ -1690,7 +1770,7 @@ mod test {
|
|||||||
let mut program_memory = ProgramMemory::new();
|
let mut program_memory = ProgramMemory::new();
|
||||||
for (name, item) in items {
|
for (name, item) in items {
|
||||||
program_memory
|
program_memory
|
||||||
.add(name.as_str(), item.clone(), SourceRange::default())
|
.add(name.clone(), item.clone(), SourceRange::default())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
program_memory
|
program_memory
|
||||||
@ -1775,11 +1855,26 @@ mod test {
|
|||||||
digest: None,
|
digest: None,
|
||||||
});
|
});
|
||||||
let args = args.into_iter().map(Arg::synthetic).collect();
|
let args = args.into_iter().map(Arg::synthetic).collect();
|
||||||
let actual = assign_args_to_params(func_expr, args, ProgramMemory::new());
|
let mut actual = ProgramMemory::new();
|
||||||
|
let actual = assign_args_to_params(func_expr, args, &mut actual).map(|_| actual);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
actual, expected,
|
actual, expected,
|
||||||
"failed test '{test_name}':\ngot {actual:?}\nbut expected\n{expected:?}"
|
"failed test '{test_name}':\ngot {actual:?}\nbut expected\n{expected:?}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn multiple_returns() {
|
||||||
|
let program = r#"fn foo() {
|
||||||
|
return 0
|
||||||
|
return 42
|
||||||
|
}
|
||||||
|
|
||||||
|
a = foo()
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let result = parse_execute(program).await;
|
||||||
|
assert!(result.unwrap_err().to_string().contains("return"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -564,29 +564,8 @@ pub struct Solid {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Solid {
|
impl Solid {
|
||||||
pub(crate) fn get_all_edge_cut_ids(&self) -> Vec<uuid::Uuid> {
|
pub(crate) fn get_all_edge_cut_ids(&self) -> impl Iterator<Item = uuid::Uuid> + '_ {
|
||||||
self.edge_cuts.iter().map(|foc| foc.id()).collect()
|
self.edge_cuts.iter().map(|foc| foc.id())
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An solid ID and its fillet and chamfer IDs. This is needed for lazy
|
|
||||||
/// fillet evaluation.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
|
||||||
pub struct SolidLazyIds {
|
|
||||||
pub solid_id: uuid::Uuid,
|
|
||||||
pub sketch_id: uuid::Uuid,
|
|
||||||
/// Chamfers or fillets on this solid.
|
|
||||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
|
||||||
pub edge_cuts: Vec<uuid::Uuid>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&Solid> for SolidLazyIds {
|
|
||||||
fn from(eg: &Solid) -> Self {
|
|
||||||
Self {
|
|
||||||
solid_id: eg.id,
|
|
||||||
sketch_id: eg.sketch.id,
|
|
||||||
edge_cuts: eg.edge_cuts.iter().map(|foc| foc.id()).collect(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user