Compare commits

...

20 Commits

Author SHA1 Message Date
9b2ae8f4b2 Merge branch 'main' into pierremtb/adhoc/update-kcl-samples 2025-02-12 16:48:29 -05:00
592c5259c6 KCL: Update patternTransform and 2d to use kwargs (#5348)
* KCL: Update patternTransform and 2d to use kwargs

* Update docs

* Convert segment functions to use keyword args

* Regenerate docs, change branch of kcl-samples
2025-02-12 21:47:02 +00:00
2949e6d325 Fixes plus pointing to @adamchalmers' 72d6d197d055a42a0afb9bedebae69f12fd3d3d9 2025-02-12 16:35:48 -05:00
61436caa0b A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) 2025-02-12 21:02:36 +00:00
ff3d961b48 A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) 2025-02-12 20:55:39 +00:00
31a6b6d967 yarn can go to hell 2025-02-12 15:48:05 -05:00
64e4fec0fc Replace source with . 2025-02-12 15:42:44 -05:00
93c872a747 Fix circular dep 2025-02-12 15:34:38 -05:00
b6101ae154 Fix package.json 2025-02-12 15:25:59 -05:00
4b2e22c867 Lint 2025-02-12 15:19:29 -05:00
41c543f898 Add VITE_KC_KCL_SAMPLES_REF env var 2025-02-12 15:19:19 -05:00
e8c206be77 Update kcl-samples 2025-02-12 13:12:18 -05:00
950f5cebfd Options and docs for foreign imports (#5351)
* Annotations for imports of foreign files

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Document foreign imports

Signed-off-by: Nick Cameron <nrc@ncameron.org>

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-02-13 06:24:27 +13:00
4c0ea136e0 Remove artifact graph mind maps (#5349)
* Remove artifact graph mind maps

* Delete mind map snap files
2025-02-11 19:28:00 -05:00
81c0035adc Remove unneeded AST test in TypeScript (#5343) 2025-02-11 23:55:05 +00:00
c68e5d7774 KCL: Linear/circular pattern in stdlib should use kwargs (#5315)
Part of https://github.com/KittyCAD/modeling-app/issues/4600
2025-02-11 16:06:47 -06:00
ed8a0e4aaa Fix formatting to preserve annotations when program is otherwise empty (#5310)
* Fix formatting to preserve annotations when program is otherwise empty

* Change to not allocate extra String
2025-02-11 21:58:48 +00:00
322f398049 ProgramMemory refactor - eliminate copies of memory (#5273)
* refactor program memory for encapsulation and remove special treatment of return values

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Refactor ProgramMemory to isolate mutation

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Refactor ProgramMemory

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Generated output

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Cache the result of executing modules for their items

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Compress envs when popped

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Remove the last traces of geometry from the memory module

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* docs

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Fixups

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Frontend

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Improve Environment GC

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Fixup mock execution frontend and interpreter, misc bug fixes

Signed-off-by: Nick Cameron <nrc@ncameron.org>

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-02-11 21:22:56 +00:00
bc4d254297 Release the Share Link feature (#5228)
* WIP: Release the Share Link feature
Fixes #5227

* Clean up
2025-02-11 16:06:24 -05:00
10b7a772bf Release kcl-test-server 0.1.21 (#5345) 2025-02-11 14:05:31 -06:00
387 changed files with 40693 additions and 25172 deletions

View File

@ -4,6 +4,7 @@ VITE_KC_API_WS_MODELING_URL=wss://api.dev.zoo.dev/ws/modeling/commands
VITE_KC_API_BASE_URL=https://api.dev.zoo.dev
VITE_KC_SITE_BASE_URL=https://dev.zoo.dev
VITE_KC_SITE_APP_URL=https://app.dev.zoo.dev
VITE_KC_KCL_SAMPLES_REF=4fef4c34805322272ffb9486e524203ba0d78c1d
VITE_KC_SKIP_AUTH=false
VITE_KC_CONNECTION_TIMEOUT_MS=5000
# ONLY add your token in .env.development.local if you want to skip auth, otherwise this token takes precedence!

View File

@ -3,5 +3,6 @@ VITE_KC_API_WS_MODELING_URL=wss://api.zoo.dev/ws/modeling/commands
VITE_KC_API_BASE_URL=https://api.zoo.dev
VITE_KC_SITE_BASE_URL=https://zoo.dev
VITE_KC_SITE_APP_URL=https://app.zoo.dev
VITE_KC_KCL_SAMPLES_REF=4fef4c34805322272ffb9486e524203ba0d78c1d
VITE_KC_SKIP_AUTH=false
VITE_KC_CONNECTION_TIMEOUT_MS=15000

File diff suppressed because one or more lines are too long

View File

@ -33,7 +33,14 @@ helix(revolutions: number, angle_start: number, ccw?: bool, radius: number, axis
```js
// 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.
springSketch = startSketchOn('YZ')
@ -49,7 +56,14 @@ helper001 = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> 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.
springSketch = startSketchOn('XY')
@ -61,12 +75,19 @@ springSketch = startSketchOn('XY')
```js
// 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 = {
axis = [0, 0, 1.0],
origin = [0, 0.25, 0]
}
})
},
)
// Create a spring by sweeping around the helix path.
springSketch = startSketchOn('XY')

File diff suppressed because one or more lines are too long

View File

@ -17,7 +17,7 @@ lastSegX(sketch: Sketch) -> number
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | A sketch is a collection of paths. | Yes |
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | The sketch whose line segment is being queried | Yes |
### Returns

View File

@ -17,7 +17,7 @@ lastSegY(sketch: Sketch) -> number
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | A sketch is a collection of paths. | Yes |
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | The sketch whose line segment is being queried | Yes |
### Returns

File diff suppressed because one or more lines are too long

View File

@ -57,3 +57,55 @@ Imported symbols can be renamed for convenience or to avoid name collisions.
```
import increment as inc, decrement as dec from "util.kcl"
```
## Importing files from other CAD systems
`import` can also be used to import files from other CAD systems. The format of the statement is the
same as for KCL files. You can only import the whole file, not items from it. E.g.,
```
import "tests/inputs/cube.obj"
// Use `cube` just like a KCL object.
```
```
import "tests/inputs/cube-2.sldprt" as cube
// Use `cube` just like a KCL object.
```
You can make the file format explicit using a format attribute (useful if using a different
extension), e.g.,
```
@(format = obj)
import "tests/inputs/cube"
```
For formats lacking unit data (such as STL, OBJ, or PLY files), the default
unit of measurement is millimeters. Alternatively you may specify the unit
by using an attirbute. Likewise, you can also specify a coordinate system. E.g.,
```
@(unitLength = ft, coords = opengl)
import "tests/inputs/cube.obj"
```
When importing a GLTF file, the bin file will be imported as well.
Import paths are relative to the current project directory. Imports currently only work when
using the native Modeling App, not in the browser.
### Supported values
File formats: `fbx`, `gltf`/`glb`, `obj`+, `ply`+, `sldprt`, `step`/`stp`, `stl`+. (Those marked with a
'+' support customising the length unit and coordinate system).
Length units: `mm` (the default), `cm`, `m`, `inch`, `ft`, `yd`.
Coordinate systems:
- `zoo` (the default), forward: -Y, up: +Z, handedness: right
- `opengl`, forward: +Z, up: +Y, handedness: right
- `vulkan`, forward: +Z, up: -Y, handedness: left

View File

@ -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.
```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 |
|----------|------|-------------|----------|
| `data` | [`CircularPattern2dData`](/docs/kcl/types/CircularPattern2dData) | Data for a circular pattern on a 2D sketch. | Yes |
| `sketch_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | A sketch or a group of sketches. | Yes |
| `sketch_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | Which sketch(es) to pattern | 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
@ -34,12 +38,12 @@ exampleSketch = startSketchOn('XZ')
|> line(end = [-1, 0])
|> line(end = [0, -5])
|> close()
|> patternCircular2d({
|> patternCircular2d(
center = [0, 0],
instances = 13,
arcDegrees = 360,
rotateDuplicates = true
}, %)
rotateDuplicates = true,
)
example = extrude(exampleSketch, length = 1)
```

File diff suppressed because one or more lines are too long

View File

@ -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.
```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 |
|----------|------|-------------|----------|
| `data` | [`LinearPattern2dData`](/docs/kcl/types/LinearPattern2dData) | Data for a linear pattern on a 2D sketch. | Yes |
| `sketch_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | A sketch or a group of sketches. | Yes |
| `use_original` | `bool` | | No |
| `sketch_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | The sketch(es) to duplicate | 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 |
| `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
@ -31,11 +33,7 @@ patternLinear2d(data: LinearPattern2dData, sketch_set: SketchSet, use_original?:
```js
exampleSketch = startSketchOn('XZ')
|> circle({ center = [0, 0], radius = 1 }, %)
|> patternLinear2d({
axis = [1, 0],
instances = 7,
distance = 4
}, %)
|> patternLinear2d(axis = [1, 0], instances = 7, distance = 4)
example = extrude(exampleSketch, length = 1)
```

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -17,7 +17,7 @@ segAng(tag: TagIdentifier) -> number
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | | Yes |
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The line segment being queried by its tag | Yes |
### Returns

View File

@ -17,7 +17,7 @@ segEnd(tag: TagIdentifier) -> [number]
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | | Yes |
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The line segment being queried by its tag | Yes |
### Returns

View File

@ -17,7 +17,7 @@ segEndX(tag: TagIdentifier) -> number
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | | Yes |
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The line segment being queried by its tag | Yes |
### Returns

View File

@ -17,7 +17,7 @@ segEndY(tag: TagIdentifier) -> number
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | | Yes |
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The line segment being queried by its tag | Yes |
### Returns

View File

@ -17,7 +17,7 @@ segLen(tag: TagIdentifier) -> number
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | | Yes |
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The line segment being queried by its tag | Yes |
### Returns

View File

@ -17,7 +17,7 @@ segStart(tag: TagIdentifier) -> [number]
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | | Yes |
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The line segment being queried by its tag | Yes |
### Returns

View File

@ -17,7 +17,7 @@ segStartX(tag: TagIdentifier) -> number
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | | Yes |
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The line segment being queried by its tag | Yes |
### Returns

View File

@ -17,7 +17,7 @@ segStartY(tag: TagIdentifier) -> number
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | | Yes |
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The line segment being queried by its tag | Yes |
### Returns

File diff suppressed because it is too large Load Diff

View File

@ -59,7 +59,14 @@ sweepSketch = startSketchOn('XY')
// 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.
springSketch = startSketchOn('YZ')

View File

@ -17,7 +17,7 @@ tangentToEnd(tag: TagIdentifier) -> number
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | | Yes |
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The line segment being queried by its tag | Yes |
### Returns

View File

@ -0,0 +1,16 @@
---
title: "EnvironmentRef"
excerpt: ""
layout: manual
---
[`SnapshotRef`](/docs/kcl/types/SnapshotRef)

View File

@ -311,7 +311,7 @@ Data for an imported geometry.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `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 |
@ -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 |
----

View File

@ -17,6 +17,5 @@ layout: manual
|----------|------|-------------|----------|
| `environments` |`[` [`Environment`](/docs/kcl/types/Environment) `]`| | No |
| `currentEnv` |`integer`| | No |
| `return` |[`KclValue`](/docs/kcl/types/KclValue)| | No |

View 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`)

View File

@ -1117,13 +1117,14 @@ openSketch = startSketchOn('XY')
}) => {
// One dumb hardcoded screen pixel value
const testPoint = { x: 620, y: 257 }
const expectedOutput = `helix001 = helix(revolutions = 1, angleStart = 360, counterClockWise = false, radius = 5, axis = 'X', length = 5)`
const expectedOutput = `helix001 = helix( revolutions = 1, angleStart = 360, counterClockWise = false, radius = 5, axis = 'X', length = 5,)`
const expectedLine = `revolutions=1,`
await homePage.goToModelingScene()
await test.step(`Look for the red of the default plane`, async () => {
await scene.expectPixelColor([96, 52, 52], testPoint, 15)
})
// await test.step(`Look for the red of the default plane`, async () => {
// await scene.expectPixelColor([96, 52, 52], testPoint, 15)
// })
await test.step(`Go through the command bar flow`, async () => {
await toolbar.helixButton.click()
await cmdBar.expectState({
@ -1154,7 +1155,7 @@ openSketch = startSketchOn('XY')
await editor.expectEditor.toContain(expectedOutput)
await editor.expectState({
diagnostics: [],
activeLines: [expectedOutput],
activeLines: [expectedLine],
highlightedCode: '',
})
// Red plane is now gone, white helix is there

View File

@ -192,11 +192,11 @@ extrude001 = extrude(sketch001, length = 50)
|> line(end = [0, -1])
|> close()
|> extrude(length = 1)
|> patternLinear3d({
axis: [1, 0, 1],
repetitions: 3,
distance: 6
}, %)`
|> patternLinear3d(
axis = [1, 0, 1],
repetitions = 3,
distance = 6,
)`
)
})
await page.setBodyDimensions({ width: 1000, height: 500 })

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

1
interface.d.ts vendored
View File

@ -70,6 +70,7 @@ export interface IElectronAPI {
VITE_KC_SKIP_AUTH: string
VITE_KC_CONNECTION_TIMEOUT_MS: string
VITE_KC_DEV_TOKEN: string
VITE_KC_KCL_SAMPLES_REF: string
NODE_ENV: string
PROD: string
DEV: string

View File

@ -85,7 +85,7 @@
"fmt": "prettier --write ./src *.ts *.json *.js ./e2e ./packages",
"fmt-check": "prettier --check ./src *.ts *.json *.js ./e2e ./packages",
"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/4fef4c34805322272ffb9486e524203ba0d78c1d/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)",
"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",

View File

@ -34,6 +34,13 @@
"title": "Car Wheel Assembly",
"description": "A car wheel assembly with a rotor, tire, and lug nuts."
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "cycloidal-gear/main.kcl",
"multipleFiles": false,
"title": "Cycloidal Gear",
"description": "A cycloidal gear is a gear with a continuous, curved tooth profile. They are used in watchmaking and high precision robotics actuation"
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "dodecahedron/main.kcl",
@ -48,6 +55,13 @@
"title": "Enclosure",
"description": "An enclosure body and sealing lid for storing items"
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "exhaust-manifold/main.kcl",
"multipleFiles": false,
"title": "Exhaust Manifold",
"description": "A welded exhaust header for an inline 4-cylinder engine"
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "flange-with-patterns/main.kcl",

View File

@ -31,7 +31,6 @@ import {
recast,
defaultSourceRange,
resultIsOk,
ProgramMemory,
topLevelRange,
} from 'lang/wasm'
import { CustomIcon, CustomIconName } from 'components/CustomIcon'
@ -425,7 +424,7 @@ export async function deleteSegment({
modifiedAst = deleteSegmentFromPipeExpression(
dependentRanges,
modifiedAst,
kclManager.programMemory,
kclManager.variables,
codeManager.code,
pathToNode
)
@ -439,8 +438,8 @@ export async function deleteSegment({
const testExecute = await executeAst({
ast: modifiedAst,
engineCommandManager: engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride: ProgramMemory.empty(),
isMock: true,
usePrevMemory: false,
})
if (testExecute.errors.length) {
toast.error('Segment tag used outside of current Sketch. Could not delete.')
@ -675,7 +674,7 @@ const ConstraintSymbol = ({
shallowPath,
argPosition,
kclManager.ast,
kclManager.programMemory
kclManager.variables
)
if (!transform) return

View File

@ -47,7 +47,6 @@ import {
PathToNode,
PipeExpression,
Program,
ProgramMemory,
recast,
Sketch,
Solid,
@ -61,6 +60,7 @@ import {
SourceRange,
topLevelRange,
CallExpressionKw,
VariableMap,
} from 'lang/wasm'
import { calculate_circle_from_3_points } from '../wasm-lib/pkg/wasm_lib'
import {
@ -158,7 +158,6 @@ type Vec3Array = [number, number, number]
export class SceneEntities {
engineCommandManager: EngineCommandManager
scene: Scene
sceneProgramMemory: ProgramMemory = ProgramMemory.empty()
activeSegments: { [key: string]: Group } = {}
intersectionPlane: Mesh | null = null
axisGroup: Group | null = null
@ -502,29 +501,25 @@ export class SceneEntities {
selectionRanges?: Selections
}): Promise<{
truncatedAst: Node<Program>
programMemoryOverride: ProgramMemory
sketch: Sketch
variableDeclarationName: string
}> {
const prepared = this.prepareTruncatedMemoryAndAst(
const prepared = this.prepareTruncatedAst(
sketchPathToNode || [],
maybeModdedAst
)
if (err(prepared)) return Promise.reject(prepared)
const { truncatedAst, programMemoryOverride, variableDeclarationName } =
prepared
const { truncatedAst, variableDeclarationName } = prepared
const { execState } = await executeAst({
ast: truncatedAst,
engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride,
isMock: true,
})
const programMemory = execState.memory
const sketch = sketchFromPathToNode({
pathToNode: sketchPathToNode,
ast: maybeModdedAst,
programMemory,
variables: execState.variables,
})
if (err(sketch)) return Promise.reject(sketch)
if (!sketch) return Promise.reject('sketch not found')
@ -532,11 +527,9 @@ export class SceneEntities {
if (!isArray(sketch?.paths))
return {
truncatedAst,
programMemoryOverride,
sketch,
variableDeclarationName,
}
this.sceneProgramMemory = programMemory
const group = new Group()
position && group.position.set(...position)
group.userData = {
@ -684,7 +677,6 @@ export class SceneEntities {
return {
truncatedAst,
programMemoryOverride,
sketch,
variableDeclarationName,
}
@ -733,7 +725,7 @@ export class SceneEntities {
const variableDeclarationName = _node1.node?.declaration.id?.name || ''
const sg = sketchFromKclValue(
kclManager.programMemory.get(variableDeclarationName),
kclManager.variables[variableDeclarationName],
variableDeclarationName
)
if (err(sg)) return Promise.reject(sg)
@ -742,7 +734,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 mod = addNewSketchLn({
node: _ast,
programMemory: kclManager.programMemory,
variables: kclManager.variables,
input: {
type: 'straight-segment',
to: lastSeg.to,
@ -761,15 +753,14 @@ export class SceneEntities {
if (shouldTearDown) await this.tearDownSketch({ removeAxis: false })
sceneInfra.resetMouseListeners()
const { truncatedAst, programMemoryOverride, sketch } =
await this.setupSketch({
sketchPathToNode,
forward,
up,
position: origin,
maybeModdedAst: modifiedAst,
draftExpressionsIndices,
})
const { truncatedAst, sketch } = await this.setupSketch({
sketchPathToNode,
forward,
up,
position: origin,
maybeModdedAst: modifiedAst,
draftExpressionsIndices,
})
sceneInfra.setCallbacks({
onClick: async (args) => {
if (!args) return
@ -801,7 +792,7 @@ export class SceneEntities {
])
modifiedAst = addCallExpressionsToPipe({
node: kclManager.ast,
programMemory: kclManager.programMemory,
variables: kclManager.variables,
pathToNode: sketchPathToNode,
expressions: [
lastSegment.type === 'TangentialArcTo'
@ -817,7 +808,7 @@ export class SceneEntities {
if (trap(modifiedAst)) return Promise.reject(modifiedAst)
modifiedAst = addCloseToPipe({
node: modifiedAst,
programMemory: kclManager.programMemory,
variables: kclManager.variables,
pathToNode: sketchPathToNode,
})
if (trap(modifiedAst)) return Promise.reject(modifiedAst)
@ -867,7 +858,7 @@ export class SceneEntities {
const tmp = addNewSketchLn({
node: kclManager.ast,
programMemory: kclManager.programMemory,
variables: kclManager.variables,
input: {
type: 'straight-segment',
from: [lastSegment.to[0], lastSegment.to[1]],
@ -908,7 +899,6 @@ export class SceneEntities {
sketchPathToNode,
draftInfo: {
truncatedAst,
programMemoryOverride,
variableDeclarationName,
},
})
@ -949,7 +939,7 @@ export class SceneEntities {
if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
_ast = pResult.program
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
const { truncatedAst } = await this.setupSketch({
sketchPathToNode,
forward,
up,
@ -982,13 +972,10 @@ export class SceneEntities {
const { execState } = await executeAst({
ast: truncatedAst,
engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride,
isMock: true,
})
const programMemory = execState.memory
this.sceneProgramMemory = programMemory
const sketch = sketchFromKclValue(
programMemory.get(variableDeclarationName),
execState.variables[variableDeclarationName],
variableDeclarationName
)
if (err(sketch)) return Promise.reject(sketch)
@ -1045,15 +1032,12 @@ export class SceneEntities {
const { execState } = await executeAst({
ast: _ast,
engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride,
isMock: true,
})
const programMemory = execState.memory
// Prepare to update the THREEjs scene
this.sceneProgramMemory = programMemory
const sketch = sketchFromKclValue(
programMemory.get(variableDeclarationName),
execState.variables[variableDeclarationName],
variableDeclarationName
)
if (err(sketch)) return
@ -1104,7 +1088,7 @@ export class SceneEntities {
if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
_ast = pResult.program
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
const { truncatedAst } = await this.setupSketch({
sketchPathToNode,
forward,
up,
@ -1144,13 +1128,10 @@ export class SceneEntities {
const { execState } = await executeAst({
ast: truncatedAst,
engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride,
isMock: true,
})
const programMemory = execState.memory
this.sceneProgramMemory = programMemory
const sketch = sketchFromKclValue(
programMemory.get(variableDeclarationName),
execState.variables[variableDeclarationName],
variableDeclarationName
)
if (err(sketch)) return Promise.reject(sketch)
@ -1210,15 +1191,12 @@ export class SceneEntities {
const { execState } = await executeAst({
ast: _ast,
engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride,
isMock: true,
})
const programMemory = execState.memory
// Prepare to update the THREEjs scene
this.sceneProgramMemory = programMemory
const sketch = sketchFromKclValue(
programMemory.get(variableDeclarationName),
execState.variables[variableDeclarationName],
variableDeclarationName
)
if (err(sketch)) return
@ -1605,7 +1583,7 @@ export class SceneEntities {
// do a quick mock execution to get the program memory up-to-date
await kclManager.executeAstMock(_ast)
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
const { truncatedAst } = await this.setupSketch({
sketchPathToNode,
forward,
up,
@ -1634,7 +1612,7 @@ export class SceneEntities {
if (sketchInit.type === 'PipeExpression') {
const moddedResult = changeSketchArguments(
modded,
kclManager.programMemory,
kclManager.variables,
{
type: 'path',
pathToNode: [
@ -1657,13 +1635,10 @@ export class SceneEntities {
const { execState } = await executeAst({
ast: modded,
engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride,
isMock: true,
})
const programMemory = execState.memory
this.sceneProgramMemory = programMemory
const sketch = sketchFromKclValue(
programMemory.get(variableDeclarationName),
execState.variables[variableDeclarationName],
variableDeclarationName
)
if (err(sketch)) return
@ -1700,7 +1675,7 @@ export class SceneEntities {
if (sketchInit.type === 'PipeExpression') {
const moddedResult = changeSketchArguments(
modded,
kclManager.programMemory,
kclManager.variables,
{
type: 'path',
pathToNode: [
@ -1788,7 +1763,7 @@ export class SceneEntities {
const sketch = sketchFromPathToNode({
pathToNode,
ast: kclManager.ast,
programMemory: kclManager.programMemory,
variables: kclManager.variables,
})
if (trap(sketch)) return
if (!sketch) {
@ -1801,7 +1776,7 @@ export class SceneEntities {
const prevSegment = sketch.paths[pipeIndex - 2]
const mod = addNewSketchLn({
node: kclManager.ast,
programMemory: kclManager.programMemory,
variables: kclManager.variables,
input: {
type: 'straight-segment',
to: [intersectionPoint.twoD.x, intersectionPoint.twoD.y],
@ -1873,15 +1848,15 @@ export class SceneEntities {
...this.mouseEnterLeaveCallbacks(),
})
}
prepareTruncatedMemoryAndAst = (
prepareTruncatedAst = (
sketchPathToNode: PathToNode,
ast?: Node<Program>,
draftSegment?: DraftSegment
) =>
prepareTruncatedMemoryAndAst(
prepareTruncatedAst(
sketchPathToNode,
ast || kclManager.ast,
kclManager.lastSuccessfulProgramMemory,
kclManager.lastSuccessfulVariables,
draftSegment
)
onDragSegment({
@ -1897,7 +1872,6 @@ export class SceneEntities {
intersects: Intersection<Object3D<Object3DEventMap>>[]
draftInfo?: {
truncatedAst: Node<Program>
programMemoryOverride: ProgramMemory
variableDeclarationName: string
}
}) {
@ -2010,12 +1984,12 @@ export class SceneEntities {
to: dragTo,
from,
},
previousProgramMemory: kclManager.programMemory,
variables: kclManager.variables,
})
} else {
modded = changeSketchArguments(
modifiedAst,
kclManager.programMemory,
kclManager.variables,
{
type: 'sourceRange',
sourceRange: topLevelRange(node.start, node.end),
@ -2028,10 +2002,9 @@ export class SceneEntities {
modifiedAst = modded.modifiedAst
const info = draftInfo
? draftInfo
: this.prepareTruncatedMemoryAndAst(pathToNode || [])
: this.prepareTruncatedAst(pathToNode || [])
if (trap(info, { suppress: true })) return
const { truncatedAst, programMemoryOverride, variableDeclarationName } =
info
const { truncatedAst, variableDeclarationName } = info
;(async () => {
const code = recast(modifiedAst)
if (trap(code)) return
@ -2042,13 +2015,11 @@ export class SceneEntities {
const { execState } = await executeAst({
ast: truncatedAst,
engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride,
isMock: true,
})
const programMemory = execState.memory
this.sceneProgramMemory = programMemory
const variables = execState.variables
const maybeSketch = programMemory.get(variableDeclarationName)
const maybeSketch = variables[variableDeclarationName]
let sketch: Sketch | undefined
const sk = sketchFromKclValueOptional(
maybeSketch,
@ -2394,15 +2365,14 @@ export class SceneEntities {
// calculations/pure-functions/easy to test so no excuse not to
function prepareTruncatedMemoryAndAst(
function prepareTruncatedAst(
sketchPathToNode: PathToNode,
ast: Node<Program>,
programMemory: ProgramMemory,
variables: VariableMap,
draftSegment?: DraftSegment
):
| {
truncatedAst: Node<Program>
programMemoryOverride: ProgramMemory
variableDeclarationName: string
}
| Error {
@ -2417,7 +2387,7 @@ function prepareTruncatedMemoryAndAst(
if (err(_node)) return _node
const variableDeclarationName = _node.node?.declaration.id?.name || ''
const sg = sketchFromKclValue(
programMemory.get(variableDeclarationName),
variables[variableDeclarationName],
variableDeclarationName
)
if (err(sg)) return sg
@ -2476,43 +2446,8 @@ function prepareTruncatedMemoryAndAst(
body: [structuredClone(_ast.body[bodyIndex])],
}
// 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 < bodyIndex; 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 {
truncatedAst,
programMemoryOverride,
variableDeclarationName,
}
}
@ -2532,11 +2467,11 @@ export function getParentGroup(
export function sketchFromPathToNode({
pathToNode,
ast,
programMemory,
variables,
}: {
pathToNode: PathToNode
ast: Program
programMemory: ProgramMemory
variables: VariableMap
}): Sketch | null | Error {
const _varDec = getNodeFromPath<VariableDeclarator>(
kclManager.ast,
@ -2545,7 +2480,7 @@ export function sketchFromPathToNode({
)
if (err(_varDec)) return _varDec
const varDec = _varDec.node
const result = programMemory.get(varDec?.id?.name || '')
const result = variables[varDec?.id?.name || '']
if (result?.type === 'Solid') {
return result.value.sketch
}
@ -2584,7 +2519,7 @@ export function getSketchQuaternion(
const sketch = sketchFromPathToNode({
pathToNode: sketchPathToNode,
ast: kclManager.ast,
programMemory: kclManager.programMemory,
variables: kclManager.variables,
})
if (err(sketch)) return sketch
const zAxis = sketch?.on.zAxis || sketchNormalBackUp
@ -2601,7 +2536,7 @@ export async function getSketchOrientationDetails(
const sketch = sketchFromPathToNode({
pathToNode: sketchPathToNode,
ast: kclManager.ast,
programMemory: kclManager.programMemory,
variables: kclManager.variables,
})
if (err(sketch)) return Promise.reject(sketch)
if (!sketch) return Promise.reject('sketch not found')

View File

@ -1,11 +1,5 @@
import { useEffect, useState, useRef } from 'react'
import {
parse,
BinaryPart,
Expr,
ProgramMemory,
resultIsOk,
} from '../lang/wasm'
import { parse, BinaryPart, Expr, resultIsOk, VariableMap } from '../lang/wasm'
import {
createIdentifier,
createLiteral,
@ -100,7 +94,7 @@ export function useCalc({
newVariableInsertIndex: number
setNewVariableName: (a: string) => void
} {
const { programMemory } = useKclContext()
const { variables } = useKclContext()
const { context } = useModelingContext()
const selectionRange =
context.selectionRanges?.graphSelections[0]?.codeRef?.range
@ -127,7 +121,7 @@ export function useCalc({
}, [])
useEffect(() => {
if (programMemory.has(newVariableName)) {
if (variables[newVariableName]) {
setIsNewVariableNameUnique(false)
} else {
setIsNewVariableNameUnique(true)
@ -135,14 +129,14 @@ export function useCalc({
}, [newVariableName])
useEffect(() => {
if (!programMemory || !selectionRange) return
if (!variables || !selectionRange) return
const varInfo = findAllPreviousVariables(
kclManager.ast,
kclManager.programMemory,
kclManager.variables,
selectionRange
)
setAvailableVarInfo(varInfo)
}, [kclManager.ast, kclManager.programMemory, selectionRange])
}, [kclManager.ast, kclManager.variables, selectionRange])
useEffect(() => {
try {
@ -150,9 +144,9 @@ export function useCalc({
const pResult = parse(code)
if (trap(pResult) || !resultIsOk(pResult)) return
const ast = pResult.program
const _programMem: ProgramMemory = ProgramMemory.empty()
const _variables: VariableMap = {}
for (const { key, value } of availableVarInfo.variables) {
const error = _programMem.set(key, {
const error = (_variables[key] = {
type: 'String',
value,
__meta: [],
@ -163,8 +157,8 @@ export function useCalc({
executeAst({
ast,
engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride: kclManager.programMemory.clone(),
isMock: true,
variables,
}).then(({ execState }) => {
const resultDeclaration = ast.body.find(
(a) =>
@ -174,7 +168,7 @@ export function useCalc({
const init =
resultDeclaration?.type === 'VariableDeclaration' &&
resultDeclaration?.declaration.init
const result = execState.memory?.get('__result__')?.value
const result = execState.variables['__result__']?.value
setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
init && setValueNode(init)
})

View File

@ -157,8 +157,6 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
plugin.requestSemanticTokens()
break
case 'kcl/memoryUpdated':
break
}
} catch (error) {
console.error(error)

View File

@ -1,6 +1,6 @@
import { processMemory } from './MemoryPane'
import { enginelessExecutor } from '../../../lib/testHelpers'
import { assertParse, initPromise, ProgramMemory } from '../../../lang/wasm'
import { assertParse, initPromise } from '../../../lang/wasm'
beforeAll(async () => {
await initPromise
@ -29,15 +29,11 @@ describe('processMemory', () => {
|> line(endAbsolute = [2.15, 4.32])
// |> rx(90, %)`
const ast = assertParse(code)
const execState = await enginelessExecutor(ast, ProgramMemory.empty())
const output = processMemory(execState.memory)
const execState = await enginelessExecutor(ast)
const output = processMemory(execState.variables)
expect(output.myVar).toEqual(5)
expect(output.otherVar).toEqual(3)
expect(output).toEqual({
HALF_TURN: 180,
QUARTER_TURN: 90,
THREE_QUARTER_TURN: 270,
ZERO: 0,
myVar: 5,
myFn: '__function(a)__',
otherVar: 3,

View File

@ -2,10 +2,10 @@ import toast from 'react-hot-toast'
import ReactJson from 'react-json-view'
import { useMemo } from 'react'
import {
ProgramMemory,
Path,
ExtrudeSurface,
sketchFromKclValueOptional,
VariableMap,
} from 'lang/wasm'
import { useKclContext } from 'lang/KclProvider'
import { useResolvedTheme } from 'hooks/useResolvedTheme'
@ -15,12 +15,12 @@ import Tooltip from 'components/Tooltip'
import { useModelingContext } from 'hooks/useModelingContext'
export const MemoryPaneMenu = () => {
const { programMemory } = useKclContext()
const { variables } = useKclContext()
function copyProgramMemoryToClipboard() {
if (globalThis && 'navigator' in globalThis) {
navigator.clipboard
.writeText(JSON.stringify(programMemory))
.writeText(JSON.stringify(variables))
.then(() => toast.success('Program memory copied to clipboard'))
.catch((e) =>
trap(new Error('Failed to copy program memory to clipboard'))
@ -50,12 +50,9 @@ export const MemoryPaneMenu = () => {
export const MemoryPane = () => {
const theme = useResolvedTheme()
const { programMemory } = useKclContext()
const { variables } = useKclContext()
const { state } = useModelingContext()
const ProcessedMemory = useMemo(
() => processMemory(programMemory),
[programMemory]
)
const ProcessedMemory = useMemo(() => processMemory(variables), [variables])
return (
<div className="h-full relative">
<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 = {}
for (const [key, val] of programMemory?.visibleEntries()) {
for (const [key, val] of Object.entries(variables)) {
if (val === undefined) continue
if (
val.type === 'Sketch' ||
// @ts-ignore

View File

@ -19,7 +19,6 @@ import { commandBarActor } from 'machines/commandBarMachine'
import { useSelector } from '@xstate/react'
import { copyFileShareLink } from 'lib/links'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { IS_NIGHTLY_OR_DEBUG } from 'routes/Settings'
import { useToken } from 'machines/appMachine'
const ProjectSidebarMenu = ({
@ -194,7 +193,7 @@ function ProjectMenuPopover({
id: 'share-link',
Element: 'button',
children: 'Share current part (via Zoo link)',
disabled: !(IS_NIGHTLY_OR_DEBUG && findCommand(shareCommandInfo)),
disabled: !findCommand(shareCommandInfo),
onClick: async () => {
await copyFileShareLink({
token: token ?? '',

View File

@ -95,7 +95,7 @@ export function applyConstraintEqualAngle({
ast: kclManager.ast,
selectionRanges,
transformInfos: transforms,
programMemory: kclManager.programMemory,
memVars: kclManager.variables,
})
if (err(transform)) return transform
const { modifiedAst, pathToNodeMap } = transform

View File

@ -93,7 +93,7 @@ export function applyConstraintEqualLength({
ast: kclManager.ast,
selectionRanges,
transformInfos: transforms,
programMemory: kclManager.programMemory,
memVars: kclManager.variables,
})
if (err(transform)) return transform
const { modifiedAst, pathToNodeMap } = transform

View File

@ -1,6 +1,6 @@
import { toolTips } from 'lang/langHelpers'
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 {
PathToNodeMap,
@ -51,7 +51,7 @@ export function applyConstraintHorzVert(
selectionRanges: Selections,
horOrVert: 'vertical' | 'horizontal',
ast: Node<Program>,
programMemory: ProgramMemory
memVars: VariableMap
):
| {
modifiedAst: Node<Program>
@ -66,7 +66,7 @@ export function applyConstraintHorzVert(
ast,
selectionRanges,
transformInfos,
programMemory,
memVars,
referenceSegName: '',
})
}

View File

@ -46,7 +46,7 @@ export function intersectInfo({
isLinesParallelAndConstrained(
kclManager.ast,
engineCommandManager.artifactGraph,
kclManager.programMemory,
kclManager.variables,
selectionRanges.graphSelections[0],
selectionRanges.graphSelections[1]
)
@ -147,7 +147,7 @@ export async function applyConstraintIntersect({
ast: structuredClone(kclManager.ast),
selectionRanges: forcedSelectionRanges,
transformInfos: transforms,
programMemory: kclManager.programMemory,
memVars: kclManager.variables,
})
if (err(transform1)) return Promise.reject(transform1)
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
@ -184,7 +184,7 @@ export async function applyConstraintIntersect({
ast: kclManager.ast,
selectionRanges: forcedSelectionRanges,
transformInfos: transforms,
programMemory: kclManager.programMemory,
memVars: kclManager.variables,
forceSegName: segName,
forceValueUsedInTransform: finalValue,
})

View File

@ -88,7 +88,7 @@ export function applyRemoveConstrainingValues({
ast: kclManager.ast,
selectionRanges: updatedSelectionRanges,
transformInfos: transforms,
programMemory: kclManager.programMemory,
memVars: kclManager.variables,
referenceSegName: '',
})
}

View File

@ -104,7 +104,7 @@ export async function applyConstraintAbsDistance({
ast: structuredClone(kclManager.ast),
selectionRanges,
transformInfos,
programMemory: kclManager.programMemory,
memVars: kclManager.variables,
referenceSegName: '',
})
if (err(transform1)) return Promise.reject(transform1)
@ -124,7 +124,7 @@ export async function applyConstraintAbsDistance({
ast: structuredClone(kclManager.ast),
selectionRanges,
transformInfos,
programMemory: kclManager.programMemory,
memVars: kclManager.variables,
referenceSegName: '',
forceValueUsedInTransform: finalValue,
})
@ -172,7 +172,7 @@ export function applyConstraintAxisAlign({
ast: structuredClone(kclManager.ast),
selectionRanges,
transformInfos,
programMemory: kclManager.programMemory,
memVars: kclManager.variables,
referenceSegName: '',
forceValueUsedInTransform: finalValue,
})

View File

@ -95,7 +95,7 @@ export async function applyConstraintAngleBetween({
ast: structuredClone(kclManager.ast),
selectionRanges,
transformInfos,
programMemory: kclManager.programMemory,
memVars: kclManager.variables,
})
if (err(transformed1)) return Promise.reject(transformed1)
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
@ -133,7 +133,7 @@ export async function applyConstraintAngleBetween({
ast: kclManager.ast,
selectionRanges,
transformInfos,
programMemory: kclManager.programMemory,
memVars: kclManager.variables,
forceSegName: segName,
forceValueUsedInTransform: finalValue,
})

View File

@ -107,7 +107,7 @@ export async function applyConstraintHorzVertDistance({
ast: structuredClone(kclManager.ast),
selectionRanges,
transformInfos,
programMemory: kclManager.programMemory,
memVars: kclManager.variables,
})
if (err(transformed)) return Promise.reject(transformed)
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
@ -145,7 +145,7 @@ export async function applyConstraintHorzVertDistance({
ast: kclManager.ast,
selectionRanges,
transformInfos,
programMemory: kclManager.programMemory,
memVars: kclManager.variables,
forceSegName: segName,
forceValueUsedInTransform: finalValue,
})
@ -195,7 +195,7 @@ export function applyConstraintHorzVertAlign({
ast: kclManager.ast,
selectionRanges,
transformInfos,
programMemory: kclManager.programMemory,
memVars: kclManager.variables,
forceValueUsedInTransform: finalValue,
})
if (err(retval)) return retval

View File

@ -105,7 +105,7 @@ export async function applyConstraintLength({
ast,
selectionRanges,
transformInfos: transforms,
programMemory: kclManager.programMemory,
memVars: kclManager.variables,
referenceSegName: '',
forceValueUsedInTransform: distanceExpression,
})
@ -137,7 +137,7 @@ export async function applyConstraintAngleLength({
ast: structuredClone(kclManager.ast),
selectionRanges,
transformInfos: transforms,
programMemory: kclManager.programMemory,
memVars: kclManager.variables,
referenceSegName: '',
})
if (err(sketched)) return Promise.reject(sketched)
@ -189,7 +189,7 @@ export async function applyConstraintAngleLength({
ast: structuredClone(kclManager.ast),
selectionRanges,
transformInfos: transforms,
programMemory: kclManager.programMemory,
memVars: kclManager.variables,
referenceSegName: '',
forceValueUsedInTransform: finalValue,
})

View File

@ -11,6 +11,7 @@ export const VITE_KC_API_WS_MODELING_URL = env.VITE_KC_API_WS_MODELING_URL as
export const VITE_KC_API_BASE_URL = env.VITE_KC_API_BASE_URL as string
export const VITE_KC_SITE_BASE_URL = env.VITE_KC_SITE_BASE_URL as string
export const VITE_KC_SITE_APP_URL = env.VITE_KC_SITE_APP_URL as string
export const VITE_KC_KCL_SAMPLES_REF = env.VITE_KC_KCL_SAMPLES_REF as string
export const VITE_KC_SKIP_AUTH = env.VITE_KC_SKIP_AUTH as string | undefined
export const VITE_KC_CONNECTION_TIMEOUT_MS =
env.VITE_KC_CONNECTION_TIMEOUT_MS as string | undefined

View File

@ -55,7 +55,7 @@ export function useConvertToVariable(range?: SourceRange) {
const { modifiedAst: _modifiedAst, pathToReplacedNode } =
moveValueIntoNewVariable(
ast,
kclManager.programMemory,
kclManager.variables,
range || context.selectionRanges.graphSelections[0]?.codeRef?.range,
variableName
)

View File

@ -7,7 +7,7 @@ import { KCLError } from './errors'
const KclContext = createContext({
code: codeManager?.code || '',
programMemory: kclManager?.programMemory,
variables: kclManager?.variables,
ast: kclManager?.ast,
isExecuting: kclManager?.isExecuting,
diagnostics: kclManager?.diagnostics,
@ -31,7 +31,7 @@ export function KclContextProvider({
// Both the code state and the editor state start off with the same 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 [isExecuting, setIsExecuting] = useState(false)
const [diagnostics, setDiagnostics] = useState<Diagnostic[]>([])
@ -44,7 +44,7 @@ export function KclContextProvider({
setCode,
})
kclManager.registerCallBacks({
setProgramMemory,
setVariables,
setAst,
setLogs,
setErrors,
@ -58,7 +58,7 @@ export function KclContextProvider({
<KclContext.Provider
value={{
code,
programMemory,
variables,
ast,
isExecuting,
diagnostics,

View File

@ -17,13 +17,14 @@ import {
emptyExecState,
ExecState,
initPromise,
KclValue,
parse,
PathToNode,
Program,
ProgramMemory,
recast,
SourceRange,
topLevelRange,
VariableMap,
} from 'lang/wasm'
import { getNodeFromPath, getSettingsAnnotation } from './queryAst'
import { codeManager, editorManager, sceneInfra } from 'lib/singletons'
@ -61,8 +62,8 @@ export class KclManager {
trivia: [],
}
private _execState: ExecState = emptyExecState()
private _programMemory: ProgramMemory = ProgramMemory.empty()
lastSuccessfulProgramMemory: ProgramMemory = ProgramMemory.empty()
private _variables: VariableMap = {}
lastSuccessfulVariables: VariableMap = {}
lastSuccessfulOperations: Operation[] = []
private _logs: string[] = []
private _errors: KCLError[] = []
@ -78,7 +79,9 @@ export class KclManager {
private _isExecutingCallback: (arg: boolean) => 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 _kclErrorsCallBack: (errors: KCLError[]) => void = () => {}
private _diagnosticsCallback: (errors: Diagnostic[]) => void = () => {}
@ -97,18 +100,18 @@ export class KclManager {
this._switchedFiles = switchedFiles
}
get programMemory() {
return this._programMemory
get variables() {
return this._variables
}
// This is private because callers should be setting the entire execState.
private set programMemory(programMemory) {
this._programMemory = programMemory
this._programMemoryCallBack(programMemory)
private set variables(variables) {
this._variables = variables
this._variablesCallBack(variables)
}
private set execState(execState) {
this._execState = execState
this.programMemory = execState.memory
this.variables = execState.variables
}
get execState() {
@ -201,7 +204,7 @@ export class KclManager {
}
registerCallBacks({
setProgramMemory,
setVariables,
setAst,
setLogs,
setErrors,
@ -209,7 +212,7 @@ export class KclManager {
setIsExecuting,
setWasmInitFailed,
}: {
setProgramMemory: (arg: ProgramMemory) => void
setVariables: (arg: VariableMap) => void
setAst: (arg: Node<Program>) => void
setLogs: (arg: string[]) => void
setErrors: (errors: KCLError[]) => void
@ -217,7 +220,7 @@ export class KclManager {
setIsExecuting: (arg: boolean) => void
setWasmInitFailed: (arg: boolean) => void
}) {
this._programMemoryCallBack = setProgramMemory
this._variablesCallBack = setVariables
this._astCallBack = setAst
this._logsCallBack = setLogs
this._kclErrorsCallBack = setErrors
@ -329,6 +332,7 @@ export class KclManager {
ast,
path: codeManager.currentFilePath || undefined,
engineCommandManager: this.engineCommandManager,
isMock: false,
})
// Program was not interrupted, setup the scene
@ -385,11 +389,11 @@ export class KclManager {
this.addDiagnostics(isInterrupted ? [] : kclErrorsToDiagnostics(errors))
this.execState = execState
if (!errors.length) {
this.lastSuccessfulProgramMemory = execState.memory
this.lastSuccessfulVariables = execState.variables
this.lastSuccessfulOperations = execState.operations
}
this.ast = { ...ast }
// updateArtifactGraph relies on updated executeState/programMemory
// updateArtifactGraph relies on updated executeState/variables
this.engineCommandManager.updateArtifactGraph(execState.artifactGraph)
this._executeCallback()
if (!isInterrupted) {
@ -442,16 +446,15 @@ export class KclManager {
const { logs, errors, execState } = await executeAst({
ast: newAst,
engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride: ProgramMemory.empty(),
isMock: true,
})
this._logs = logs
this.addDiagnostics(kclErrorsToDiagnostics(errors))
this._execState = execState
this._programMemory = execState.memory
this._variables = execState.variables
if (!errors.length) {
this.lastSuccessfulProgramMemory = execState.memory
this.lastSuccessfulVariables = execState.variables
this.lastSuccessfulOperations = execState.operations
}
}

View File

@ -1,59 +0,0 @@
import { assertParse, initPromise, parse } from './wasm'
import { err } from 'lib/trap'
beforeAll(async () => {
await initPromise
})
describe('testing AST', () => {
test('5 + 6', () => {
const ast = assertParse('5 +6')
delete (ast as any).nonCodeMeta
expect(ast.body).toEqual([
{
type: 'ExpressionStatement',
start: 0,
end: 4,
expression: {
type: 'BinaryExpression',
start: 0,
end: 4,
left: {
type: 'Literal',
start: 0,
end: 1,
value: {
suffix: 'None',
value: 5,
},
raw: '5',
},
operator: '+',
right: {
type: 'Literal',
start: 3,
end: 4,
value: {
suffix: 'None',
value: 6,
},
raw: '6',
},
},
},
])
})
})
describe('parsing errors', () => {
it('should return an error when there is a unexpected closed curly brace', async () => {
const code = `const myVar = startSketchAt([}], %)`
const result = parse(code)
if (err(result)) throw result
const error = result.errors[0]
expect(error.message).toBe('Array is missing a closing bracket(`]`)')
expect(error.sourceRange).toEqual([28, 29, 0])
})
})

View File

@ -15,8 +15,7 @@ const mySketch001 = startSketchOn('XY')
|> line(endAbsolute = [0.46, -5.82])
// |> rx(45, %)`
const execState = await enginelessExecutor(assertParse(code))
// @ts-ignore
const sketch001 = execState.memory.get('mySketch001')
const sketch001 = execState.variables['mySketch001']
expect(sketch001).toEqual({
type: 'Sketch',
value: {
@ -73,8 +72,7 @@ const mySketch001 = startSketchOn('XY')
// |> rx(45, %)
|> extrude(length = 2)`
const execState = await enginelessExecutor(assertParse(code))
// @ts-ignore
const sketch001 = execState.memory.get('mySketch001')
const sketch001 = execState.variables['mySketch001']
expect(sketch001).toEqual({
type: 'Solid',
value: {
@ -165,9 +163,9 @@ const sk2 = startSketchOn('XY')
`
const execState = await enginelessExecutor(assertParse(code))
const programMemory = execState.memory
const variables = execState.variables
// @ts-ignore
const geos = [programMemory.get('theExtrude'), programMemory.get('sk2')]
const geos = [variables['theExtrude'], variables['sk2']]
expect(geos).toEqual([
{
type: 'Solid',

View File

@ -2,12 +2,12 @@ import fs from 'node:fs'
import {
assertParse,
ProgramMemory,
Sketch,
initPromise,
sketchFromKclValue,
defaultArtifactGraph,
topLevelRange,
VariableMap,
} from './wasm'
import { enginelessExecutor } from '../lib/testHelpers'
import { KCLError } from './errors'
@ -21,13 +21,13 @@ describe('test executor', () => {
const code = `const myVar = 5
const newVar = myVar + 1`
const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(5)
expect(mem.get('newVar')?.value).toBe(6)
expect(mem['myVar']?.value).toBe(5)
expect(mem['newVar']?.value).toBe(6)
})
it('test assigning a var with a string', async () => {
const code = `const myVar = "a str"`
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 () => {
const code = fs.readFileSync(
@ -35,7 +35,7 @@ const newVar = myVar + 1`
'utf-8'
)
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 () => {
const mem = await exe(
@ -47,8 +47,8 @@ const newVar = myVar + 1`
'const magicNum = funcN(9, theVar)',
].join('\n')
)
expect(mem.get('theVar')?.value).toBe(60)
expect(mem.get('magicNum')?.value).toBe(69)
expect(mem['theVar']?.value).toBe(60)
expect(mem['magicNum']?.value).toBe(69)
})
it('sketch declaration', async () => {
let code = `const mySketch = startSketchOn('XY')
@ -60,7 +60,7 @@ const newVar = myVar + 1`
`
const mem = await exe(code)
// 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')
if (sk?.type !== 'Sketch') {
return
@ -117,7 +117,7 @@ const newVar = myVar + 1`
'const myVar = 5 + 1 |> myFn(%)',
].join('\n')
const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(7)
expect(mem['myVar']?.value).toBe(7)
})
// Enable rotations #152
@ -130,15 +130,15 @@ const newVar = myVar + 1`
// 'const rotated = rx(90, mySk1)',
// ].join('\n')
// const mem = await exe(code)
// expect(mem.get('mySk1')?.value).toHaveLength(3)
// expect(mem.get('rotated')?.type).toBe('Sketch')
// expect(mem['mySk1']?.value).toHaveLength(3)
// expect(mem['rotated')?.type).toBe('Sketch']
// if (
// mem.get('mySk1')?.type !== 'Sketch' ||
// mem.get('rotated')?.type !== 'Sketch'
// mem['mySk1']?.type !== 'Sketch' ||
// mem['rotated']?.type !== 'Sketch'
// )
// throw new Error('not a sketch')
// expect(mem.get('mySk1')?.rotation).toEqual([0, 0, 0, 1])
// expect(mem.get('rotated')?.rotation.map((a) => a.toFixed(4))).toEqual([
// expect(mem['mySk1']?.rotation).toEqual([0, 0, 0, 1])
// expect(mem['rotated']?.rotation.map((a) => a.toFixed(4))).toEqual([
// '0.7071',
// '0.0000',
// '0.0000',
@ -157,7 +157,7 @@ const newVar = myVar + 1`
// ' |> rx(90, %)',
].join('\n')
const mem = await exe(code)
expect(mem.get('mySk1')).toEqual({
expect(mem['mySk1']).toEqual({
type: 'Sketch',
value: {
type: 'Sketch',
@ -236,7 +236,7 @@ const newVar = myVar + 1`
)
const mem = await exe(code)
// TODO path to node is probably wrong here, zero indexes are not correct
expect(mem.get('three')).toEqual({
expect(mem['three']).toEqual({
type: 'Number',
value: 3,
__meta: [
@ -245,7 +245,7 @@ const newVar = myVar + 1`
},
],
})
expect(mem.get('yo')).toEqual({
expect(mem['yo']).toEqual({
type: 'Array',
value: [
{ 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 () => {
const code = [
@ -273,7 +270,7 @@ const newVar = myVar + 1`
"const yo = {aStr: 'str', anum: 2, identifier: three, binExp: 4 + 5}",
].join('\n')
const mem = await exe(code)
expect(mem.get('yo')).toEqual({
expect(mem['yo']).toEqual({
type: 'Object',
value: {
aStr: {
@ -309,7 +306,7 @@ const newVar = myVar + 1`
'\n'
)
const mem = await exe(code)
expect(mem.get('myVar')).toEqual({
expect(mem['myVar']).toEqual({
type: 'String',
value: '123',
__meta: [
@ -325,80 +322,80 @@ describe('testing math operators', () => {
it('can sum', async () => {
const code = ['const myVar = 1 + 2'].join('\n')
const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(3)
expect(mem['myVar']?.value).toBe(3)
})
it('can subtract', async () => {
const code = ['const myVar = 1 - 2'].join('\n')
const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(-1)
expect(mem['myVar']?.value).toBe(-1)
})
it('can multiply', async () => {
const code = ['const myVar = 1 * 2'].join('\n')
const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(2)
expect(mem['myVar']?.value).toBe(2)
})
it('can divide', async () => {
const code = ['const myVar = 1 / 2'].join('\n')
const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(0.5)
expect(mem['myVar']?.value).toBe(0.5)
})
it('can modulus', async () => {
const code = ['const myVar = 5 % 2'].join('\n')
const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(1)
expect(mem['myVar']?.value).toBe(1)
})
it('can do multiple operations', async () => {
const code = ['const myVar = 1 + 2 * 3'].join('\n')
const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(7)
expect(mem['myVar']?.value).toBe(7)
})
it('big example with parans', async () => {
const code = ['const myVar = 1 + 2 * (3 - 4) / -5 + 6'].join('\n')
const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(7.4)
expect(mem['myVar']?.value).toBe(7.4)
})
it('with identifier', async () => {
const code = ['const yo = 6', 'const myVar = yo / 2'].join('\n')
const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(3)
expect(mem['myVar']?.value).toBe(3)
})
it('with lots of testing', async () => {
const code = ['const myVar = 2 * ((2 + 3 ) / 4 + 5)'].join('\n')
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 () => {
const code = 'const myVar = min(4, 100) + 2'
const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(6)
expect(mem['myVar']?.value).toBe(6)
})
it('with callExpression at end', async () => {
const code = 'const myVar = 2 + min(4, 100)'
const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(6)
expect(mem['myVar']?.value).toBe(6)
})
it('with nested callExpression', async () => {
const code = 'const myVar = 2 + min(100, legLen(5, 3))'
const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(6)
expect(mem['myVar']?.value).toBe(6)
})
it('with unaryExpression', async () => {
const code = 'const myVar = -min(100, 3)'
const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(-3)
expect(mem['myVar']?.value).toBe(-3)
})
it('with unaryExpression in callExpression', async () => {
const code = 'const myVar = min(-legLen(5, 4), 5)'
const code2 = 'const myVar = min(5 , -legLen(5, 4))'
const mem = await exe(code)
const mem2 = await exe(code2)
expect(mem.get('myVar')?.value).toBe(-3)
expect(mem.get('myVar')?.value).toBe(mem2.get('myVar')?.value)
expect(mem['myVar']?.value).toBe(-3)
expect(mem['myVar']?.value).toBe(mem2['myVar']?.value)
})
it('with unaryExpression in ArrayExpression', async () => {
const code = 'const myVar = [1,-legLen(5, 4)]'
const mem = await exe(code)
expect(mem.get('myVar')?.value).toEqual([
expect(mem['myVar']?.value).toEqual([
{
__meta: [
{
@ -426,7 +423,7 @@ describe('testing math operators', () => {
'|> line(end = [-2.21, -legLen(5, min(3, 999))])',
].join('\n')
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
const yVal = (sketch as Sketch).paths?.[0]?.to?.[1]
expect(yVal).toBe(-4)
@ -444,7 +441,7 @@ describe('testing math operators', () => {
``,
].join('\n')
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((sketch as Sketch).paths?.[1]?.from).toEqual([3, 4])
expect((sketch as Sketch).paths?.[1]?.to).toEqual([6, 0])
@ -454,7 +451,7 @@ describe('testing math operators', () => {
)
const removedUnaryExpMem = await exe(removedUnaryExp)
const removedUnaryExpMemSketch = sketchFromKclValue(
removedUnaryExpMem.get('part001'),
removedUnaryExpMem['part001'],
'part001'
)
@ -464,12 +461,12 @@ describe('testing math operators', () => {
it('with nested callExpression and binaryExpression', async () => {
const code = 'const myVar = 2 + min(100, -1 + legLen(5, 3))'
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 () => {
const code = 'const myNeg2 = 4 ^ 2 - 3 ^ 2 * 2'
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
async function exe(
code: string,
programMemory: ProgramMemory = ProgramMemory.empty()
) {
async function exe(code: string, variables: VariableMap = {}) {
const ast = assertParse(code)
const execState = await enginelessExecutor(ast, programMemory)
return execState.memory
const execState = await enginelessExecutor(ast, true, undefined, variables)
return execState.variables
}

View File

@ -1,9 +1,10 @@
import { assertParse, initPromise, programMemoryInit } from './wasm'
import { assertParse, initPromise } from './wasm'
import { enginelessExecutor } from '../lib/testHelpers'
import path from 'node:path'
import fs from 'node:fs/promises'
import child_process from 'node:child_process'
import { VITE_KC_KCL_SAMPLES_REF } from 'env'
// The purpose of these tests is to act as a first line of defense
// if something gets real screwy with our KCL ecosystem.
@ -28,14 +29,11 @@ try {
console.log(e)
}
child_process.spawnSync('git', [
'clone',
'--single-branch',
'--branch',
'achalmers/kw-appearance',
URL_GIT_KCL_SAMPLES,
DIR_KCL_SAMPLES,
])
child_process.spawnSync('git', ['clone', URL_GIT_KCL_SAMPLES, DIR_KCL_SAMPLES])
child_process.spawnSync('git', ['checkout', VITE_KC_KCL_SAMPLES_REF], {
cwd: DIR_KCL_SAMPLES,
})
// @ts-expect-error
let files = await fs.readdir(DIR_KCL_SAMPLES)
@ -72,7 +70,7 @@ describe('Test KCL Samples from public Github repository', () => {
const ast = assertParse(code)
await enginelessExecutor(
ast,
programMemoryInit(),
false,
file.pathFromProjectDirectoryToFirstFile
)
},

View File

@ -1,12 +1,12 @@
import {
Program,
executor,
ProgramMemory,
executeWithEngine,
executeMock,
kclLint,
emptyExecState,
ExecState,
VariableMap,
} from 'lang/wasm'
import { enginelessExecutor } from 'lib/testHelpers'
import { EngineCommandManager } from 'lang/std/engineConnection'
import { KCLError } from 'lang/errors'
import { Diagnostic } from '@codemirror/lint'
@ -48,14 +48,16 @@ export async function executeAst({
ast,
path,
engineCommandManager,
// If you set programMemoryOverride we assume you mean mock mode. Since that
// is the only way to go about it.
programMemoryOverride,
isMock,
usePrevMemory,
variables,
}: {
ast: Node<Program>
path?: string
engineCommandManager: EngineCommandManager
programMemoryOverride?: ProgramMemory
isMock: boolean
usePrevMemory?: boolean
variables?: VariableMap
isInterrupted?: boolean
}): Promise<{
logs: string[]
@ -64,9 +66,9 @@ export async function executeAst({
isInterrupted: boolean
}> {
try {
const execState = await (programMemoryOverride
? enginelessExecutor(ast, programMemoryOverride, path)
: executor(ast, engineCommandManager, path))
const execState = await (isMock
? executeMock(ast, usePrevMemory, path, variables)
: executeWithEngine(ast, engineCommandManager, path))
await engineCommandManager.waitForAllCommands()

View File

@ -315,7 +315,7 @@ yo2 = hmm([identifierGuy + 5])`
const startIndex = code.indexOf('100 + 100') + 1
const { modifiedAst } = moveValueIntoNewVariable(
ast,
execState.memory,
execState.variables,
topLevelRange(startIndex, startIndex),
'newVar'
)
@ -329,7 +329,7 @@ yo2 = hmm([identifierGuy + 5])`
const startIndex = code.indexOf('2.8') + 1
const { modifiedAst } = moveValueIntoNewVariable(
ast,
execState.memory,
execState.variables,
topLevelRange(startIndex, startIndex),
'newVar'
)
@ -343,7 +343,7 @@ yo2 = hmm([identifierGuy + 5])`
const startIndex = code.indexOf('def(')
const { modifiedAst } = moveValueIntoNewVariable(
ast,
execState.memory,
execState.variables,
topLevelRange(startIndex, startIndex),
'newVar'
)
@ -357,7 +357,7 @@ yo2 = hmm([identifierGuy + 5])`
const startIndex = code.indexOf('jkl(') + 1
const { modifiedAst } = moveValueIntoNewVariable(
ast,
execState.memory,
execState.variables,
topLevelRange(startIndex, startIndex),
'newVar'
)
@ -371,7 +371,7 @@ yo2 = hmm([identifierGuy + 5])`
const startIndex = code.indexOf('identifierGuy +') + 1
const { modifiedAst } = moveValueIntoNewVariable(
ast,
execState.memory,
execState.variables,
topLevelRange(startIndex, startIndex),
'newVar'
)
@ -557,7 +557,7 @@ describe('Testing deleteSegmentFromPipeExpression', () => {
const modifiedAst = deleteSegmentFromPipeExpression(
[],
ast,
execState.memory,
execState.variables,
code,
pathToNode
)
@ -639,7 +639,7 @@ ${!replace1 ? ` |> ${line}\n` : ''} |> angledLine([-65, ${
const modifiedAst = deleteSegmentFromPipeExpression(
dependentSegments,
ast,
execState.memory,
execState.variables,
code,
pathToNode
)
@ -745,7 +745,7 @@ describe('Testing removeSingleConstraintInfo', () => {
pathToNode,
argPosition,
ast,
execState.memory
execState.variables
)
if (!mod) return new Error('mod is undefined')
const recastCode = recast(mod.modifiedAst)
@ -794,7 +794,7 @@ describe('Testing removeSingleConstraintInfo', () => {
pathToNode,
argPosition,
ast,
execState.memory
execState.variables
)
if (!mod) return new Error('mod is undefined')
const recastCode = recast(mod.modifiedAst)
@ -978,7 +978,7 @@ sketch002 = startSketchOn({
codeRef: codeRefFromRange(range, ast),
artifact,
},
execState.memory,
execState.variables,
async () => {
await new Promise((resolve) => setTimeout(resolve, 100))
return {

View File

@ -18,11 +18,11 @@ import {
UnaryExpression,
BinaryExpression,
PathToNode,
ProgramMemory,
SourceRange,
sketchFromKclValue,
isPathToNodeNumber,
formatNumber,
VariableMap,
} from './wasm'
import {
isNodeSafeToReplacePath,
@ -1218,7 +1218,7 @@ export function replaceValueAtNodePath({
export function moveValueIntoNewVariablePath(
ast: Node<Program>,
programMemory: ProgramMemory,
memVars: VariableMap,
pathToNode: PathToNode,
variableName: string
): {
@ -1231,11 +1231,7 @@ export function moveValueIntoNewVariablePath(
if (!isSafe || value.type === 'Identifier') return { modifiedAst: ast }
const { insertIndex } = findAllPreviousVariablesPath(
ast,
programMemory,
pathToNode
)
const { insertIndex } = findAllPreviousVariablesPath(ast, memVars, pathToNode)
let _node = structuredClone(ast)
const boop = replacer(_node, variableName)
if (trap(boop)) return { modifiedAst: ast }
@ -1251,7 +1247,7 @@ export function moveValueIntoNewVariablePath(
export function moveValueIntoNewVariable(
ast: Node<Program>,
programMemory: ProgramMemory,
memVars: VariableMap,
sourceRange: SourceRange,
variableName: string
): {
@ -1263,11 +1259,7 @@ export function moveValueIntoNewVariable(
const { isSafe, value, replacer } = meta
if (!isSafe || value.type === 'Identifier') return { modifiedAst: ast }
const { insertIndex } = findAllPreviousVariables(
ast,
programMemory,
sourceRange
)
const { insertIndex } = findAllPreviousVariables(ast, memVars, sourceRange)
let _node = structuredClone(ast)
const replaced = replacer(_node, variableName)
if (trap(replaced)) return { modifiedAst: ast }
@ -1289,7 +1281,7 @@ export function moveValueIntoNewVariable(
export function deleteSegmentFromPipeExpression(
dependentRanges: SourceRange[],
modifiedAst: Node<Program>,
programMemory: ProgramMemory,
memVars: VariableMap,
code: string,
pathToNode: PathToNode
): Node<Program> | Error {
@ -1321,7 +1313,7 @@ export function deleteSegmentFromPipeExpression(
callExp.shallowPath,
constraintInfo.argPosition,
_modifiedAst,
programMemory
memVars
)
if (!transform) return
_modifiedAst = transform.modifiedAst
@ -1350,7 +1342,7 @@ export function removeSingleConstraintInfo(
pathToCallExp: PathToNode,
argDetails: SimplifiedArgDetails,
ast: Node<Program>,
programMemory: ProgramMemory
memVars: VariableMap
):
| {
modifiedAst: Node<Program>
@ -1367,7 +1359,7 @@ export function removeSingleConstraintInfo(
ast,
selectionRanges: [pathToCallExp],
transformInfos: [transform],
programMemory,
memVars,
referenceSegName: '',
})
if (err(retval)) return false
@ -1377,7 +1369,7 @@ export function removeSingleConstraintInfo(
export async function deleteFromSelection(
ast: Node<Program>,
selection: Selection,
programMemory: ProgramMemory,
variables: VariableMap,
getFaceDetails: (id: string) => Promise<Models['FaceIsPlanar_type']> = () =>
({} as any)
): Promise<Node<Program> | Error> {
@ -1506,7 +1498,7 @@ export async function deleteFromSelection(
return
}
const sketchToPreserve = sketchFromKclValue(
programMemory.get(sketchName),
variables[sketchName],
sketchName
)
if (err(sketchToPreserve)) return sketchToPreserve

View File

@ -298,7 +298,7 @@ export function getPathToExtrudeForSegmentSelection(
const sketchVar = varDecNode.node.declaration.id.name
const sketch = sketchFromKclValue(
dependencies.kclManager.programMemory.get(sketchVar),
dependencies.kclManager.variables[sketchVar],
sketchVar
)
if (trap(sketch)) return sketch

View File

@ -9,7 +9,6 @@ import {
CallExpression,
VariableDeclarator,
} from './wasm'
import { ProgramMemory } from 'lang/wasm'
import {
findAllPreviousVariables,
isNodeSafeToReplace,
@ -63,7 +62,7 @@ variableBelowShouldNotBeIncluded = 3
const { variables, bodyPath, insertIndex } = findAllPreviousVariables(
ast,
execState.memory,
execState.variables,
topLevelRange(rangeStart, rangeStart)
)
expect(variables).toEqual([
@ -398,7 +397,7 @@ part001 = startSketchAt([-1.41, 3.46])
selection: {
codeRef: codeRefFromRange(topLevelRange(100, 101), ast),
},
programMemory: execState.memory,
memVars: execState.variables,
})
expect(result).toEqual(true)
})
@ -418,7 +417,7 @@ part001 = startSketchAt([-1.41, 3.46])
selection: {
codeRef: codeRefFromRange(topLevelRange(100, 101), ast),
},
programMemory: execState.memory,
memVars: execState.variables,
})
expect(result).toEqual(true)
})
@ -432,7 +431,7 @@ part001 = startSketchAt([-1.41, 3.46])
selection: {
codeRef: codeRefFromRange(topLevelRange(10, 11), ast),
},
programMemory: execState.memory,
memVars: execState.variables,
})
expect(result).toEqual(false)
})
@ -722,7 +721,7 @@ describe('Testing specific sketch getNodeFromPath workflow', () => {
const sketchPathToNode = getNodePathFromSourceRange(ast, sketchRange)
const modifiedAst = addCallExpressionsToPipe({
node: ast,
programMemory: ProgramMemory.empty(),
variables: {},
pathToNode: sketchPathToNode,
expressions: [
createCallExpressionStdLib(
@ -777,7 +776,7 @@ describe('Testing specific sketch getNodeFromPath workflow', () => {
const sketchPathToNode = getNodePathFromSourceRange(ast, sketchRange)
const modifiedAst = addCloseToPipe({
node: ast,
programMemory: ProgramMemory.empty(),
variables: {},
pathToNode: sketchPathToNode,
})

View File

@ -13,7 +13,6 @@ import {
PathToNode,
PipeExpression,
Program,
ProgramMemory,
ReturnStatement,
sketchFromKclValue,
sketchFromKclValueOptional,
@ -26,6 +25,7 @@ import {
kclSettings,
unitLenToUnitLength,
unitAngToUnitAngle,
VariableMap,
} from './wasm'
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
import { createIdentifier, splitPathAtLastIndex } from './modifyAst'
@ -287,7 +287,7 @@ export interface PrevVariable<T> {
export function findAllPreviousVariablesPath(
ast: Program,
programMemory: ProgramMemory,
memVars: VariableMap,
path: PathToNode,
type: 'number' | 'string' = 'number'
): {
@ -325,7 +325,7 @@ export function findAllPreviousVariablesPath(
bodyItems?.forEach?.((item) => {
if (item.type !== 'VariableDeclaration' || item.end > startRange) return
const varName = item.declaration.id.name
const varValue = programMemory?.get(varName)
const varValue = memVars[varName]
if (!varValue || typeof varValue?.value !== type) return
variables.push({
key: varName,
@ -342,7 +342,7 @@ export function findAllPreviousVariablesPath(
export function findAllPreviousVariables(
ast: Program,
programMemory: ProgramMemory,
memVars: VariableMap,
sourceRange: SourceRange,
type: 'number' | 'string' = 'number'
): {
@ -351,7 +351,7 @@ export function findAllPreviousVariables(
insertIndex: number
} {
const path = getNodePathFromSourceRange(ast, sourceRange)
return findAllPreviousVariablesPath(ast, programMemory, path, type)
return findAllPreviousVariablesPath(ast, memVars, path, type)
}
type ReplacerFn = (
@ -479,7 +479,7 @@ function isTypeInArrayExp(
export function isLinesParallelAndConstrained(
ast: Program,
artifactGraph: ArtifactGraph,
programMemory: ProgramMemory,
memVars: VariableMap,
primaryLine: Selection,
secondaryLine: Selection
):
@ -509,7 +509,7 @@ export function isLinesParallelAndConstrained(
if (err(_varDec)) return _varDec
const varDec = _varDec.node
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
const _primarySegment = getSketchSegmentFromSourceRange(
sg,
@ -589,11 +589,11 @@ export function isLinesParallelAndConstrained(
export function hasExtrudeSketch({
ast,
selection,
programMemory,
memVars,
}: {
ast: Program
selection: Selection
programMemory: ProgramMemory
memVars: VariableMap
}): boolean {
const varDecMeta = getNodeFromPath<VariableDeclaration>(
ast,
@ -607,7 +607,7 @@ export function hasExtrudeSketch({
const varDec = varDecMeta.node
if (varDec.type !== 'VariableDeclaration') return false
const varName = varDec.declaration.id.name
const varValue = programMemory?.get(varName)
const varValue = memVars[varName]
return (
varValue?.type === 'Solid' ||
!(sketchFromKclValueOptional(varValue, varName) instanceof Reason)

View File

@ -124,7 +124,7 @@ describe('testing changeSketchArguments', () => {
const sourceStart = code.indexOf(lineToChange)
const changeSketchArgsRetVal = changeSketchArguments(
ast,
execState.memory,
execState.variables,
{
type: 'sourceRange',
sourceRange: topLevelRange(
@ -160,7 +160,7 @@ mySketch001 = startSketchOn('XY')
expect(sourceStart).toBe(89)
const newSketchLnRetVal = addNewSketchLn({
node: ast,
programMemory: execState.memory,
variables: execState.variables,
input: {
type: 'straight-segment',
from: [0, 0],
@ -190,7 +190,7 @@ mySketch001 = startSketchOn('XY')
const modifiedAst2 = addCloseToPipe({
node: ast,
programMemory: execState.memory,
variables: execState.variables,
pathToNode: [
['body', ''],
[0, 'index'],

View File

@ -1,5 +1,4 @@
import {
ProgramMemory,
Path,
Sketch,
SourceRange,
@ -14,6 +13,7 @@ import {
Identifier,
sketchFromKclValue,
topLevelRange,
VariableMap,
} from 'lang/wasm'
import {
getNodeFromPath,
@ -355,7 +355,7 @@ function getTagKwArg(): SketchLineHelperKw['getTag'] {
export const line: SketchLineHelperKw = {
add: ({
node,
previousProgramMemory,
variables,
pathToNode,
segmentInput,
replaceExistingCallback,
@ -494,7 +494,7 @@ export const line: SketchLineHelperKw = {
export const lineTo: SketchLineHelperKw = {
add: ({
node,
previousProgramMemory,
variables,
pathToNode,
segmentInput,
replaceExistingCallback,
@ -1313,7 +1313,7 @@ export const angledLine: SketchLineHelper = {
export const angledLineOfXLength: SketchLineHelper = {
add: ({
node,
previousProgramMemory,
variables,
pathToNode,
segmentInput,
replaceExistingCallback,
@ -1337,10 +1337,7 @@ export const angledLineOfXLength: SketchLineHelper = {
const { node: varDec } = nodeMeta2
const variableName = varDec.id.name
const sketch = sketchFromKclValue(
previousProgramMemory?.get(variableName),
variableName
)
const sketch = sketchFromKclValue(variables[variableName], variableName)
if (err(sketch)) {
return sketch
}
@ -1429,7 +1426,7 @@ export const angledLineOfXLength: SketchLineHelper = {
export const angledLineOfYLength: SketchLineHelper = {
add: ({
node,
previousProgramMemory,
variables,
pathToNode,
segmentInput,
replaceExistingCallback,
@ -1452,10 +1449,7 @@ export const angledLineOfYLength: SketchLineHelper = {
if (err(nodeMeta2)) return nodeMeta2
const { node: varDec } = nodeMeta2
const variableName = varDec.id.name
const sketch = sketchFromKclValue(
previousProgramMemory?.get(variableName),
variableName
)
const sketch = sketchFromKclValue(variables[variableName], variableName)
if (err(sketch)) return sketch
const angle = createLiteral(roundOff(getAngle(from, to), 0))
@ -1793,7 +1787,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
}
return new Error('not implemented')
},
updateArgs: ({ node, pathToNode, input, previousProgramMemory }) => {
updateArgs: ({ node, pathToNode, input, variables }) => {
if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
const { to, from } = input
const _node = { ...node }
@ -1820,10 +1814,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
const { node: varDec } = nodeMeta2
const varName = varDec.declaration.id.name
const sketch = sketchFromKclValue(
previousProgramMemory.get(varName),
varName
)
const sketch = sketchFromKclValue(variables[varName], varName)
if (err(sketch)) return sketch
const intersectPath = sketch.paths.find(
({ tag }: Path) => tag && tag.value === intersectTagName
@ -1996,7 +1987,7 @@ export const sketchLineHelperMapKw: { [key: string]: SketchLineHelperKw } = {
export function changeSketchArguments(
node: Node<Program>,
programMemory: ProgramMemory,
variables: VariableMap,
sourceRangeOrPath:
| {
type: 'sourceRange'
@ -2030,7 +2021,7 @@ export function changeSketchArguments(
return updateArgs({
node: _node,
previousProgramMemory: programMemory,
variables,
pathToNode: shallowPath,
input,
})
@ -2047,7 +2038,7 @@ export function changeSketchArguments(
return updateArgs({
node: _node,
previousProgramMemory: programMemory,
variables,
pathToNode: shallowPath,
input,
})
@ -2112,7 +2103,7 @@ export function compareVec2Epsilon2(
interface CreateLineFnCallArgs {
node: Node<Program>
programMemory: ProgramMemory
variables: VariableMap
input: SegmentInputs
fnName: ToolTip
pathToNode: PathToNode
@ -2121,7 +2112,7 @@ interface CreateLineFnCallArgs {
export function addNewSketchLn({
node: _node,
programMemory: previousProgramMemory,
variables,
fnName,
pathToNode,
input: segmentInput,
@ -2151,7 +2142,7 @@ export function addNewSketchLn({
)
return add({
node,
previousProgramMemory,
variables,
pathToNode,
segmentInput,
spliceBetween,
@ -2164,7 +2155,7 @@ export function addCallExpressionsToPipe({
expressions,
}: {
node: Node<Program>
programMemory: ProgramMemory
variables: VariableMap
pathToNode: PathToNode
expressions: Node<CallExpression | CallExpressionKw>[]
}) {
@ -2188,7 +2179,7 @@ export function addCloseToPipe({
pathToNode,
}: {
node: Program
programMemory: ProgramMemory
variables: VariableMap
pathToNode: PathToNode
}) {
const _node = { ...node }
@ -2209,7 +2200,7 @@ export function addCloseToPipe({
export function replaceSketchLine({
node,
programMemory,
variables,
pathToNode: _pathToNode,
fnName,
segmentInput,
@ -2217,7 +2208,7 @@ export function replaceSketchLine({
referencedSegment,
}: {
node: Node<Program>
programMemory: ProgramMemory
variables: VariableMap
pathToNode: PathToNode
fnName: ToolTip
segmentInput: SegmentInputs
@ -2241,7 +2232,7 @@ export function replaceSketchLine({
: sketchLineHelperMap[fnName]
const addRetVal = add({
node: _node,
previousProgramMemory: programMemory,
variables,
pathToNode: _pathToNode,
referencedSegment,
segmentInput,

View File

@ -53,7 +53,7 @@ async function testingSwapSketchFnCall({
return Promise.reject(new Error('transformInfos undefined'))
const ast2 = transformAstSketchLines({
ast,
programMemory: execState.memory,
memVars: execState.variables,
selectionRanges: selections,
transformInfos,
referenceSegName: '',
@ -373,7 +373,7 @@ part001 = startSketchOn('XY')
const execState = await enginelessExecutor(assertParse(code))
const index = code.indexOf('// normal-segment') - 7
const sg = sketchFromKclValue(
execState.memory.get('part001'),
execState.variables['part001'],
'part001'
) as Sketch
const _segment = getSketchSegmentFromSourceRange(
@ -393,7 +393,7 @@ part001 = startSketchOn('XY')
const execState = await enginelessExecutor(assertParse(code))
const index = code.indexOf('// segment-in-start') - 7
const _segment = getSketchSegmentFromSourceRange(
sketchFromKclValue(execState.memory.get('part001'), 'part001') as Sketch,
sketchFromKclValue(execState.variables['part001'], 'part001') as Sketch,
topLevelRange(index, index)
)
if (err(_segment)) throw _segment

View File

@ -193,7 +193,7 @@ describe('testing transformAstForSketchLines for equal length constraint', () =>
ast,
selectionRanges: transformedSelection,
transformInfos,
programMemory: execState.memory,
memVars: execState.variables,
})
if (err(newAst)) return Promise.reject(newAst)
@ -356,7 +356,7 @@ part001 = startSketchOn('XY')
ast,
selectionRanges: makeSelections(selectionRanges),
transformInfos,
programMemory: execState.memory,
memVars: execState.variables,
})
if (err(newAst)) return Promise.reject(newAst)
@ -445,7 +445,7 @@ part001 = startSketchOn('XY')
ast,
selectionRanges: makeSelections(selectionRanges),
transformInfos,
programMemory: execState.memory,
memVars: execState.variables,
referenceSegName: '',
})
if (err(newAst)) return Promise.reject(newAst)
@ -505,7 +505,7 @@ part001 = startSketchOn('XY')
ast,
selectionRanges: makeSelections(selectionRanges),
transformInfos,
programMemory: execState.memory,
memVars: execState.variables,
referenceSegName: '',
})
if (err(newAst)) return Promise.reject(newAst)
@ -600,7 +600,7 @@ async function helperThing(
ast,
selectionRanges: makeSelections(selectionRanges),
transformInfos,
programMemory: execState.memory,
memVars: execState.variables,
})
if (err(newAst)) return Promise.reject(newAst)

View File

@ -17,13 +17,13 @@ import {
BinaryPart,
VariableDeclarator,
PathToNode,
ProgramMemory,
sketchFromKclValue,
Literal,
SourceRange,
LiteralValue,
recast,
LabeledArg,
VariableMap,
} from '../wasm'
import { getNodeFromPath, getNodeFromPathCurry } from '../queryAst'
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
@ -1745,14 +1745,14 @@ export function transformSecondarySketchLinesTagFirst({
ast,
selectionRanges,
transformInfos,
programMemory,
memVars,
forceSegName,
forceValueUsedInTransform,
}: {
ast: Node<Program>
selectionRanges: Selections
transformInfos: TransformInfo[]
programMemory: ProgramMemory
memVars: VariableMap
forceSegName?: string
forceValueUsedInTransform?: BinaryPart
}):
@ -1788,7 +1788,7 @@ export function transformSecondarySketchLinesTagFirst({
},
referencedSegmentRange: primarySelection,
transformInfos,
programMemory,
memVars,
referenceSegName: tag,
forceValueUsedInTransform,
})
@ -1822,7 +1822,7 @@ export function transformAstSketchLines({
ast,
selectionRanges,
transformInfos,
programMemory,
memVars,
referenceSegName,
forceValueUsedInTransform,
referencedSegmentRange,
@ -1830,7 +1830,7 @@ export function transformAstSketchLines({
ast: Node<Program>
selectionRanges: Selections | PathToNode[]
transformInfos: TransformInfo[]
programMemory: ProgramMemory
memVars: VariableMap
referenceSegName: string
referencedSegmentRange?: SourceRange
forceValueUsedInTransform?: BinaryPart
@ -1946,7 +1946,7 @@ export function transformAstSketchLines({
})
const varName = varDec.node.id.name
let kclVal = programMemory.get(varName)
let kclVal = memVars[varName]
let sketch
if (kclVal?.type === 'Solid') {
sketch = kclVal.value.sketch
@ -1977,7 +1977,7 @@ export function transformAstSketchLines({
// Note to ADAM: Here is where the replaceExisting call gets sent.
const replacedSketchLine = replaceSketchLine({
node: node,
programMemory,
variables: memVars,
pathToNode: _pathToNode,
referencedSegment,
fnName: transformTo || (call.node.callee.name as ToolTip),

View File

@ -18,8 +18,8 @@ describe('testing angledLineThatIntersects', () => {
}, %, $yo2)
intersect = segEndX(yo2)`
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')))
expect(noOffset.memory.get('intersect')?.value).toBeCloseTo(1)
expect(noOffset.variables['intersect']?.value).toBeCloseTo(1)
})
})

View File

@ -1,6 +1,5 @@
import { ToolTip } from 'lang/langHelpers'
import {
ProgramMemory,
Path,
SourceRange,
Program,
@ -10,14 +9,15 @@ import {
Literal,
BinaryPart,
CallExpressionKw,
VariableMap,
} from '../wasm'
import { LineInputsType } from './sketchcombos'
import { Node } from 'wasm-lib/kcl/bindings/Node'
export interface ModifyAstBase {
node: Node<Program>
// TODO #896: Remove ProgramMemory from this interface
previousProgramMemory: ProgramMemory
// TODO #896: Remove memory variables from this interface
variables: VariableMap
pathToNode: PathToNode
}

View File

@ -18,7 +18,7 @@ it('can execute parsed AST', async () => {
expect(pResult.program).not.toEqual(null)
const execState = await enginelessExecutor(pResult.program as Node<Program>)
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', () => {

View File

@ -3,7 +3,8 @@ import {
parse_wasm,
recast_wasm,
format_number,
execute,
execute_with_engine,
execute_mock,
kcl_lint,
modify_ast_for_sketch_wasm,
is_points_ccw,
@ -281,6 +282,8 @@ export const assertParse = (code: string): Node<Program> => {
return result.program
}
export type VariableMap = { [key in string]?: KclValue }
export type PathToNode = [string | number, string][]
export const isPathToNodeNumber = (
@ -290,7 +293,7 @@ export const isPathToNodeNumber = (
}
export interface ExecState {
memory: ProgramMemory
variables: { [key in string]?: KclValue }
operations: Operation[]
artifacts: { [key in ArtifactId]?: RustArtifact }
artifactCommands: ArtifactCommand[]
@ -303,7 +306,7 @@ export interface ExecState {
*/
export function emptyExecState(): ExecState {
return {
memory: ProgramMemory.empty(),
variables: {},
operations: [],
artifacts: {},
artifactCommands: [],
@ -328,7 +331,7 @@ function execStateFromRust(
}
return {
memory: ProgramMemory.fromRaw(execOutcome.memory),
variables: execOutcome.variables,
operations: execOutcome.operations,
artifacts: execOutcome.artifacts,
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>
function rustArtifactGraphToMap(
@ -354,203 +367,6 @@ export function defaultArtifactGraph(): ArtifactGraph {
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.
export function sketchFromKclValueOptional(
obj: any,
@ -590,58 +406,85 @@ export function sketchFromKclValue(
* @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.
* @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>,
engineCommandManager: EngineCommandManager,
usePrevMemory?: boolean,
path?: string,
programMemoryOverride: ProgramMemory | Error | null = null
variables?: { [key in string]?: KclValue }
): Promise<ExecState> => {
if (programMemoryOverride !== null && err(programMemoryOverride))
return Promise.reject(programMemoryOverride)
// eslint-disable-next-line @typescript-eslint/no-floating-promises
if (programMemoryOverride !== null && err(programMemoryOverride))
return Promise.reject(programMemoryOverride)
try {
let jsAppSettings = default_app_settings()
if (!TEST) {
const lastSettingsSnapshot = await import(
'components/SettingsAuthProvider'
).then((module) => module.lastSettingsContextSnapshot)
if (lastSettingsSnapshot) {
jsAppSettings = getAllCurrentSettings(lastSettingsSnapshot)
}
if (!variables) {
variables = {}
}
const execOutcome: RustExecOutcome = await execute(
if (usePrevMemory === undefined) {
usePrevMemory = true
}
const execOutcome: RustExecOutcome = await execute_mock(
JSON.stringify(node),
path,
JSON.stringify(programMemoryOverride?.toRaw() || null),
JSON.stringify({ settings: jsAppSettings }),
JSON.stringify({ settings: await 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,
fileSystemManager
)
return execStateFromRust(execOutcome, node)
} catch (e: any) {
console.log(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)
return Promise.reject(errFromErrWithOutputs(e))
}
}
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>> => {
try {
const discovered_findings: Array<Discovered> = await kcl_lint(
@ -707,7 +550,6 @@ export const modifyAstForSketch = async (
defaultArtifactGraph()
)
console.log(kclError)
return Promise.reject(kclError)
}
}
@ -755,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(
coreDumpManager: CoreDumpManager,
openGithubIssue: boolean = false

View File

@ -576,7 +576,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
ast: structuredClone(kclManager.ast),
selectionRanges,
transformInfos: transforms,
programMemory: kclManager.programMemory,
memVars: kclManager.variables,
referenceSegName: '',
})
if (err(sketched)) return KCL_DEFAULT_LENGTH

View File

@ -105,8 +105,8 @@ export const MAKE_TOAST_MESSAGES = {
/** The URL for the KCL samples manifest files */
export const KCL_SAMPLES_MANIFEST_URLS = {
remote:
'https://raw.githubusercontent.com/KittyCAD/kcl-samples/main/manifest.json',
// TODO: enable remote fetching again, maybe?
// remote: `https://raw.githubusercontent.com/KittyCAD/kcl-samples/${VITE_KC_KCL_SAMPLES_REF}/manifest.json`,
localFallback: '/kcl-samples-manifest-fallback.json',
} as const

View File

@ -51,16 +51,16 @@ sketch002 = startSketchOn(sketch001, seg03)
center = [-1.25, 1],
radius = mountingHoleDiameter / 2,
}, %)
|> patternLinear2d({
|> patternLinear2d(
instances = 2,
distance = 2.5,
axis = [-1, 0],
}, %)
|> patternLinear2d({
)
|> patternLinear2d(
instances = 2,
distance = 4,
axis = [0, 1],
}, %)
)
|> extrude(%, length = -thickness-.01)
sketch003 = startSketchOn(sketch001, seg04)
@ -68,11 +68,11 @@ sketch003 = startSketchOn(sketch001, seg04)
center = [1, -1],
radius = mountingHoleDiameter / 2,
}, %)
|> patternLinear2d({
|> patternLinear2d(
instances = 2,
distance = 4,
axis = [1, 0],
}, %)
)
|> extrude(%, length = -thickness-0.1)
`

View File

@ -10,23 +10,27 @@ export type KclSamplesManifestItem = {
}
export async function getKclSamplesManifest() {
let response = await fetch(KCL_SAMPLES_MANIFEST_URLS.remote)
if (!response.ok) {
console.warn(
'Failed to fetch latest remote KCL samples manifest, falling back to local:',
response.statusText
)
response = await fetch(
(isDesktop() ? '.' : '') + KCL_SAMPLES_MANIFEST_URLS.localFallback
)
if (!response.ok) {
console.error(
'Failed to fetch fallback KCL samples manifest:',
response.statusText
)
return []
}
}
// TODO: enable remote fetching again, maybe?
// let response = await fetch(KCL_SAMPLES_MANIFEST_URLS.remote)
// if (!response.ok) {
// console.warn(
// 'Failed to fetch latest remote KCL samples manifest, falling back to local:',
// response.statusText
// )
// response = await fetch(
// (isDesktop() ? '.' : '') + KCL_SAMPLES_MANIFEST_URLS.localFallback
// )
// if (!response.ok) {
// console.error(
// 'Failed to fetch fallback KCL samples manifest:',
// response.statusText
// )
// return []
// }
// }
const response = await fetch(
(isDesktop() ? '.' : '') + KCL_SAMPLES_MANIFEST_URLS.localFallback
)
return response.json().then((manifest) => {
return manifest as KclSamplesManifestItem[]
})

View File

@ -6,8 +6,8 @@ import { FILE_EXT } from './constants'
import { UnitLength_type } from '@kittycad/lib/dist/types/src/models'
import { reportRejection } from './trap'
import { IndexLoaderData } from './types'
import { IS_NIGHTLY_OR_DEBUG } from 'routes/Settings'
import { copyFileShareLink } from './links'
import { VITE_KC_KCL_SAMPLES_REF } from 'env'
interface OnSubmitProps {
sampleName: string
@ -64,7 +64,7 @@ export function kclCommands(commandProps: KclCommandConfig): Command[] {
const pathParts = data.sample.split('/')
const projectPathPart = pathParts[0]
const primaryKclFile = pathParts[1]
const sampleCodeUrl = `https://raw.githubusercontent.com/KittyCAD/kcl-samples/main/${encodeURIComponent(
const sampleCodeUrl = `https://raw.githubusercontent.com/KittyCAD/kcl-samples/${VITE_KC_KCL_SAMPLES_REF}/${encodeURIComponent(
projectPathPart
)}/${encodeURIComponent(primaryKclFile)}`
@ -137,7 +137,6 @@ export function kclCommands(commandProps: KclCommandConfig): Command[] {
{
name: 'share-file-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.',
groupId: 'code',
needsReview: false,

View File

@ -1,67 +1,55 @@
import { ParseResult, ProgramMemory } from 'lang/wasm'
import { ParseResult, VariableMap } from 'lang/wasm'
import { getCalculatedKclExpressionValue } from './kclHelpers'
describe('KCL expression calculations', () => {
it('calculates a simple expression', async () => {
const actual = await getCalculatedKclExpressionValue({
value: '1 + 2',
programMemory: ProgramMemory.empty(),
})
const actual = await getCalculatedKclExpressionValue('1 + 2', {})
const coercedActual = actual as Exclude<typeof actual, Error | ParseResult>
expect(coercedActual).not.toHaveProperty('errors')
expect(coercedActual.valueAsString).toEqual('3')
expect(coercedActual?.astNode).toBeDefined()
})
it('calculates a simple expression with a variable', async () => {
const programMemory = ProgramMemory.empty()
programMemory.set('x', {
const variables: VariableMap = {}
variables['x'] = {
type: 'Number',
value: 2,
__meta: [],
})
const actual = await getCalculatedKclExpressionValue({
value: '1 + x',
programMemory,
})
}
const actual = await getCalculatedKclExpressionValue('1 + x', variables)
const coercedActual = actual as Exclude<typeof actual, Error | ParseResult>
expect(coercedActual.valueAsString).toEqual('3')
expect(coercedActual.astNode).toBeDefined()
})
it('returns NAN for an invalid expression', async () => {
const actual = await getCalculatedKclExpressionValue({
value: '1 + x',
programMemory: ProgramMemory.empty(),
})
const actual = await getCalculatedKclExpressionValue('1 + x', {})
const coercedActual = actual as Exclude<typeof actual, Error | ParseResult>
expect(coercedActual.valueAsString).toEqual('NAN')
expect(coercedActual.astNode).toBeDefined()
})
it('returns NAN for an expression with an invalid variable', async () => {
const programMemory = ProgramMemory.empty()
programMemory.set('y', {
const variables: VariableMap = {}
variables['y'] = {
type: 'Number',
value: 2,
__meta: [],
})
const actual = await getCalculatedKclExpressionValue({
value: '1 + x',
programMemory,
})
}
const actual = await getCalculatedKclExpressionValue('1 + x', variables)
const coercedActual = actual as Exclude<typeof actual, Error | ParseResult>
expect(coercedActual.valueAsString).toEqual('NAN')
expect(coercedActual.astNode).toBeDefined()
})
it('calculates a more complex expression with a variable', async () => {
const programMemory = ProgramMemory.empty()
programMemory.set('x', {
const variables: VariableMap = {}
variables['x'] = {
type: 'Number',
value: 2,
__meta: [],
})
const actual = await getCalculatedKclExpressionValue({
value: '(1 + x * x) * 2',
programMemory,
})
}
const actual = await getCalculatedKclExpressionValue(
'(1 + x * x) * 2',
variables
)
const coercedActual = actual as Exclude<typeof actual, Error | ParseResult>
expect(coercedActual.valueAsString).toEqual('10')
expect(coercedActual.astNode).toBeDefined()

View File

@ -1,48 +1,20 @@
import { err } from './trap'
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 { executeAst } from 'lang/langHelpers'
import { KclExpression } from './commandTypes'
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,
* given the value and the variables that are available
*/
export async function getCalculatedKclExpressionValue({
value,
programMemory,
}: {
value: string
programMemory: ProgramMemory
}) {
export async function getCalculatedKclExpressionValue(
value: string,
variables: VariableMap
) {
// Create a one-line program that assigns the value to a variable
const dummyProgramCode = `const ${DUMMY_VARIABLE_NAME} = ${value}`
const pResult = parse(dummyProgramCode)
@ -53,7 +25,8 @@ export async function getCalculatedKclExpressionValue({
const { execState } = await executeAst({
ast,
engineCommandManager,
programMemoryOverride: programMemory,
isMock: true,
variables,
})
// Find the variable declaration for the result
@ -65,7 +38,7 @@ export async function getCalculatedKclExpressionValue({
const variableDeclaratorAstNode =
resultDeclaration?.type === 'VariableDeclaration' &&
resultDeclaration?.declaration.init
const resultRawValue = execState.memory?.get(DUMMY_VARIABLE_NAME)?.value
const resultRawValue = execState.variables[DUMMY_VARIABLE_NAME]?.value
return {
astNode: variableDeclaratorAstNode,
@ -74,17 +47,14 @@ export async function getCalculatedKclExpressionValue({
}
}
export async function stringToKclExpression({
value,
programMemory,
}: {
value: string
programMemory: ProgramMemory
}) {
const calculatedResult = await getCalculatedKclExpressionValue({
export async function stringToKclExpression(
value: string,
variables: VariableMap
) {
const calculatedResult = await getCalculatedKclExpressionValue(
value,
programMemory,
})
variables
)
if (err(calculatedResult) || 'errors' in calculatedResult) {
return calculatedResult
} else if (!calculatedResult.astNode) {

View File

@ -80,13 +80,13 @@ const prepareToEditExtrude: PrepareToEditCallback =
}
// Convert the length argument from a string to a KCL expression
const distanceResult = await stringToKclExpression({
value: codeManager.code.slice(
const distanceResult = await stringToKclExpression(
codeManager.code.slice(
operation.labeledArgs?.['length']?.sourceRange[0],
operation.labeledArgs?.['length']?.sourceRange[1]
),
programMemory: kclManager.programMemory.clone(),
})
{}
)
if (err(distanceResult) || 'errors' in distanceResult) {
return baseCommand
}
@ -163,13 +163,13 @@ const prepareToEditOffsetPlane: PrepareToEditCallback = async ({
}
// Convert the distance argument from a string to a KCL expression
const distanceResult = await stringToKclExpression({
value: codeManager.code.slice(
const distanceResult = await stringToKclExpression(
codeManager.code.slice(
operation.labeledArgs.offset.sourceRange[0],
operation.labeledArgs.offset.sourceRange[1]
),
programMemory: kclManager.programMemory.clone(),
})
{}
)
if (err(distanceResult) || 'errors' in distanceResult) {
return baseCommand

View File

@ -1,9 +1,9 @@
import {
Program,
ProgramMemory,
executor,
executeMock,
SourceRange,
ExecState,
VariableMap,
} from '../lang/wasm'
import { EngineCommandManager } from 'lang/std/engineConnection'
import { EngineCommand } from 'lang/std/artifactGraph'
@ -80,18 +80,9 @@ class MockEngineCommandManager {
export async function enginelessExecutor(
ast: Node<Program>,
pmo: ProgramMemory | Error = ProgramMemory.empty(),
path?: string
usePrevMemory?: boolean,
path?: string,
variables?: VariableMap
): Promise<ExecState> {
if (pmo !== null && err(pmo)) return Promise.reject(pmo)
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
return await executeMock(ast, usePrevMemory, path, variables)
}

View File

@ -5,10 +5,7 @@ import { findUniqueName } from 'lang/modifyAst'
import { PrevVariable, findAllPreviousVariables } from 'lang/queryAst'
import { Expr } from 'lang/wasm'
import { useEffect, useRef, useState } from 'react'
import {
getCalculatedKclExpressionValue,
programMemoryFromVariables,
} from './kclHelpers'
import { getCalculatedKclExpressionValue } from './kclHelpers'
import { parse, resultIsOk } from 'lang/wasm'
import { err } from 'lib/trap'
@ -36,7 +33,7 @@ export function useCalculateKclExpression({
newVariableInsertIndex: number
setNewVariableName: (a: string) => void
} {
const { programMemory, code } = useKclContext()
const { variables, code } = useKclContext()
const { context } = useModelingContext()
// If there is no selection, use the end of the code
// so all variables are available
@ -80,7 +77,7 @@ export function useCalculateKclExpression({
useEffect(() => {
if (
programMemory.has(newVariableName) ||
variables[newVariableName] ||
newVariableName === '' ||
!isValidVariableName(newVariableName)
) {
@ -88,33 +85,22 @@ export function useCalculateKclExpression({
} else {
setIsNewVariableNameUnique(true)
}
}, [programMemory, newVariableName])
}, [variables, newVariableName])
useEffect(() => {
if (!programMemory) return
if (!variables) return
const varInfo = findAllPreviousVariables(
kclManager.ast,
kclManager.programMemory,
kclManager.variables,
// If there is no selection, use the end of the code
selectionRange || [code.length, code.length]
)
setAvailableVarInfo(varInfo)
}, [kclManager.ast, kclManager.programMemory, selectionRange])
}, [kclManager.ast, kclManager.variables, selectionRange])
useEffect(() => {
const execAstAndSetResult = async () => {
const programMemory = programMemoryFromVariables(
availableVarInfo.variables
)
if (programMemory instanceof Error) {
setCalcResult('NAN')
setValueNode(null)
return
}
const result = await getCalculatedKclExpressionValue({
value,
programMemory,
})
const result = await getCalculatedKclExpressionValue(value, {})
if (result instanceof Error || 'errors' in result) {
setCalcResult('NAN')
setValueNode(null)
@ -128,7 +114,7 @@ export function useCalculateKclExpression({
setCalcResult('NAN')
setValueNode(null)
})
}, [value, availableVarInfo, code, kclManager.programMemory])
}, [value, availableVarInfo, code, kclManager.variables])
return {
valueNode,

View File

@ -5,7 +5,7 @@ import { findAllPreviousVariables } from 'lang/queryAst'
import { useEffect, useState } from 'react'
export function usePreviousVariables() {
const { programMemory, code } = useKclContext()
const { variables, code } = useKclContext()
const { context } = useModelingContext()
const selectionRange = context.selectionRanges.graphSelections[0]?.codeRef
?.range || [code.length, code.length]
@ -18,14 +18,14 @@ export function usePreviousVariables() {
})
useEffect(() => {
if (!programMemory || !selectionRange) return
if (!variables || !selectionRange) return
const varInfo = findAllPreviousVariables(
kclManager.ast,
kclManager.programMemory,
kclManager.variables,
selectionRange
)
setPreviousVariablesInfo(varInfo)
}, [kclManager.ast, kclManager.programMemory, selectionRange])
}, [kclManager.ast, kclManager.variables, selectionRange])
return previousVariablesInfo
}

View File

@ -11,7 +11,8 @@ import {
parse_wasm as ParseWasm,
recast_wasm as RecastWasm,
format_number as FormatNumber,
execute as Execute,
execute_with_engine as ExecuteWithEngine,
execute_mock as ExecuteMock,
kcl_lint as KclLint,
modify_ast_for_sketch_wasm as ModifyAstForSketch,
is_points_ccw as IsPointsCcw,
@ -57,8 +58,11 @@ export const recast_wasm: typeof RecastWasm = (...args) => {
export const format_number: typeof FormatNumber = (...args) => {
return getModule().format_number(...args)
}
export const execute: typeof Execute = (...args) => {
return getModule().execute(...args)
export const execute_with_engine: typeof ExecuteWithEngine = (...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) => {
return getModule().kcl_lint(...args)

View File

@ -1,6 +1,5 @@
import {
PathToNode,
ProgramMemory,
VariableDeclaration,
VariableDeclarator,
parse,
@ -708,7 +707,7 @@ export const modelingMachine = setup({
const modifiedAst = await deleteFromSelection(
ast,
selectionRanges.graphSelections[0],
kclManager.programMemory,
kclManager.variables,
getFaceDetails
)
if (err(modifiedAst)) {
@ -719,8 +718,7 @@ export const modelingMachine = setup({
const testExecute = await executeAst({
ast: modifiedAst,
engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride: ProgramMemory.empty(),
isMock: true,
})
if (testExecute.errors.length) {
toast.error(errorMessage)
@ -1087,7 +1085,7 @@ export const modelingMachine = setup({
selectionRanges,
'horizontal',
kclManager.ast,
kclManager.programMemory
kclManager.variables
)
if (trap(constraint)) return false
const { modifiedAst, pathToNodeMap } = constraint
@ -1122,7 +1120,7 @@ export const modelingMachine = setup({
selectionRanges,
'vertical',
kclManager.ast,
kclManager.programMemory
kclManager.variables
)
if (trap(constraint)) return false
const { modifiedAst, pathToNodeMap } = constraint

View File

@ -45,6 +45,7 @@ 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_SITE_BASE_URL ??= viteEnv.VITE_KC_SITE_BASE_URL
process.env.VITE_KC_SITE_APP_URL ??= viteEnv.VITE_KC_SITE_APP_URL
process.env.VITE_KC_KCL_SAMPLES_REF ??= viteEnv.VITE_KC_KCL_SAMPLES_REF
process.env.VITE_KC_SKIP_AUTH ??= viteEnv.VITE_KC_SKIP_AUTH
process.env.VITE_KC_CONNECTION_TIMEOUT_MS ??=
viteEnv.VITE_KC_CONNECTION_TIMEOUT_MS

View File

@ -187,6 +187,7 @@ contextBridge.exposeInMainWorld('electron', {
'VITE_KC_API_BASE_URL',
'VITE_KC_SITE_BASE_URL',
'VITE_KC_SITE_APP_URL',
'VITE_KC_KCL_SAMPLES_REF',
'VITE_KC_SKIP_AUTH',
'VITE_KC_CONNECTION_TIMEOUT_MS',
'VITE_KC_DEV_TOKEN',

View File

@ -1779,7 +1779,7 @@ dependencies = [
[[package]]
name = "kcl-test-server"
version = "0.1.20"
version = "0.1.21"
dependencies = [
"anyhow",
"hyper 0.14.32",

View File

@ -33,6 +33,9 @@ tokio = { version = "1.41.1", features = ["rt-multi-thread", "macros", "time"] }
twenty-twenty = "0.8"
uuid = { version = "1.11.0", features = ["v4", "js", "serde"] }
[features]
dhat-heap = ["kcl-lib/dhat-heap"]
[target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1.7"
futures = "0.3.31"

View File

@ -36,5 +36,11 @@ run-sim-test test_name:
{{cita}} -p kcl-lib -- simulation_tests::{{test_name}}::unparse
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:
export RUST_BRACKTRACE="full" && cargo nextest run --workspace --test-threads=1

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-test-server"
description = "A test server for KCL"
version = "0.1.20"
version = "0.1.21"
edition = "2021"
license = "MIT"

View File

@ -944,13 +944,7 @@ mod tests {
let snippet = pattern_fn.to_autocomplete_snippet().unwrap();
assert_eq!(
snippet,
r#"patternCircular3d({
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:%})${}"#
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})${}"#
);
}
@ -1006,11 +1000,7 @@ mod tests {
let snippet = pattern_fn.to_autocomplete_snippet().unwrap();
assert_eq!(
snippet,
r#"patternLinear2d({
instances = ${0:10},
distance = ${1:3.14},
axis = [${2:3.14}, ${3:3.14}],
}, ${4:%})${}"#
r#"patternLinear2d(${0:%}, instances = ${1:10}, distance = ${2:3.14}, axis = [${3:3.14}, ${4:3.14}])${}"#
);
}

View File

@ -1,8 +1,10 @@
//! Data on available annotations.
use super::kcl_value::{UnitAngle, UnitLen};
use kittycad_modeling_cmds::coord::{System, KITTYCAD, OPENGL, VULKAN};
use crate::{
errors::KclErrorDetails,
execution::kcl_value::{UnitAngle, UnitLen},
parsing::ast::types::{Expr, Node, NonCodeValue, ObjectProperty},
KclError, SourceRange,
};
@ -11,6 +13,12 @@ pub(crate) const SETTINGS: &str = "settings";
pub(crate) const SETTINGS_UNIT_LENGTH: &str = "defaultLengthUnit";
pub(crate) const SETTINGS_UNIT_ANGLE: &str = "defaultAngleUnit";
pub(super) const NO_PRELUDE: &str = "no_prelude";
pub(super) const IMPORT_FORMAT: &str = "format";
pub(super) const IMPORT_FORMAT_VALUES: [&str; 9] = ["fbx", "gltf", "glb", "obj", "ply", "sldprt", "stp", "step", "stl"];
pub(super) const IMPORT_COORDS: &str = "coords";
pub(super) const IMPORT_COORDS_VALUES: [(&str, &System); 3] =
[("zoo", KITTYCAD), ("opengl", OPENGL), ("vulkan", VULKAN)];
pub(super) const IMPORT_LENGTH_UNIT: &str = "lengthUnit";
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(super) enum AnnotationScope {
@ -36,6 +44,18 @@ pub(super) fn expect_properties<'a>(
}
}
pub(super) fn unnamed_properties<'a>(
annotations: impl Iterator<Item = &'a NonCodeValue>,
) -> Option<&'a [Node<ObjectProperty>]> {
for annotation in annotations {
if let NonCodeValue::Annotation { name: None, properties } = annotation {
return properties.as_deref();
}
}
None
}
pub(super) fn expect_ident(expr: &Expr) -> Result<&str, KclError> {
match expr {
Expr::Identifier(id) => Ok(&id.name),
@ -57,7 +77,7 @@ impl UnitLen {
"yd" => Ok(UnitLen::Yards),
value => Err(KclError::Semantic(KclErrorDetails {
message: format!(
"Unexpected settings value: `{value}`; expected one of `mm`, `cm`, `m`, `inch`, `ft`, `yd`"
"Unexpected value for length units: `{value}`; expected one of `mm`, `cm`, `m`, `inch`, `ft`, `yd`"
),
source_ranges: vec![source_range],
})),
@ -71,7 +91,7 @@ impl UnitAngle {
"deg" => Ok(UnitAngle::Degrees),
"rad" => Ok(UnitAngle::Radians),
value => Err(KclError::Semantic(KclErrorDetails {
message: format!("Unexpected settings value: `{value}`; expected one of `deg`, `rad`"),
message: format!("Unexpected value for angle units: `{value}`; expected one of `deg`, `rad`"),
source_ranges: vec![source_range],
})),
}

View File

@ -462,98 +462,4 @@ impl ArtifactGraph {
Ok(())
}
/// Output the Mermaid mind map for the artifact graph.
///
/// This is sometimes easier to read than the flowchart. But since it
/// does a depth-first traversal starting from all the planes, it may
/// not include all the artifacts. It also doesn't show edge direction.
/// It's useful for a high-level overview of the graph, not for
/// including all the information.
pub(crate) fn to_mermaid_mind_map(&self) -> Result<String, std::fmt::Error> {
let mut output = String::new();
output.push_str("```mermaid\n");
output.push_str("mindmap\n");
output.push_str(" root\n");
let mut ids_seen: fnv::FnvHashSet<ArtifactId> = Default::default();
for (_, artifact) in &self.map {
// Only the planes are roots.
let Artifact::Plane(_) = artifact else {
continue;
};
self.mind_map_artifact(&mut output, &mut ids_seen, artifact, " ")?;
}
output.push_str("```\n");
Ok(output)
}
fn mind_map_artifact<W: Write>(
&self,
output: &mut W,
ids_seen: &mut fnv::FnvHashSet<ArtifactId>,
artifact: &Artifact,
prefix: &str,
) -> std::fmt::Result {
match artifact {
Artifact::Plane(_plane) => {
ids_seen.clear();
writeln!(output, "{prefix}Plane")?;
}
Artifact::Path(_path) => {
writeln!(output, "{prefix}Path")?;
}
Artifact::Segment(_segment) => {
writeln!(output, "{prefix}Segment")?;
}
Artifact::Solid2d(_solid2d) => {
writeln!(output, "{prefix}Solid2d")?;
}
Artifact::StartSketchOnFace { .. } => {
writeln!(output, "{prefix}StartSketchOnFace")?;
}
Artifact::StartSketchOnPlane { .. } => {
writeln!(output, "{prefix}StartSketchOnPlane")?;
}
Artifact::Sweep(sweep) => {
writeln!(output, "{prefix}Sweep {:?}", sweep.sub_type)?;
}
Artifact::Wall(_wall) => {
writeln!(output, "{prefix}Wall")?;
}
Artifact::Cap(cap) => {
writeln!(output, "{prefix}Cap {:?}", cap.sub_type)?;
}
Artifact::SweepEdge(sweep_edge) => {
writeln!(output, "{prefix}SweepEdge {:?}", sweep_edge.sub_type,)?;
}
Artifact::EdgeCut(edge_cut) => {
writeln!(output, "{prefix}EdgeCut {:?}", edge_cut.sub_type)?;
}
Artifact::EdgeCutEdge(_edge_cut_edge) => {
writeln!(output, "{prefix}EdgeCutEdge")?;
}
Artifact::Helix(_) => {
writeln!(output, "{prefix}Helix")?;
}
}
if ids_seen.contains(&artifact.id()) {
return Ok(());
}
ids_seen.insert(artifact.id());
for child_id in artifact.child_ids() {
let Some(child_artifact) = self.map.get(&child_id) else {
continue;
};
self.mind_map_artifact(output, ids_seen, child_artifact, &format!("{} ", prefix))?;
}
Ok(())
}
}

View File

@ -11,28 +11,46 @@ use crate::{
walk::Node as WalkNode,
};
use super::ProgramMemory;
lazy_static::lazy_static! {
/// 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.
pub(super) async fn read_old_ast_memory() -> Option<OldAstState> {
let old_ast = OLD_AST_MEMORY.read().await;
pub(super) async fn read_old_ast() -> Option<OldAstState> {
let old_ast = OLD_AST.read().await;
old_ast.clone()
}
pub(super) async fn write_old_ast_memory(old_state: OldAstState) {
let mut old_ast = OLD_AST_MEMORY.write().await;
pub(super) async fn write_old_ast(old_state: OldAstState) {
let mut old_ast = OLD_AST.write().await;
*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() {
let mut old_ast = OLD_AST_MEMORY.write().await;
// Set the cache to None.
let mut old_ast = OLD_AST.write().await;
*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.
#[derive(Debug, Clone)]
pub struct CacheInformation<'a> {

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