Compare commits

...

24 Commits

Author SHA1 Message Date
fecf5c2ee7 Merge branch 'main' into pierremtb/issue5101-Allow-feature-tree-selection-for-point-and-click-Sweep 2025-01-23 16:44:09 +01:00
8ef31a0be1 Refactor: decouple command palette actor from React (#5108)
* Convert commandBarMachine to standalone actor

* Switch all uses of CommandBarProvider pattern to use actor and selector snapshots directly
2025-01-23 10:25:21 -05:00
3adb42b5f2 Supress stdio logs on e2e tests in CI (#5132)
* WIP: pw log error only

* Force tests to run on branch

* Remove all page.on('console', console.log)

* Remove context.console too

* Add --quiet flag

* Revert useless changes

* Supress stdio logs on e2e tests in CI
2025-01-23 16:13:49 +01:00
20016b101e Fix: Properly setting selection range when KCL editor is not mounted. (#4960)
* fix: fixed selection range issue when doing a constraint when the KCL editor is closed

* fix: linter and tsc errors

* fix: trying to reuse logic instead?

* fix: removed console log
2025-01-23 09:45:45 -05:00
8d9dbf36c3 Bump vite from 5.4.6 to 5.4.12 (#5129)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.4.6 to 5.4.12.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.4.12/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.4.12/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-22 20:58:53 -05:00
440704ed9f Remove extra margin on some code editor menu items (#5094)
* Extra padding on 'Load a sample model' menu item
Fixes #5047

* Update src/components/ModelingSidebar/ModelingPanes/KclEditorMenu.module.css

Co-authored-by: Frank Noirot <frank@zoo.dev>

---------

Co-authored-by: Frank Noirot <frank@zoo.dev>
2025-01-22 16:57:27 +01:00
2261217a5d Rename debug pane label for artifact graph (#5092)
* Rename debug pane label for artifact graph

* Rename component
2025-01-22 15:37:51 +00:00
10da986649 Add dry-run validation for Sweep (#5097)
* Add dry-run validation for Sweep
Fixes #5095

* Add sweep test failing validation

* Make naming more consistent with engine

* Fix tests after big rename

* Fix tsc after main merge
2025-01-22 15:59:47 +01:00
10789d9c3c set scene units based on a module's default units (#5127)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-01-22 15:23:55 +13:00
67cc4f5835 Tweaks to clarify tooltips from tool dropdown menus (#5123)
* Separate content from ToolbarItemTooltip, make simple and "rich" versions

* Add support for dropdown-arrow-only tooltip

* Add toolbar-wide hover timeouts and clears to switch between simple and rich tooltips

* Fix the dropdown arrow button hover styling now that they're separate

* Add missing doc links to rich toolbar tooltips

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* Re-run CI after snapshots

* fix codespell

* fmt

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-21 18:32:56 -05:00
2692f2b73a Add units to geometry structs (#5075)
* Make all geometry KclValue variants into struct variants

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

* Add units to geometry types

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

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-01-21 20:42:09 +00:00
965cb18059 Parse units on numeric literals and keep them in the AST (#5061)
* Code changes

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

* test changes

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

* Frontend changes

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

* Refactor asNum

Co-authored-by: Jonathan Tran <jonnytran@gmail.com>

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
2025-01-22 08:29:30 +13:00
a022b8ef6c Fix suggestion for updating function decl syntax for anon functions (#5088)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-01-22 07:10:07 +13:00
4d24bf7c94 Add API Call ID log for debugging (#5107) 2025-01-20 19:49:02 +00:00
d531728675 Fix merge issue 2025-01-17 14:52:10 -05:00
1d78fc15ac Merge branch 'pierremtb/issue5095-Add-dry-run-validation-for-Sweep' into pierremtb/issue5101-Allow-feature-tree-selection-for-point-and-click-Sweep 2025-01-17 14:51:13 -05:00
c32aebc8ad Merge branch 'main' into pierremtb/issue5095-Add-dry-run-validation-for-Sweep 2025-01-17 14:50:56 -05:00
997ebce3eb Merge branch 'pierremtb/issue5095-Add-dry-run-validation-for-Sweep' into pierremtb/issue5101-Allow-feature-tree-selection-for-point-and-click-Sweep 2025-01-17 14:47:16 -05:00
1eaf371b44 Merge branch 'main' into pierremtb/issue5095-Add-dry-run-validation-for-Sweep 2025-01-17 14:46:26 -05:00
54da18d8ab WIP: Allow feature tree selection for point-and-click Sweep
Relates to #5101
2025-01-17 14:03:51 -05:00
2fe5ef7034 Fix tests after big rename 2025-01-17 13:22:58 -05:00
16b5eeadb1 Make naming more consistent with engine 2025-01-17 12:25:07 -05:00
7be4001839 Add sweep test failing validation 2025-01-17 12:08:51 -05:00
ffb2559787 Add dry-run validation for Sweep
Fixes #5095
2025-01-17 12:02:50 -05:00
290 changed files with 99924 additions and 70241 deletions

File diff suppressed because it is too large Load Diff

28
docs/kcl/types/Face.md Normal file
View File

@ -0,0 +1,28 @@
---
title: "Face"
excerpt: "A face."
layout: manual
---
A face.
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `id` |`string`| The id of the face. | No |
| `value` |`string`| The tag of the face. | No |
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the faces X axis be? | No |
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the faces Y axis be? | No |
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
| `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A face. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |

View File

@ -20,6 +20,7 @@ A helix.
| `revolutions` |`number`| Number of revolutions. | No | | `revolutions` |`number`| Number of revolutions. | No |
| `angleStart` |`number`| Start angle (in degrees). | No | | `angleStart` |`number`| Start angle (in degrees). | No |
| `ccw` |`boolean`| Is the helix rotation counter clockwise? | No | | `ccw` |`boolean`| Is the helix rotation counter clockwise? | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A helix. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No | | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |

View File

@ -20,6 +20,7 @@ A helix.
| `revolutions` |`number`| Number of revolutions. | No | | `revolutions` |`number`| Number of revolutions. | No |
| `angleStart` |`number`| Start angle (in degrees). | No | | `angleStart` |`number`| Start angle (in degrees). | No |
| `ccw` |`boolean`| Is the helix rotation counter clockwise? | No | | `ccw` |`boolean`| Is the helix rotation counter clockwise? | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A helix. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No | | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |

View File

@ -168,7 +168,6 @@ Any KCL value.
---- ----
A plane.
**Type:** `object` **Type:** `object`
@ -181,17 +180,10 @@ A plane.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: [`Plane`](/docs/kcl/types/Plane)| | No | | `type` |enum: [`Plane`](/docs/kcl/types/Plane)| | No |
| `id` |`string`| The id of the plane. | No | | `value` |[`Plane`](/docs/kcl/types/Plane)| Any KCL value. | No |
| `value` |[`PlaneType`](/docs/kcl/types/PlaneType)| Any KCL value. | No |
| `origin` |[`Point3d`](/docs/kcl/types/Point3d)| Origin of the plane. | No |
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the planes X axis be? | No |
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the planes Y axis be? | No |
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
---- ----
A face.
**Type:** `object` **Type:** `object`
@ -203,14 +195,8 @@ A face.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `Face`| | No | | `type` |enum: [`Face`](/docs/kcl/types/Face)| | No |
| `id` |`string`| The id of the face. | No | | `value` |[`Face`](/docs/kcl/types/Face)| Any KCL value. | No |
| `value` |`string`| The tag of the face. | No |
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the faces X axis be? | No |
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the faces Y axis be? | No |
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
| `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
---- ----
@ -246,7 +232,6 @@ A face.
---- ----
An solid is a collection of extrude surfaces.
**Type:** `object` **Type:** `object`
@ -259,14 +244,7 @@ An solid is a collection of extrude surfaces.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: [`Solid`](/docs/kcl/types/Solid)| | No | | `type` |enum: [`Solid`](/docs/kcl/types/Solid)| | No |
| `id` |`string`| The id of the solid. | No | | `value` |[`Solid`](/docs/kcl/types/Solid)| Any KCL value. | No |
| `value` |`[` [`ExtrudeSurface`](/docs/kcl/types/ExtrudeSurface) `]`| The extrude surfaces. | No |
| `sketch` |[`Sketch`](/docs/kcl/types/Sketch)| The sketch. | No |
| `height` |`number`| The height of the solid. | No |
| `startCapId` |`string`| The id of the extrusion start cap | No |
| `endCapId` |`string`| The id of the extrusion end cap | No |
| `edgeCuts` |`[` [`EdgeCut`](/docs/kcl/types/EdgeCut) `]`| Chamfers or fillets on this solid. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |
---- ----
@ -286,7 +264,6 @@ An solid is a collection of extrude surfaces.
---- ----
A helix.
**Type:** `object` **Type:** `object`
@ -299,11 +276,7 @@ A helix.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: [`Helix`](/docs/kcl/types/Helix)| | No | | `type` |enum: [`Helix`](/docs/kcl/types/Helix)| | No |
| `value` |`string`| The id of the helix. | No | | `value` |[`Helix`](/docs/kcl/types/Helix)| Any KCL value. | No |
| `revolutions` |`number`| Number of revolutions. | No |
| `angleStart` |`number`| Start angle (in degrees). | No |
| `ccw` |`boolean`| Is the helix rotation counter clockwise? | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
---- ----

View File

@ -22,6 +22,7 @@ A plane.
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the planes X axis be? | No | | `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the planes X axis be? | No |
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the planes Y axis be? | No | | `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the planes Y axis be? | No |
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No | | `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A plane. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No | | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |

View File

@ -21,6 +21,7 @@ A sketch is a collection of paths.
| `on` |[`SketchSurface`](/docs/kcl/types/SketchSurface)| What the sketch is on (can be a plane or a face). | No | | `on` |[`SketchSurface`](/docs/kcl/types/SketchSurface)| What the sketch is on (can be a plane or a face). | No |
| `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No | | `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No |
| `tags` |`object`| Tag identifiers that have been declared in this sketch. | No | | `tags` |`object`| Tag identifiers that have been declared in this sketch. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A sketch is a collection of paths. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No | | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |

View File

@ -30,6 +30,7 @@ A sketch is a collection of paths.
| `on` |[`SketchSurface`](/docs/kcl/types/SketchSurface)| What the sketch is on (can be a plane or a face). | No | | `on` |[`SketchSurface`](/docs/kcl/types/SketchSurface)| What the sketch is on (can be a plane or a face). | No |
| `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No | | `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No |
| `tags` |`object`| Tag identifiers that have been declared in this sketch. | No | | `tags` |`object`| Tag identifiers that have been declared in this sketch. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A sketch or a group of sketches. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No | | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |

View File

@ -31,6 +31,7 @@ A plane.
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the planes X axis be? | No | | `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the planes X axis be? | No |
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the planes Y axis be? | No | | `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the planes Y axis be? | No |
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No | | `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A sketch type. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No | | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
@ -54,6 +55,7 @@ A face.
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the faces Y axis be? | No | | `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the faces Y axis be? | No |
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No | | `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
| `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No | | `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A sketch type. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No | | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |

View File

@ -23,6 +23,7 @@ An solid is a collection of extrude surfaces.
| `startCapId` |`string`| The id of the extrusion start cap | No | | `startCapId` |`string`| The id of the extrusion start cap | No |
| `endCapId` |`string`| The id of the extrusion end cap | No | | `endCapId` |`string`| The id of the extrusion end cap | No |
| `edgeCuts` |`[` [`EdgeCut`](/docs/kcl/types/EdgeCut) `]`| Chamfers or fillets on this solid. | No | | `edgeCuts` |`[` [`EdgeCut`](/docs/kcl/types/EdgeCut) `]`| Chamfers or fillets on this solid. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| An solid is a collection of extrude surfaces. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No | | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |

View File

@ -32,6 +32,7 @@ An solid is a collection of extrude surfaces.
| `startCapId` |`string`| The id of the extrusion start cap | No | | `startCapId` |`string`| The id of the extrusion start cap | No |
| `endCapId` |`string`| The id of the extrusion end cap | No | | `endCapId` |`string`| The id of the extrusion end cap | No |
| `edgeCuts` |`[` [`EdgeCut`](/docs/kcl/types/EdgeCut) `]`| Chamfers or fillets on this solid. | No | | `edgeCuts` |`[` [`EdgeCut`](/docs/kcl/types/EdgeCut) `]`| Chamfers or fillets on this solid. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A solid or a group of solids. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No | | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |

107
docs/kcl/types/UnitLen.md Normal file
View File

@ -0,0 +1,107 @@
---
title: "UnitLen"
excerpt: ""
layout: manual
---
**This schema accepts exactly one of the following:**
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Mm`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Cm`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `M`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Inches`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Feet`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Yards`| | No |
----

View File

@ -38,14 +38,14 @@ test.describe('Debug pane', () => {
// Set the code in the code editor. // Set the code in the code editor.
await u.codeLocator.click() await u.codeLocator.click()
await page.keyboard.type(code, { delay: 0 }) await page.keyboard.type(code, { delay: 0 })
// Scroll to the feature tree. // Scroll to the artifact graph.
await tree.scrollIntoViewIfNeeded() await tree.scrollIntoViewIfNeeded()
// Expand the feature tree. // Expand the artifact graph.
await tree.getByText('Feature Tree').click() await tree.getByText('Artifact Graph').click()
// Just expanded the details, making the element taller, so scroll again. // Just expanded the details, making the element taller, so scroll again.
await tree.getByText('Plane').first().scrollIntoViewIfNeeded() await tree.getByText('Plane').first().scrollIntoViewIfNeeded()
}) })
// Extract the artifact IDs from the debug feature tree. // Extract the artifact IDs from the debug artifact graph.
const initialSegmentIds = await segment.innerText({ timeout: 5_000 }) const initialSegmentIds = await segment.innerText({ timeout: 5_000 })
// The artifact ID should include a UUID. // The artifact ID should include a UUID.
expect(initialSegmentIds).toMatch( expect(initialSegmentIds).toMatch(

View File

@ -963,37 +963,31 @@ sketch002 = startSketchOn('XZ')
await toolbar.sweepButton.click() await toolbar.sweepButton.click()
await cmdBar.expectState({ await cmdBar.expectState({
commandName: 'Sweep', commandName: 'Sweep',
currentArgKey: 'profile', currentArgKey: 'target',
currentArgValue: '', currentArgValue: '',
headerArguments: { headerArguments: {
Path: '', Target: '',
Profile: '', Trajectory: '',
}, },
highlightedHeaderArg: 'profile', highlightedHeaderArg: 'target',
stage: 'arguments', stage: 'arguments',
}) })
await clickOnSketch1() await clickOnSketch1()
await cmdBar.expectState({ await cmdBar.expectState({
commandName: 'Sweep', commandName: 'Sweep',
currentArgKey: 'path', currentArgKey: 'trajectory',
currentArgValue: '', currentArgValue: '',
headerArguments: { headerArguments: {
Path: '', Target: '1 face',
Profile: '1 face', Trajectory: '',
}, },
highlightedHeaderArg: 'path', highlightedHeaderArg: 'trajectory',
stage: 'arguments', stage: 'arguments',
}) })
await clickOnSketch2() await clickOnSketch2()
await cmdBar.expectState({ await page.waitForTimeout(500)
commandName: 'Sweep',
headerArguments: {
Path: '1 face',
Profile: '1 face',
},
stage: 'review',
})
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
await page.waitForTimeout(500)
}) })
await test.step(`Confirm code is added to the editor, scene has changed`, async () => { await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
@ -1020,6 +1014,75 @@ sketch002 = startSketchOn('XZ')
}) })
}) })
test(`Sweep point-and-click failing validation`, async ({
context,
page,
homePage,
scene,
toolbar,
cmdBar,
}) => {
const initialCode = `sketch001 = startSketchOn('YZ')
|> circle({
center = [0, 0],
radius = 500
}, %)
sketch002 = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> xLine(-500, %)
|> lineTo([-2000, 500], %)
`
await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode)
}, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene()
await scene.waitForExecutionDone()
// One dumb hardcoded screen pixel value
const testPoint = { x: 700, y: 250 }
const [clickOnSketch1] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
const [clickOnSketch2] = scene.makeMouseHelpers(testPoint.x - 50, testPoint.y)
await test.step(`Look for sketch001`, async () => {
await toolbar.closePane('code')
await scene.expectPixelColor([53, 53, 53], testPoint, 15)
})
await test.step(`Go through the command bar flow and fail validation with a toast`, async () => {
await toolbar.sweepButton.click()
await cmdBar.expectState({
commandName: 'Sweep',
currentArgKey: 'target',
currentArgValue: '',
headerArguments: {
Target: '',
Trajectory: '',
},
highlightedHeaderArg: 'target',
stage: 'arguments',
})
await clickOnSketch1()
await cmdBar.expectState({
commandName: 'Sweep',
currentArgKey: 'trajectory',
currentArgValue: '',
headerArguments: {
Target: '1 face',
Trajectory: '',
},
highlightedHeaderArg: 'trajectory',
stage: 'arguments',
})
await clickOnSketch2()
await page.waitForTimeout(500)
await cmdBar.progressCmdBar()
await expect(
page.getByText('Unable to sweep with the provided selection')
).toBeVisible()
})
})
test(`Fillet point-and-click`, async ({ test(`Fillet point-and-click`, async ({
context, context,
page, page,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 60 KiB

View File

@ -113,9 +113,9 @@
"test:unit": "vitest run --mode development --exclude **/kclSamples.test.ts", "test:unit": "vitest run --mode development --exclude **/kclSamples.test.ts",
"test:unit:kcl-samples": "vitest run --mode development ./src/lang/kclSamples.test.ts", "test:unit:kcl-samples": "vitest run --mode development ./src/lang/kclSamples.test.ts",
"test:playwright:electron": "playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot'", "test:playwright:electron": "playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot'",
"test:playwright:electron:windows": "playwright test --config=playwright.electron.config.ts --grep-invert=\"@skipWin|@snapshot\"", "test:playwright:electron:windows": "playwright test --config=playwright.electron.config.ts --grep-invert=\"@skipWin|@snapshot\" --quiet",
"test:playwright:electron:macos": "playwright test --config=playwright.electron.config.ts --grep-invert='@skipMacos|@snapshot'", "test:playwright:electron:macos": "playwright test --config=playwright.electron.config.ts --grep-invert='@skipMacos|@snapshot' --quiet",
"test:playwright:electron:ubuntu": "playwright test --config=playwright.electron.config.ts --grep-invert='@skipLinux|@snapshot'", "test:playwright:electron:ubuntu": "playwright test --config=playwright.electron.config.ts --grep-invert='@skipLinux|@snapshot' --quiet",
"test:playwright:electron:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot'", "test:playwright:electron:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot'",
"test:playwright:electron:windows:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert=\"@skipWin|@snapshot\"", "test:playwright:electron:windows:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert=\"@skipWin|@snapshot\"",
"test:playwright:electron:macos:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipMacos|@snapshot'", "test:playwright:electron:macos:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipMacos|@snapshot'",
@ -201,7 +201,7 @@
"ts-node": "^10.0.0", "ts-node": "^10.0.0",
"typescript": "^5.7.3", "typescript": "^5.7.3",
"typescript-eslint": "^8.19.1", "typescript-eslint": "^8.19.1",
"vite": "^5.4.6", "vite": "^5.4.12",
"vite-plugin-package-version": "^1.1.0", "vite-plugin-package-version": "^1.1.0",
"vite-tsconfig-paths": "^4.3.2", "vite-tsconfig-paths": "^4.3.2",
"vitest": "^1.6.0", "vitest": "^1.6.0",

View File

@ -31,7 +31,6 @@ import {
settingsLoader, settingsLoader,
telemetryLoader, telemetryLoader,
} from 'lib/routeLoaders' } from 'lib/routeLoaders'
import { CommandBarProvider } from 'components/CommandBar/CommandBarProvider'
import SettingsAuthProvider from 'components/SettingsAuthProvider' import SettingsAuthProvider from 'components/SettingsAuthProvider'
import LspProvider from 'components/LspProvider' import LspProvider from 'components/LspProvider'
import { KclContextProvider } from 'lang/KclProvider' import { KclContextProvider } from 'lang/KclProvider'
@ -58,7 +57,6 @@ const router = createRouter([
/* Make sure auth is the outermost provider or else we will have /* Make sure auth is the outermost provider or else we will have
* inefficient re-renders, use the react profiler to see. */ * inefficient re-renders, use the react profiler to see. */
element: ( element: (
<CommandBarProvider>
<RouteProvider> <RouteProvider>
<SettingsAuthProvider> <SettingsAuthProvider>
<LspProvider> <LspProvider>
@ -74,7 +72,6 @@ const router = createRouter([
</LspProvider> </LspProvider>
</SettingsAuthProvider> </SettingsAuthProvider>
</RouteProvider> </RouteProvider>
</CommandBarProvider>
), ),
errorElement: <ErrorPage />, errorElement: <ErrorPage />,
children: [ children: [

View File

@ -1,8 +1,7 @@
import { useRef, useMemo, memo } from 'react' import { useRef, useMemo, memo, useCallback, useState } from 'react'
import { isCursorInSketchCommandRange } from 'lang/util' import { isCursorInSketchCommandRange } from 'lang/util'
import { engineCommandManager, kclManager } from 'lib/singletons' import { engineCommandManager, kclManager } from 'lib/singletons'
import { useModelingContext } from 'hooks/useModelingContext' import { useModelingContext } from 'hooks/useModelingContext'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { useNetworkContext } from 'hooks/useNetworkContext' import { useNetworkContext } from 'hooks/useNetworkContext'
import { NetworkHealthState } from 'hooks/useNetworkStatus' import { NetworkHealthState } from 'hooks/useNetworkStatus'
import { ActionButton } from 'components/ActionButton' import { ActionButton } from 'components/ActionButton'
@ -22,20 +21,19 @@ import {
} from 'lib/toolbar' } from 'lib/toolbar'
import { isDesktop } from 'lib/isDesktop' import { isDesktop } from 'lib/isDesktop'
import { openExternalBrowserIfDesktop } from 'lib/openWindow' import { openExternalBrowserIfDesktop } from 'lib/openWindow'
import { commandBarActor } from 'machines/commandBarMachine'
export function Toolbar({ export function Toolbar({
className = '', className = '',
...props ...props
}: React.HTMLAttributes<HTMLElement>) { }: React.HTMLAttributes<HTMLElement>) {
const { state, send, context } = useModelingContext() const { state, send, context } = useModelingContext()
const { commandBarSend } = useCommandsContext()
const iconClassName = const iconClassName =
'group-disabled:text-chalkboard-50 !text-inherit dark:group-enabled:group-hover:!text-inherit' 'group-disabled:text-chalkboard-50 !text-inherit dark:group-enabled:group-hover:!text-inherit'
const bgClassName = '!bg-transparent' const bgClassName = '!bg-transparent'
const buttonBgClassName = const buttonBgClassName =
'bg-chalkboard-transparent dark:bg-transparent disabled:bg-transparent dark:disabled:bg-transparent enabled:hover:bg-chalkboard-10 dark:enabled:hover:bg-chalkboard-100 pressed:!bg-primary pressed:enabled:hover:!text-chalkboard-10' 'bg-chalkboard-transparent dark:bg-transparent disabled:bg-transparent dark:disabled:bg-transparent enabled:hover:bg-chalkboard-10 dark:enabled:hover:bg-chalkboard-100 pressed:!bg-primary pressed:enabled:hover:!text-chalkboard-10'
const buttonBorderClassName = const buttonBorderClassName = '!border-transparent'
'!border-transparent hover:!border-chalkboard-20 dark:enabled:hover:!border-primary pressed:!border-primary ui-open:!border-primary'
const sketchPathId = useMemo(() => { const sketchPathId = useMemo(() => {
if (!isSingleCursorInPipe(context.selectionRanges, kclManager.ast)) if (!isSingleCursorInPipe(context.selectionRanges, kclManager.ast))
@ -50,6 +48,7 @@ export function Toolbar({
const { overallState } = useNetworkContext() const { overallState } = useNetworkContext()
const { isExecuting } = useKclContext() const { isExecuting } = useKclContext()
const { isStreamReady } = useAppState() const { isStreamReady } = useAppState()
const [showRichContent, setShowRichContent] = useState(false)
const disableAllButtons = const disableAllButtons =
(overallState !== NetworkHealthState.Ok && (overallState !== NetworkHealthState.Ok &&
@ -71,12 +70,45 @@ export function Toolbar({
() => ({ () => ({
modelingState: state, modelingState: state,
modelingSend: send, modelingSend: send,
commandBarSend,
sketchPathId, sketchPathId,
}), }),
[state, send, commandBarSend, sketchPathId] [state, send, commandBarActor.send, sketchPathId]
) )
const tooltipContentClassName = !showRichContent
? ''
: '!text-left text-wrap !text-xs !p-0 !pb-2 flex gap-2 !max-w-none !w-72 flex-col items-stretch'
const richContentTimeout = useRef<number | null>(null)
const richContentClearTimeout = useRef<number | null>(null)
// On mouse enter, show rich content after a 1s delay
const handleMouseEnter = useCallback(() => {
// Cancel the clear timeout if it's already set
if (richContentClearTimeout.current) {
clearTimeout(richContentClearTimeout.current)
}
// Start our own timeout to show the rich content
richContentTimeout.current = window.setTimeout(() => {
setShowRichContent(true)
if (richContentClearTimeout.current) {
clearTimeout(richContentClearTimeout.current)
}
}, 1000)
}, [setShowRichContent])
// On mouse leave, clear the timeout and hide rich content
const handleMouseLeave = useCallback(() => {
// Clear the timeout to show rich content
if (richContentTimeout.current) {
clearTimeout(richContentTimeout.current)
}
// Start a timeout to hide the rich content
richContentClearTimeout.current = window.setTimeout(() => {
setShowRichContent(false)
if (richContentClearTimeout.current) {
clearTimeout(richContentClearTimeout.current)
}
}, 500)
}, [setShowRichContent])
/** /**
* Resolve all the callbacks and values for the current mode, * Resolve all the callbacks and values for the current mode,
* so we don't need to worry about the other modes * so we don't need to worry about the other modes
@ -173,6 +205,12 @@ export function Toolbar({
itemConfig.disabled === true, itemConfig.disabled === true,
status: itemConfig.status, status: itemConfig.status,
}))} }))}
>
<div
className="contents"
// Mouse events do not fire on disabled buttons
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
> >
<ActionButton <ActionButton
Element="button" Element="button"
@ -206,12 +244,26 @@ export function Toolbar({
> >
{maybeIconConfig[0].title} {maybeIconConfig[0].title}
</span> </span>
</ActionButton>
<ToolbarItemTooltip <ToolbarItemTooltip
itemConfig={maybeIconConfig[0]} itemConfig={maybeIconConfig[0]}
configCallbackProps={configCallbackProps} configCallbackProps={configCallbackProps}
className="ui-open:!hidden" wrapperClassName="ui-open:!hidden"
contentClassName={tooltipContentClassName}
>
{showRichContent ? (
<ToolbarItemTooltipRichContent
itemConfig={maybeIconConfig[0]}
/> />
) : (
<ToolbarItemTooltipShortContent
status={maybeIconConfig[0].status}
title={maybeIconConfig[0].title}
hotkey={maybeIconConfig[0].hotkey}
/>
)}
</ToolbarItemTooltip>
</ActionButton>
</div>
</ActionButtonDropdown> </ActionButtonDropdown>
) )
} }
@ -219,7 +271,13 @@ export function Toolbar({
// A single button // A single button
return ( return (
<div className="relative" key={itemConfig.id}> <div
className="relative"
key={itemConfig.id}
// Mouse events do not fire on disabled buttons
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<ActionButton <ActionButton
Element="button" Element="button"
key={itemConfig.id} key={itemConfig.id}
@ -256,7 +314,18 @@ export function Toolbar({
<ToolbarItemTooltip <ToolbarItemTooltip
itemConfig={itemConfig} itemConfig={itemConfig}
configCallbackProps={configCallbackProps} configCallbackProps={configCallbackProps}
contentClassName={tooltipContentClassName}
>
{showRichContent ? (
<ToolbarItemTooltipRichContent itemConfig={itemConfig} />
) : (
<ToolbarItemTooltipShortContent
status={itemConfig.status}
title={itemConfig.title}
hotkey={itemConfig.hotkey}
/> />
)}
</ToolbarItemTooltip>
</div> </div>
) )
})} })}
@ -270,6 +339,12 @@ export function Toolbar({
) )
} }
interface ToolbarItemContentsProps extends React.PropsWithChildren {
itemConfig: ToolbarItemResolved
configCallbackProps: ToolbarItemCallbackProps
wrapperClassName?: string
contentClassName?: string
}
/** /**
* The single button and dropdown button share content, so we extract it here * The single button and dropdown button share content, so we extract it here
* It contains a tooltip with the title, description, and links * It contains a tooltip with the title, description, and links
@ -278,14 +353,10 @@ export function Toolbar({
const ToolbarItemTooltip = memo(function ToolbarItemContents({ const ToolbarItemTooltip = memo(function ToolbarItemContents({
itemConfig, itemConfig,
configCallbackProps, configCallbackProps,
className, wrapperClassName = '',
}: { contentClassName = '',
itemConfig: ToolbarItemResolved children,
configCallbackProps: ToolbarItemCallbackProps }: ToolbarItemContentsProps) {
className?: string
}) {
const { state } = useModelingContext()
useHotkeys( useHotkeys(
itemConfig.hotkey || '', itemConfig.hotkey || '',
() => { () => {
@ -310,10 +381,48 @@ const ToolbarItemTooltip = memo(function ToolbarItemContents({
} }
hoverOnly hoverOnly
position="bottom" position="bottom"
wrapperClassName={'!p-4 !pointer-events-auto ' + className} wrapperClassName={'!p-4 !pointer-events-auto ' + wrapperClassName}
contentClassName="!text-left text-wrap !text-xs !p-0 !pb-2 flex gap-2 !max-w-none !w-72 flex-col items-stretch" contentClassName={contentClassName}
delay={0}
> >
{children}
</Tooltip>
)
})
const ToolbarItemTooltipShortContent = ({
status,
title,
hotkey,
}: {
status: string
title: string
hotkey?: string | string[]
}) => (
<span
className={`text-sm ${
status !== 'available' ? 'text-chalkboard-70 dark:text-chalkboard-40' : ''
}`}
>
{title}
{hotkey && (
<kbd className="inline-block ml-2 flex-none hotkey">{hotkey}</kbd>
)}
</span>
)
const ToolbarItemTooltipRichContent = ({
itemConfig,
}: {
itemConfig: ToolbarItemResolved
}) => {
const { state } = useModelingContext()
return (
<>
<div className="rounded-top flex items-center gap-2 pt-3 pb-2 px-2 bg-chalkboard-20/50 dark:bg-chalkboard-80/50"> <div className="rounded-top flex items-center gap-2 pt-3 pb-2 px-2 bg-chalkboard-20/50 dark:bg-chalkboard-80/50">
{itemConfig.icon && (
<CustomIcon className="w-5 h-5" name={itemConfig.icon} />
)}
<span <span
className={`text-sm flex-1 ${ className={`text-sm flex-1 ${
itemConfig.status !== 'available' itemConfig.status !== 'available'
@ -382,6 +491,6 @@ const ToolbarItemTooltip = memo(function ToolbarItemContents({
</ul> </ul>
</> </>
)} )}
</Tooltip> </>
) )
}) }

View File

@ -46,8 +46,8 @@ import {
} from 'lang/modifyAst' } from 'lang/modifyAst'
import { ActionButton } from 'components/ActionButton' import { ActionButton } from 'components/ActionButton'
import { err, reportRejection, trap } from 'lib/trap' import { err, reportRejection, trap } from 'lib/trap'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { Node } from 'wasm-lib/kcl/bindings/Node' import { Node } from 'wasm-lib/kcl/bindings/Node'
import { commandBarActor } from 'machines/commandBarMachine'
function useShouldHideScene(): { hideClient: boolean; hideServer: boolean } { function useShouldHideScene(): { hideClient: boolean; hideServer: boolean } {
const [isCamMoving, setIsCamMoving] = useState(false) const [isCamMoving, setIsCamMoving] = useState(false)
@ -510,7 +510,6 @@ const ConstraintSymbol = ({
constrainInfo: ConstrainInfo constrainInfo: ConstrainInfo
verticalPosition: 'top' | 'bottom' verticalPosition: 'top' | 'bottom'
}) => { }) => {
const { commandBarSend } = useCommandsContext()
const { context } = useModelingContext() const { context } = useModelingContext()
const varNameMap: { const varNameMap: {
[key in ConstrainInfo['type']]: { [key in ConstrainInfo['type']]: {
@ -630,7 +629,7 @@ const ConstraintSymbol = ({
// disabled={implicitDesc} TODO why does this change styles that are hard to override? // disabled={implicitDesc} TODO why does this change styles that are hard to override?
onClick={toSync(async () => { onClick={toSync(async () => {
if (!isConstrained) { if (!isConstrained) {
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: { data: {
name: 'Constrain with named value', name: 'Constrain with named value',
@ -756,7 +755,6 @@ export const CamDebugSettings = () => {
sceneInfra.camControls.reactCameraProperties sceneInfra.camControls.reactCameraProperties
) )
const [fov, setFov] = useState(12) const [fov, setFov] = useState(12)
const { commandBarSend } = useCommandsContext()
useEffect(() => { useEffect(() => {
sceneInfra.camControls.setReactCameraPropertiesCallback(setCamSettings) sceneInfra.camControls.setReactCameraPropertiesCallback(setCamSettings)
@ -775,7 +773,7 @@ export const CamDebugSettings = () => {
type="checkbox" type="checkbox"
checked={camSettings.type === 'perspective'} checked={camSettings.type === 'perspective'}
onChange={() => onChange={() =>
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: { data: {
groupId: 'settings', groupId: 'settings',

View File

@ -1398,23 +1398,23 @@ export class SceneEntities {
const arg0 = arg(kclCircle3PointArgs[0]) const arg0 = arg(kclCircle3PointArgs[0])
if (!arg0) return kclManager.ast if (!arg0) return kclManager.ast
arg0[0].value = points[0].x arg0[0].value = { value: points[0].x, suffix: 'None' }
arg0[0].raw = points[0].x.toString() arg0[0].raw = points[0].x.toString()
arg0[1].value = points[0].y arg0[1].value = { value: points[0].y, suffix: 'None' }
arg0[1].raw = points[0].y.toString() arg0[1].raw = points[0].y.toString()
const arg1 = arg(kclCircle3PointArgs[1]) const arg1 = arg(kclCircle3PointArgs[1])
if (!arg1) return kclManager.ast if (!arg1) return kclManager.ast
arg1[0].value = points[1].x arg1[0].value = { value: points[1].x, suffix: 'None' }
arg1[0].raw = points[1].x.toString() arg1[0].raw = points[1].x.toString()
arg1[1].value = points[1].y arg1[1].value = { value: points[1].y, suffix: 'None' }
arg1[1].raw = points[1].y.toString() arg1[1].raw = points[1].y.toString()
const arg2 = arg(kclCircle3PointArgs[2]) const arg2 = arg(kclCircle3PointArgs[2])
if (!arg2) return kclManager.ast if (!arg2) return kclManager.ast
arg2[0].value = points[2].x arg2[0].value = { value: points[2].x, suffix: 'None' }
arg2[0].raw = points[2].x.toString() arg2[0].raw = points[2].x.toString()
arg2[1].value = points[2].y arg2[1].value = { value: points[2].y, suffix: 'None' }
arg2[1].raw = points[2].y.toString() arg2[1].raw = points[2].y.toString()
const astSnapshot = structuredClone(kclManager.ast) const astSnapshot = structuredClone(kclManager.ast)
@ -2051,8 +2051,8 @@ export class SceneEntities {
) )
if (!(sk instanceof Reason)) { if (!(sk instanceof Reason)) {
sketch = sk sketch = sk
} else if ((maybeSketch as Solid).sketch) { } else if (maybeSketch && (maybeSketch.value as Solid)?.sketch) {
sketch = (maybeSketch as Solid).sketch sketch = (maybeSketch.value as Solid).sketch
} }
if (!sketch) return if (!sketch) return
@ -2541,7 +2541,7 @@ export function sketchFromPathToNode({
const varDec = _varDec.node const varDec = _varDec.node
const result = programMemory.get(varDec?.id?.name || '') const result = programMemory.get(varDec?.id?.name || '')
if (result?.type === 'Solid') { if (result?.type === 'Solid') {
return result.sketch return result.value.sketch
} }
const sg = sketchFromKclValue(result, varDec?.id?.name) const sg = sketchFromKclValue(result, varDec?.id?.name)
if (err(sg)) { if (err(sg)) {

View File

@ -61,6 +61,7 @@ import { SegmentInputs } from 'lang/std/stdTypes'
import { err } from 'lib/trap' import { err } from 'lib/trap'
import { editorManager, sceneInfra } from 'lib/singletons' import { editorManager, sceneInfra } from 'lib/singletons'
import { Selections } from 'lib/selections' import { Selections } from 'lib/selections'
import { commandBarActor } from 'machines/commandBarMachine'
interface CreateSegmentArgs { interface CreateSegmentArgs {
input: SegmentInputs input: SegmentInputs
@ -847,7 +848,7 @@ function createLengthIndicator({
}) })
// Command Bar // Command Bar
editorManager.commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: { data: {
name: 'Constrain length', name: 'Constrain length',

View File

@ -1,9 +1,11 @@
import { Popover } from '@headlessui/react' import { Popover } from '@headlessui/react'
import { ActionButtonProps } from './ActionButton' import { ActionButtonProps } from './ActionButton'
import { CustomIcon } from './CustomIcon' import { CustomIcon } from './CustomIcon'
import Tooltip from './Tooltip'
type ActionButtonSplitProps = ActionButtonProps & { Element: 'button' } & { type ActionButtonSplitProps = ActionButtonProps & { Element: 'button' } & {
name?: string name?: string
dropdownTooltipText?: string
splitMenuItems: { splitMenuItems: {
id: string id: string
label: string label: string
@ -17,6 +19,7 @@ type ActionButtonSplitProps = ActionButtonProps & { Element: 'button' } & {
export function ActionButtonDropdown({ export function ActionButtonDropdown({
splitMenuItems, splitMenuItems,
className, className,
dropdownTooltipText = 'More tools',
children, children,
...props ...props
}: ActionButtonSplitProps) { }: ActionButtonSplitProps) {
@ -26,7 +29,14 @@ export function ActionButtonDropdown({
{({ close }) => ( {({ close }) => (
<> <>
{children} {children}
<Popover.Button className="border-transparent dark:border-transparent p-0 m-0 rounded-none !outline-none ui-open:border-primary ui-open:bg-primary"> <Popover.Button
className={
'!border-transparent dark:!border-transparent ' +
'bg-chalkboard-transparent dark:bg-transparent disabled:bg-transparent dark:disabled:bg-transparent ' +
'enabled:hover:bg-chalkboard-10 dark:enabled:hover:bg-chalkboard-100 ' +
'pressed:!bg-primary pressed:enabled:hover:!text-chalkboard-10 p-0 m-0 rounded-none !outline-none ui-open:border-primary ui-open:bg-primary'
}
>
<CustomIcon <CustomIcon
name="caretDown" name="caretDown"
className={ className={
@ -37,6 +47,14 @@ export function ActionButtonDropdown({
<span className="sr-only"> <span className="sr-only">
{props.name ? props.name + ': ' : ''}open menu {props.name ? props.name + ': ' : ''}open menu
</span> </span>
<Tooltip
delay={0}
position="bottom"
hoverOnly
wrapperClassName="ui-open:!hidden"
>
{dropdownTooltipText}
</Tooltip>
</Popover.Button> </Popover.Button>
<Popover.Panel <Popover.Panel
as="ul" as="ul"

View File

@ -1,8 +1,8 @@
import { Combobox } from '@headlessui/react' import { Combobox } from '@headlessui/react'
import { useSelector } from '@xstate/react' import { useSelector } from '@xstate/react'
import Fuse from 'fuse.js' import Fuse from 'fuse.js'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { CommandArgument, CommandArgumentOption } from 'lib/commandTypes' import { CommandArgument, CommandArgumentOption } from 'lib/commandTypes'
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
import { useEffect, useMemo, useRef, useState } from 'react' import { useEffect, useMemo, useRef, useState } from 'react'
import { AnyStateMachine, StateFrom } from 'xstate' import { AnyStateMachine, StateFrom } from 'xstate'
@ -23,7 +23,7 @@ function CommandArgOptionInput({
placeholder?: string placeholder?: string
}) { }) {
const actorContext = useSelector(arg.machineActor, contextSelector) const actorContext = useSelector(arg.machineActor, contextSelector)
const { commandBarSend, commandBarState } = useCommandsContext() const commandBarState = useCommandBarState()
const resolvedOptions = useMemo( const resolvedOptions = useMemo(
() => () =>
typeof arg.options === 'function' typeof arg.options === 'function'
@ -142,7 +142,7 @@ function CommandArgOptionInput({
className="flex-grow px-2 py-1 border-b border-b-chalkboard-100 dark:border-b-chalkboard-80 !bg-transparent focus:outline-none" className="flex-grow px-2 py-1 border-b border-b-chalkboard-100 dark:border-b-chalkboard-80 !bg-transparent focus:outline-none"
onKeyDown={(event) => { onKeyDown={(event) => {
if (event.metaKey && event.key === 'k') if (event.metaKey && event.key === 'k')
commandBarSend({ type: 'Close' }) commandBarActor.send({ type: 'Close' })
if (event.key === 'Backspace' && !event.currentTarget.value) { if (event.key === 'Backspace' && !event.currentTarget.value) {
stepBack() stepBack()
} }

View File

@ -1,6 +1,5 @@
import { Dialog, Popover, Transition } from '@headlessui/react' import { Dialog, Popover, Transition } from '@headlessui/react'
import { Fragment, useEffect } from 'react' import { Fragment, useEffect } from 'react'
import { useCommandsContext } from 'hooks/useCommandsContext'
import CommandBarArgument from './CommandBarArgument' import CommandBarArgument from './CommandBarArgument'
import CommandComboBox from '../CommandComboBox' import CommandComboBox from '../CommandComboBox'
import CommandBarReview from './CommandBarReview' import CommandBarReview from './CommandBarReview'
@ -8,12 +7,13 @@ import { useLocation } from 'react-router-dom'
import useHotkeyWrapper from 'lib/hotkeyWrapper' import useHotkeyWrapper from 'lib/hotkeyWrapper'
import { CustomIcon } from 'components/CustomIcon' import { CustomIcon } from 'components/CustomIcon'
import Tooltip from 'components/Tooltip' import Tooltip from 'components/Tooltip'
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
export const COMMAND_PALETTE_HOTKEY = 'mod+k' export const COMMAND_PALETTE_HOTKEY = 'mod+k'
export const CommandBar = () => { export const CommandBar = () => {
const { pathname } = useLocation() const { pathname } = useLocation()
const { commandBarState, commandBarSend } = useCommandsContext() const commandBarState = useCommandBarState()
const { const {
context: { selectedCommand, currentArgument, commands }, context: { selectedCommand, currentArgument, commands },
} = commandBarState } = commandBarState
@ -23,16 +23,16 @@ export const CommandBar = () => {
// Close the command bar when navigating // Close the command bar when navigating
useEffect(() => { useEffect(() => {
if (commandBarState.matches('Closed')) return if (commandBarState.matches('Closed')) return
commandBarSend({ type: 'Close' }) commandBarActor.send({ type: 'Close' })
}, [pathname]) }, [pathname])
// Hook up keyboard shortcuts // Hook up keyboard shortcuts
useHotkeyWrapper([COMMAND_PALETTE_HOTKEY], () => { useHotkeyWrapper([COMMAND_PALETTE_HOTKEY], () => {
if (commandBarState.context.commands.length === 0) return if (commandBarState.context.commands.length === 0) return
if (commandBarState.matches('Closed')) { if (commandBarState.matches('Closed')) {
commandBarSend({ type: 'Open' }) commandBarActor.send({ type: 'Open' })
} else { } else {
commandBarSend({ type: 'Close' }) commandBarActor.send({ type: 'Close' })
} }
}) })
@ -52,14 +52,14 @@ export const CommandBar = () => {
...entries[entries.length - 1][1], ...entries[entries.length - 1][1],
} }
commandBarSend({ commandBarActor.send({
type: 'Edit argument', type: 'Edit argument',
data: { data: {
arg: currentArg, arg: currentArg,
}, },
}) })
} else { } else {
commandBarSend({ type: 'Deselect command' }) commandBarActor.send({ type: 'Deselect command' })
} }
} else { } else {
const entries = Object.entries(selectedCommand?.args || {}) const entries = Object.entries(selectedCommand?.args || {})
@ -68,9 +68,9 @@ export const CommandBar = () => {
) )
if (index === 0) { if (index === 0) {
commandBarSend({ type: 'Deselect command' }) commandBarActor.send({ type: 'Deselect command' })
} else { } else {
commandBarSend({ commandBarActor.send({
type: 'Change current argument', type: 'Change current argument',
data: { data: {
arg: { name: entries[index - 1][0], ...entries[index - 1][1] }, arg: { name: entries[index - 1][0], ...entries[index - 1][1] },
@ -85,14 +85,14 @@ export const CommandBar = () => {
show={!commandBarState.matches('Closed') || false} show={!commandBarState.matches('Closed') || false}
afterLeave={() => { afterLeave={() => {
if (selectedCommand?.onCancel) selectedCommand.onCancel() if (selectedCommand?.onCancel) selectedCommand.onCancel()
commandBarSend({ type: 'Clear' }) commandBarActor.send({ type: 'Clear' })
}} }}
as={Fragment} as={Fragment}
> >
<WrapperComponent <WrapperComponent
open={!commandBarState.matches('Closed') || isSelectionArgument} open={!commandBarState.matches('Closed') || isSelectionArgument}
onClose={() => { onClose={() => {
commandBarSend({ type: 'Close' }) commandBarActor.send({ type: 'Close' })
}} }}
className={ className={
'fixed inset-0 z-50 overflow-y-auto pb-4 pt-1 ' + 'fixed inset-0 z-50 overflow-y-auto pb-4 pt-1 ' +
@ -122,7 +122,7 @@ export const CommandBar = () => {
) )
)} )}
<button <button
onClick={() => commandBarSend({ type: 'Close' })} onClick={() => commandBarActor.send({ type: 'Close' })}
className="group block !absolute left-auto right-full top-[-3px] m-2.5 p-0 border-none bg-transparent hover:bg-transparent" className="group block !absolute left-auto right-full top-[-3px] m-2.5 p-0 border-none bg-transparent hover:bg-transparent"
> >
<CustomIcon <CustomIcon

View File

@ -2,13 +2,13 @@ import CommandArgOptionInput from './CommandArgOptionInput'
import CommandBarBasicInput from './CommandBarBasicInput' import CommandBarBasicInput from './CommandBarBasicInput'
import CommandBarSelectionInput from './CommandBarSelectionInput' import CommandBarSelectionInput from './CommandBarSelectionInput'
import { CommandArgument } from 'lib/commandTypes' import { CommandArgument } from 'lib/commandTypes'
import { useCommandsContext } from 'hooks/useCommandsContext'
import CommandBarHeader from './CommandBarHeader' import CommandBarHeader from './CommandBarHeader'
import CommandBarKclInput from './CommandBarKclInput' import CommandBarKclInput from './CommandBarKclInput'
import CommandBarTextareaInput from './CommandBarTextareaInput' import CommandBarTextareaInput from './CommandBarTextareaInput'
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
function CommandBarArgument({ stepBack }: { stepBack: () => void }) { function CommandBarArgument({ stepBack }: { stepBack: () => void }) {
const { commandBarState, commandBarSend } = useCommandsContext() const commandBarState = useCommandBarState()
const { const {
context: { currentArgument }, context: { currentArgument },
} = commandBarState } = commandBarState
@ -16,7 +16,7 @@ function CommandBarArgument({ stepBack }: { stepBack: () => void }) {
function onSubmit(data: unknown) { function onSubmit(data: unknown) {
if (!currentArgument) return if (!currentArgument) return
commandBarSend({ commandBarActor.send({
type: 'Submit argument', type: 'Submit argument',
data: { data: {
[currentArgument.name]: data, [currentArgument.name]: data,

View File

@ -1,5 +1,5 @@
import { useCommandsContext } from 'hooks/useCommandsContext'
import { CommandArgument } from 'lib/commandTypes' import { CommandArgument } from 'lib/commandTypes'
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
import { useEffect, useRef } from 'react' import { useEffect, useRef } from 'react'
import { useHotkeys } from 'react-hotkeys-hook' import { useHotkeys } from 'react-hotkeys-hook'
@ -15,8 +15,8 @@ function CommandBarBasicInput({
stepBack: () => void stepBack: () => void
onSubmit: (event: unknown) => void onSubmit: (event: unknown) => void
}) { }) {
const { commandBarSend, commandBarState } = useCommandsContext() const commandBarState = useCommandBarState()
useHotkeys('mod + k, mod + /', () => commandBarSend({ type: 'Close' })) useHotkeys('mod + k, mod + /', () => commandBarActor.send({ type: 'Close' }))
const inputRef = useRef<HTMLInputElement>(null) const inputRef = useRef<HTMLInputElement>(null)
useEffect(() => { useEffect(() => {

View File

@ -1,4 +1,3 @@
import { useCommandsContext } from 'hooks/useCommandsContext'
import { CustomIcon } from '../CustomIcon' import { CustomIcon } from '../CustomIcon'
import React, { useState } from 'react' import React, { useState } from 'react'
import { ActionButton } from '../ActionButton' import { ActionButton } from '../ActionButton'
@ -7,9 +6,10 @@ import { useHotkeys } from 'react-hotkeys-hook'
import { KclCommandValue, KclExpressionWithVariable } from 'lib/commandTypes' import { KclCommandValue, KclExpressionWithVariable } from 'lib/commandTypes'
import Tooltip from 'components/Tooltip' import Tooltip from 'components/Tooltip'
import { roundOff } from 'lib/utils' import { roundOff } from 'lib/utils'
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
function CommandBarHeader({ children }: React.PropsWithChildren<{}>) { function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
const { commandBarState, commandBarSend } = useCommandsContext() const commandBarState = useCommandBarState()
const { const {
context: { selectedCommand, currentArgument, argumentsToSubmit }, context: { selectedCommand, currentArgument, argumentsToSubmit },
} = commandBarState } = commandBarState
@ -49,7 +49,7 @@ function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
] ]
const arg = selectedCommand?.args[argName] const arg = selectedCommand?.args[argName]
if (!argName || !arg) return if (!argName || !arg) return
commandBarSend({ commandBarActor.send({
type: 'Change current argument', type: 'Change current argument',
data: { arg: { ...arg, name: argName } }, data: { arg: { ...arg, name: argName } },
}) })
@ -100,7 +100,7 @@ function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
} }
disabled={!isReviewing && currentArgument?.name === argName} disabled={!isReviewing && currentArgument?.name === argName}
onClick={() => { onClick={() => {
commandBarSend({ commandBarActor.send({
type: isReviewing type: isReviewing
? 'Edit argument' ? 'Edit argument'
: 'Change current argument', : 'Change current argument',

View File

@ -7,7 +7,6 @@ import {
} from '@codemirror/autocomplete' } from '@codemirror/autocomplete'
import { EditorView, keymap, ViewUpdate } from '@codemirror/view' import { EditorView, keymap, ViewUpdate } from '@codemirror/view'
import { CustomIcon } from 'components/CustomIcon' import { CustomIcon } from 'components/CustomIcon'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext' import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { CommandArgument, KclCommandValue } from 'lib/commandTypes' import { CommandArgument, KclCommandValue } from 'lib/commandTypes'
import { getSystemTheme } from 'lib/theme' import { getSystemTheme } from 'lib/theme'
@ -20,6 +19,7 @@ import styles from './CommandBarKclInput.module.css'
import { createIdentifier, createVariableDeclaration } from 'lang/modifyAst' import { createIdentifier, createVariableDeclaration } from 'lang/modifyAst'
import { useCodeMirror } from 'components/ModelingSidebar/ModelingPanes/CodeEditor' import { useCodeMirror } from 'components/ModelingSidebar/ModelingPanes/CodeEditor'
import { useSelector } from '@xstate/react' import { useSelector } from '@xstate/react'
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
const machineContextSelector = (snapshot?: { const machineContextSelector = (snapshot?: {
context: Record<string, unknown> context: Record<string, unknown>
@ -37,7 +37,7 @@ function CommandBarKclInput({
stepBack: () => void stepBack: () => void
onSubmit: (event: unknown) => void onSubmit: (event: unknown) => void
}) { }) {
const { commandBarSend, commandBarState } = useCommandsContext() const commandBarState = useCommandBarState()
const previouslySetValue = commandBarState.context.argumentsToSubmit[ const previouslySetValue = commandBarState.context.argumentsToSubmit[
arg.name arg.name
] as KclCommandValue | undefined ] as KclCommandValue | undefined
@ -82,7 +82,7 @@ function CommandBarKclInput({
false false
) )
const [canSubmit, setCanSubmit] = useState(true) const [canSubmit, setCanSubmit] = useState(true)
useHotkeys('mod + k, mod + /', () => commandBarSend({ type: 'Close' })) useHotkeys('mod + k, mod + /', () => commandBarActor.send({ type: 'Close' }))
const editorRef = useRef<HTMLDivElement>(null) const editorRef = useRef<HTMLDivElement>(null)
const { const {

View File

@ -1,43 +0,0 @@
import { createActorContext } from '@xstate/react'
import { editorManager } from 'lib/singletons'
import { commandBarMachine } from 'machines/commandBarMachine'
import { useEffect } from 'react'
export const CommandsContext = createActorContext(
commandBarMachine.provide({
guards: {
'Command has no arguments': ({ context }) => {
return (
!context.selectedCommand?.args ||
Object.keys(context.selectedCommand?.args).length === 0
)
},
'All arguments are skippable': ({ context }) => {
return Object.values(context.selectedCommand!.args!).every(
(argConfig) => argConfig.skip
)
},
},
})
)
export const CommandBarProvider = ({
children,
}: {
children: React.ReactNode
}) => {
return (
<CommandsContext.Provider>
<CommandBarProviderInner>{children}</CommandBarProviderInner>
</CommandsContext.Provider>
)
}
function CommandBarProviderInner({ children }: { children: React.ReactNode }) {
const commandBarActor = CommandsContext.useActorRef()
useEffect(() => {
editorManager.setCommandBarSend(commandBarActor.send)
})
return children
}

View File

@ -1,9 +1,9 @@
import { useCommandsContext } from 'hooks/useCommandsContext' import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
import CommandBarHeader from './CommandBarHeader' import CommandBarHeader from './CommandBarHeader'
import { useHotkeys } from 'react-hotkeys-hook' import { useHotkeys } from 'react-hotkeys-hook'
function CommandBarReview({ stepBack }: { stepBack: () => void }) { function CommandBarReview({ stepBack }: { stepBack: () => void }) {
const { commandBarState, commandBarSend } = useCommandsContext() const commandBarState = useCommandBarState()
const { const {
context: { argumentsToSubmit, selectedCommand }, context: { argumentsToSubmit, selectedCommand },
} = commandBarState } = commandBarState
@ -33,7 +33,7 @@ function CommandBarReview({ stepBack }: { stepBack: () => void }) {
parseInt(b.keys[0], 10) - 1 parseInt(b.keys[0], 10) - 1
] ]
const arg = selectedCommand?.args[argName] const arg = selectedCommand?.args[argName]
commandBarSend({ commandBarActor.send({
type: 'Edit argument', type: 'Edit argument',
data: { arg: { ...arg, name: argName } }, data: { arg: { ...arg, name: argName } },
}) })
@ -50,7 +50,7 @@ function CommandBarReview({ stepBack }: { stepBack: () => void }) {
function submitCommand(e: React.FormEvent<HTMLFormElement>) { function submitCommand(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault() e.preventDefault()
commandBarSend({ commandBarActor.send({
type: 'Submit command', type: 'Submit command',
output: argumentsToSubmit, output: argumentsToSubmit,
}) })

View File

@ -1,5 +1,4 @@
import { useSelector } from '@xstate/react' import { useSelector } from '@xstate/react'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { Artifact } from 'lang/std/artifactGraph' import { Artifact } from 'lang/std/artifactGraph'
import { CommandArgument } from 'lib/commandTypes' import { CommandArgument } from 'lib/commandTypes'
import { import {
@ -10,6 +9,7 @@ import {
import { kclManager } from 'lib/singletons' import { kclManager } from 'lib/singletons'
import { reportRejection } from 'lib/trap' import { reportRejection } from 'lib/trap'
import { toSync } from 'lib/utils' import { toSync } from 'lib/utils'
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
import { modelingMachine } from 'machines/modelingMachine' import { modelingMachine } from 'machines/modelingMachine'
import { useEffect, useMemo, useRef, useState } from 'react' import { useEffect, useMemo, useRef, useState } from 'react'
import { StateFrom } from 'xstate' import { StateFrom } from 'xstate'
@ -49,7 +49,7 @@ function CommandBarSelectionInput({
onSubmit: (data: unknown) => void onSubmit: (data: unknown) => void
}) { }) {
const inputRef = useRef<HTMLInputElement>(null) const inputRef = useRef<HTMLInputElement>(null)
const { commandBarState, commandBarSend } = useCommandsContext() const commandBarState = useCommandBarState()
const [hasSubmitted, setHasSubmitted] = useState(false) const [hasSubmitted, setHasSubmitted] = useState(false)
const selection = useSelector(arg.machineActor, selectionSelector) const selection = useSelector(arg.machineActor, selectionSelector)
const selectionsByType = useMemo(() => { const selectionsByType = useMemo(() => {
@ -145,7 +145,7 @@ function CommandBarSelectionInput({
if (event.key === 'Backspace') { if (event.key === 'Backspace') {
stepBack() stepBack()
} else if (event.key === 'Escape') { } else if (event.key === 'Escape') {
commandBarSend({ type: 'Close' }) commandBarActor.send({ type: 'Close' })
} }
}} }}
onChange={handleChange} onChange={handleChange}

View File

@ -1,5 +1,5 @@
import { useCommandsContext } from 'hooks/useCommandsContext'
import { CommandArgument } from 'lib/commandTypes' import { CommandArgument } from 'lib/commandTypes'
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
import { RefObject, useEffect, useRef } from 'react' import { RefObject, useEffect, useRef } from 'react'
import { useHotkeys } from 'react-hotkeys-hook' import { useHotkeys } from 'react-hotkeys-hook'
@ -15,8 +15,8 @@ function CommandBarTextareaInput({
stepBack: () => void stepBack: () => void
onSubmit: (event: unknown) => void onSubmit: (event: unknown) => void
}) { }) {
const { commandBarSend, commandBarState } = useCommandsContext() const commandBarState = useCommandBarState()
useHotkeys('mod + k, mod + /', () => commandBarSend({ type: 'Close' })) useHotkeys('mod + k, mod + /', () => commandBarActor.send({ type: 'Close' }))
const formRef = useRef<HTMLFormElement>(null) const formRef = useRef<HTMLFormElement>(null)
const inputRef = useRef<HTMLTextAreaElement>(null) const inputRef = useRef<HTMLTextAreaElement>(null)
useTextareaAutoGrow(inputRef) useTextareaAutoGrow(inputRef)

View File

@ -1,16 +1,15 @@
import { useCommandsContext } from 'hooks/useCommandsContext'
import usePlatform from 'hooks/usePlatform' import usePlatform from 'hooks/usePlatform'
import { hotkeyDisplay } from 'lib/hotkeyWrapper' import { hotkeyDisplay } from 'lib/hotkeyWrapper'
import { COMMAND_PALETTE_HOTKEY } from './CommandBar/CommandBar' import { COMMAND_PALETTE_HOTKEY } from './CommandBar/CommandBar'
import { commandBarActor } from 'machines/commandBarMachine'
export function CommandBarOpenButton() { export function CommandBarOpenButton() {
const { commandBarSend } = useCommandsContext()
const platform = usePlatform() const platform = usePlatform()
return ( return (
<button <button
className="group rounded-full flex items-center justify-center gap-2 px-2 py-1 bg-primary/10 dark:bg-chalkboard-90 dark:backdrop-blur-sm border-primary hover:border-primary dark:border-chalkboard-50 dark:hover:border-inherit text-primary dark:text-inherit" className="group rounded-full flex items-center justify-center gap-2 px-2 py-1 bg-primary/10 dark:bg-chalkboard-90 dark:backdrop-blur-sm border-primary hover:border-primary dark:border-chalkboard-50 dark:hover:border-inherit text-primary dark:text-inherit"
onClick={() => commandBarSend({ type: 'Open' })} onClick={() => commandBarActor.send({ type: 'Open' })}
data-testid="command-bar-open-button" data-testid="command-bar-open-button"
> >
<span>Commands</span> <span>Commands</span>

View File

@ -1,11 +1,11 @@
import { Combobox } from '@headlessui/react' import { Combobox } from '@headlessui/react'
import Fuse from 'fuse.js' import Fuse from 'fuse.js'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { Command } from 'lib/commandTypes' import { Command } from 'lib/commandTypes'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { CustomIcon } from './CustomIcon' import { CustomIcon } from './CustomIcon'
import { getActorNextEvents } from 'lib/utils' import { getActorNextEvents } from 'lib/utils'
import { sortCommands } from 'lib/commandUtils' import { sortCommands } from 'lib/commandUtils'
import { commandBarActor } from 'machines/commandBarMachine'
function CommandComboBox({ function CommandComboBox({
options, options,
@ -14,7 +14,6 @@ function CommandComboBox({
options: Command[] options: Command[]
placeholder?: string placeholder?: string
}) { }) {
const { commandBarSend } = useCommandsContext()
const [query, setQuery] = useState('') const [query, setQuery] = useState('')
const [filteredOptions, setFilteredOptions] = useState<typeof options>() const [filteredOptions, setFilteredOptions] = useState<typeof options>()
@ -41,7 +40,7 @@ function CommandComboBox({
}, [query]) }, [query])
function handleSelection(command: Command) { function handleSelection(command: Command) {
commandBarSend({ type: 'Select command', data: { command } }) commandBarActor.send({ type: 'Select command', data: { command } })
} }
return ( return (
@ -61,7 +60,7 @@ function CommandComboBox({
(event.key === 'Backspace' && !event.currentTarget.value) (event.key === 'Backspace' && !event.currentTarget.value)
) { ) {
event.preventDefault() event.preventDefault()
commandBarSend({ type: 'Close' }) commandBarActor.send({ type: 'Close' })
} }
}} }}
placeholder={ placeholder={

View File

@ -4,18 +4,18 @@ import { expandPlane, PlaneArtifactRich } from 'lang/std/artifactGraph'
import { ArtifactGraph } from 'lang/wasm' import { ArtifactGraph } from 'lang/wasm'
import { DebugDisplayArray, GenericObj } from './DebugDisplayObj' import { DebugDisplayArray, GenericObj } from './DebugDisplayObj'
export function DebugFeatureTree() { export function DebugArtifactGraph() {
const featureTree = useMemo(() => { const artifactGraphTree = useMemo(() => {
return computeTree(engineCommandManager.artifactGraph) return computeTree(engineCommandManager.artifactGraph)
}, [engineCommandManager.artifactGraph]) }, [engineCommandManager.artifactGraph])
const filterKeys: string[] = ['__meta', 'codeRef', 'pathToNode'] const filterKeys: string[] = ['__meta', 'codeRef', 'pathToNode']
return ( return (
<details data-testid="debug-feature-tree" className="relative"> <details data-testid="debug-feature-tree" className="relative">
<summary>Feature Tree</summary> <summary>Artifact Graph</summary>
{featureTree.length > 0 ? ( {artifactGraphTree.length > 0 ? (
<pre className="text-xs"> <pre className="text-xs">
<DebugDisplayArray arr={featureTree} filterKeys={filterKeys} /> <DebugDisplayArray arr={artifactGraphTree} filterKeys={filterKeys} />
</pre> </pre>
) : ( ) : (
<p>(Empty)</p> <p>(Empty)</p>

View File

@ -12,7 +12,6 @@ import {
StateFrom, StateFrom,
fromPromise, fromPromise,
} from 'xstate' } from 'xstate'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { fileMachine } from 'machines/fileMachine' import { fileMachine } from 'machines/fileMachine'
import { isDesktop } from 'lib/isDesktop' import { isDesktop } from 'lib/isDesktop'
import { import {
@ -30,6 +29,7 @@ import {
} from 'lib/getKclSamplesManifest' } from 'lib/getKclSamplesManifest'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext' import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { markOnce } from 'lib/performance' import { markOnce } from 'lib/performance'
import { commandBarActor } from 'machines/commandBarMachine'
type MachineContext<T extends AnyStateMachine> = { type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T> state: StateFrom<T>
@ -47,7 +47,6 @@ export const FileMachineProvider = ({
children: React.ReactNode children: React.ReactNode
}) => { }) => {
const navigate = useNavigate() const navigate = useNavigate()
const { commandBarSend } = useCommandsContext()
const { settings } = useSettingsAuthContext() const { settings } = useSettingsAuthContext()
const { project, file } = useRouteLoaderData(PATHS.FILE) as IndexLoaderData const { project, file } = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
const [kclSamples, setKclSamples] = React.useState<KclSamplesManifestItem[]>( const [kclSamples, setKclSamples] = React.useState<KclSamplesManifestItem[]>(
@ -90,7 +89,7 @@ export const FileMachineProvider = ({
navigateToFile: ({ context, event }) => { navigateToFile: ({ context, event }) => {
if (event.type !== 'xstate.done.actor.create-and-open-file') return if (event.type !== 'xstate.done.actor.create-and-open-file') return
if (event.output && 'name' in event.output) { if (event.output && 'name' in event.output) {
commandBarSend({ type: 'Close' }) commandBarActor.send({ type: 'Close' })
navigate( navigate(
`..${PATHS.FILE}/${encodeURIComponent( `..${PATHS.FILE}/${encodeURIComponent(
context.selectedDirectory + context.selectedDirectory +
@ -336,15 +335,18 @@ export const FileMachineProvider = ({
) )
useEffect(() => { useEffect(() => {
commandBarSend({ type: 'Add commands', data: { commands: kclCommandMemo } }) commandBarActor.send({
type: 'Add commands',
data: { commands: kclCommandMemo },
})
return () => { return () => {
commandBarSend({ commandBarActor.send({
type: 'Remove commands', type: 'Remove commands',
data: { commands: kclCommandMemo }, data: { commands: kclCommandMemo },
}) })
} }
}, [commandBarSend, kclCommandMemo]) }, [commandBarActor.send, kclCommandMemo])
return ( return (
<FileContext.Provider <FileContext.Provider

View File

@ -1,11 +1,11 @@
import { createContext, useEffect, useState } from 'react' import { createContext, useEffect, useState } from 'react'
import { engineCommandManager } from 'lib/singletons' import { engineCommandManager } from 'lib/singletons'
import { CommandsContext } from 'components/CommandBar/CommandBarProvider'
import { isDesktop } from 'lib/isDesktop' import { isDesktop } from 'lib/isDesktop'
import { components } from 'lib/machine-api' import { components } from 'lib/machine-api'
import { reportRejection } from 'lib/trap' import { reportRejection } from 'lib/trap'
import { toSync } from 'lib/utils' import { toSync } from 'lib/utils'
import { commandBarActor } from 'machines/commandBarMachine'
export type MachinesListing = Array< export type MachinesListing = Array<
components['schemas']['MachineInfoResponse'] components['schemas']['MachineInfoResponse']
@ -42,8 +42,6 @@ export const MachineManagerProvider = ({
components['schemas']['MachineInfoResponse'] | null components['schemas']['MachineInfoResponse'] | null
>(null) >(null)
const commandBarActor = CommandsContext.useActorRef()
// Get the reason message for why there are no machines. // Get the reason message for why there are no machines.
const noMachinesReason = (): string | undefined => { const noMachinesReason = (): string | undefined => {
if (machines.length > 0) { if (machines.length > 0) {

View File

@ -1,4 +1,4 @@
import { useMachine } from '@xstate/react' import { useMachine, useSelector } from '@xstate/react'
import React, { import React, {
createContext, createContext,
useEffect, useEffect,
@ -11,6 +11,7 @@ import {
AnyStateMachine, AnyStateMachine,
ContextFrom, ContextFrom,
Prop, Prop,
SnapshotFrom,
StateFrom, StateFrom,
assign, assign,
fromPromise, fromPromise,
@ -78,7 +79,6 @@ import toast from 'react-hot-toast'
import { useLoaderData, useNavigate, useSearchParams } from 'react-router-dom' import { useLoaderData, useNavigate, useSearchParams } from 'react-router-dom'
import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls' import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls'
import { err, reportRejection, trap } from 'lib/trap' import { err, reportRejection, trap } from 'lib/trap'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { import {
ExportIntent, ExportIntent,
EngineConnectionStateType, EngineConnectionStateType,
@ -91,6 +91,7 @@ import { IndexLoaderData } from 'lib/types'
import { Node } from 'wasm-lib/kcl/bindings/Node' import { Node } from 'wasm-lib/kcl/bindings/Node'
import { promptToEditFlow } from 'lib/promptToEdit' import { promptToEditFlow } from 'lib/promptToEdit'
import { kclEditorActor } from 'machines/kclEditorMachine' import { kclEditorActor } from 'machines/kclEditorMachine'
import { commandBarActor } from 'machines/commandBarMachine'
type MachineContext<T extends AnyStateMachine> = { type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T> state: StateFrom<T>
@ -102,6 +103,10 @@ export const ModelingMachineContext = createContext(
{} as MachineContext<typeof modelingMachine> {} as MachineContext<typeof modelingMachine>
) )
const commandBarIsClosedSelector = (
state: SnapshotFrom<typeof commandBarActor>
) => state.matches('Closed')
export const ModelingMachineProvider = ({ export const ModelingMachineProvider = ({
children, children,
}: { }: {
@ -132,8 +137,10 @@ export const ModelingMachineProvider = ({
let [searchParams] = useSearchParams() let [searchParams] = useSearchParams()
const pool = searchParams.get('pool') const pool = searchParams.get('pool')
const { commandBarState, commandBarSend } = useCommandsContext() const isCommandBarClosed = useSelector(
commandBarActor,
commandBarIsClosedSelector
)
// Settings machine setup // Settings machine setup
// const retrievedSettings = useRef( // const retrievedSettings = useRef(
// localStorage?.getItem(MODELING_PERSIST_KEY) || '{}' // localStorage?.getItem(MODELING_PERSIST_KEY) || '{}'
@ -388,7 +395,16 @@ export const ModelingMachineProvider = ({
} }
if (setSelections.selectionType === 'completeSelection') { if (setSelections.selectionType === 'completeSelection') {
editorManager.selectRange(setSelections.selection) const codeMirrorSelection = editorManager.createEditorSelection(
setSelections.selection
)
kclEditorActor.send({
type: 'setLastSelectionEvent',
data: {
codeMirrorSelection,
scrollIntoView: false,
},
})
if (!sketchDetails) if (!sketchDetails)
return { return {
selectionRanges: setSelections.selection, selectionRanges: setSelections.selection,
@ -529,7 +545,6 @@ export const ModelingMachineProvider = ({
trimmedPrompt, trimmedPrompt,
fileMachineSend, fileMachineSend,
navigate, navigate,
commandBarSend,
context, context,
token, token,
settings: { settings: {
@ -543,7 +558,7 @@ export const ModelingMachineProvider = ({
'has valid selection for deletion': ({ 'has valid selection for deletion': ({
context: { selectionRanges }, context: { selectionRanges },
}) => { }) => {
if (!commandBarState.matches('Closed')) return false if (!isCommandBarClosed) return false
if (selectionRanges.graphSelections.length <= 0) return false if (selectionRanges.graphSelections.length <= 0) return false
return true return true
}, },

View File

@ -1,4 +1,4 @@
import { DebugFeatureTree } from 'components/DebugFeatureTree' import { DebugArtifactGraph } from 'components/DebugArtifactGraph'
import { AstExplorer } from '../../AstExplorer' import { AstExplorer } from '../../AstExplorer'
import { EngineCommands } from '../../EngineCommands' import { EngineCommands } from '../../EngineCommands'
import { CamDebugSettings } from 'clientSideScene/ClientSideSceneComp' import { CamDebugSettings } from 'clientSideScene/ClientSideSceneComp'
@ -14,7 +14,7 @@ export const DebugPane = () => {
<EngineCommands /> <EngineCommands />
<CamDebugSettings /> <CamDebugSettings />
<AstExplorer /> <AstExplorer />
<DebugFeatureTree /> <DebugArtifactGraph />
</div> </div>
</section> </section>
</div> </div>

View File

@ -3,6 +3,7 @@
@apply font-mono !no-underline text-xs font-bold select-none text-chalkboard-90; @apply font-mono !no-underline text-xs font-bold select-none text-chalkboard-90;
@apply ui-active:bg-primary/10 ui-active:text-primary ui-active:text-inherit; @apply ui-active:bg-primary/10 ui-active:text-primary ui-active:text-inherit;
@apply transition-colors ease-out; @apply transition-colors ease-out;
@apply m-0;
} }
:global(.dark) .button { :global(.dark) .button {

View File

@ -9,12 +9,11 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { kclManager } from 'lib/singletons' import { kclManager } from 'lib/singletons'
import { openExternalBrowserIfDesktop } from 'lib/openWindow' import { openExternalBrowserIfDesktop } from 'lib/openWindow'
import { reportRejection } from 'lib/trap' import { reportRejection } from 'lib/trap'
import { useCommandsContext } from 'hooks/useCommandsContext' import { commandBarActor } from 'machines/commandBarMachine'
export const KclEditorMenu = ({ children }: PropsWithChildren) => { export const KclEditorMenu = ({ children }: PropsWithChildren) => {
const { enable: convertToVarEnabled, handleClick: handleConvertToVarClick } = const { enable: convertToVarEnabled, handleClick: handleConvertToVarClick } =
useConvertToVariable() useConvertToVariable()
const { commandBarSend } = useCommandsContext()
return ( return (
<Menu> <Menu>
@ -85,7 +84,7 @@ export const KclEditorMenu = ({ children }: PropsWithChildren) => {
<Menu.Item> <Menu.Item>
<button <button
onClick={() => { onClick={() => {
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: { data: {
groupId: 'code', groupId: 'code',

View File

@ -95,9 +95,11 @@ export const processMemory = (programMemory: ProgramMemory) => {
) { ) {
const sk = sketchFromKclValueOptional(val, key) const sk = sketchFromKclValueOptional(val, key)
if (val.type === 'Solid') { if (val.type === 'Solid') {
processedMemory[key] = val.value.map(({ ...rest }: ExtrudeSurface) => { processedMemory[key] = val.value.value.map(
({ ...rest }: ExtrudeSurface) => {
return rest return rest
}) }
)
} else if (!(sk instanceof Reason)) { } else if (!(sk instanceof Reason)) {
processedMemory[key] = sk.paths.map(({ __geoMeta, ...rest }: Path) => { processedMemory[key] = sk.paths.map(({ __geoMeta, ...rest }: Path) => {
return rest return rest

View File

@ -15,12 +15,12 @@ import { ModelingPane } from './ModelingPane'
import { isDesktop } from 'lib/isDesktop' import { isDesktop } from 'lib/isDesktop'
import { useModelingContext } from 'hooks/useModelingContext' import { useModelingContext } from 'hooks/useModelingContext'
import { CustomIconName } from 'components/CustomIcon' import { CustomIconName } from 'components/CustomIcon'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { IconDefinition } from '@fortawesome/free-solid-svg-icons' import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
import { useKclContext } from 'lang/KclProvider' import { useKclContext } from 'lang/KclProvider'
import { MachineManagerContext } from 'components/MachineManagerProvider' import { MachineManagerContext } from 'components/MachineManagerProvider'
import { onboardingPaths } from 'routes/Onboarding/paths' import { onboardingPaths } from 'routes/Onboarding/paths'
import { SIDEBAR_BUTTON_SUFFIX } from 'lib/constants' import { SIDEBAR_BUTTON_SUFFIX } from 'lib/constants'
import { commandBarActor } from 'machines/commandBarMachine'
interface ModelingSidebarProps { interface ModelingSidebarProps {
paneOpacity: '' | 'opacity-20' | 'opacity-40' paneOpacity: '' | 'opacity-20' | 'opacity-40'
@ -37,7 +37,6 @@ function getPlatformString(): 'web' | 'desktop' {
export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) { export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
const machineManager = useContext(MachineManagerContext) const machineManager = useContext(MachineManagerContext)
const { commandBarSend } = useCommandsContext()
const kclContext = useKclContext() const kclContext = useKclContext()
const { settings } = useSettingsAuthContext() const { settings } = useSettingsAuthContext()
const onboardingStatus = settings.context.app.onboardingStatus const onboardingStatus = settings.context.app.onboardingStatus
@ -66,7 +65,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
icon: 'floppyDiskArrow', icon: 'floppyDiskArrow',
keybinding: 'Ctrl + Shift + E', keybinding: 'Ctrl + Shift + E',
action: () => action: () =>
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: { name: 'Export', groupId: 'modeling' }, data: { name: 'Export', groupId: 'modeling' },
}), }),
@ -79,7 +78,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
keybinding: 'Ctrl + Shift + M', keybinding: 'Ctrl + Shift + M',
// eslint-disable-next-line @typescript-eslint/no-misused-promises // eslint-disable-next-line @typescript-eslint/no-misused-promises
action: async () => { action: async () => {
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: { name: 'Make', groupId: 'modeling' }, data: { name: 'Make', groupId: 'modeling' },
}) })

View File

@ -1,7 +1,6 @@
import { fireEvent, render, screen } from '@testing-library/react' import { fireEvent, render, screen } from '@testing-library/react'
import { BrowserRouter } from 'react-router-dom' import { BrowserRouter } from 'react-router-dom'
import { SettingsAuthProviderJest } from './SettingsAuthProvider' import { SettingsAuthProviderJest } from './SettingsAuthProvider'
import { CommandBarProvider } from './CommandBar/CommandBarProvider'
import { import {
NETWORK_HEALTH_TEXT, NETWORK_HEALTH_TEXT,
NetworkHealthIndicator, NetworkHealthIndicator,
@ -12,9 +11,7 @@ function TestWrap({ children }: { children: React.ReactNode }) {
// wrap in router and xState context // wrap in router and xState context
return ( return (
<BrowserRouter> <BrowserRouter>
<CommandBarProvider>
<SettingsAuthProviderJest>{children}</SettingsAuthProviderJest> <SettingsAuthProviderJest>{children}</SettingsAuthProviderJest>
</CommandBarProvider>
</BrowserRouter> </BrowserRouter>
) )
} }

View File

@ -2,7 +2,6 @@ import { render, screen } from '@testing-library/react'
import { BrowserRouter } from 'react-router-dom' import { BrowserRouter } from 'react-router-dom'
import ProjectSidebarMenu from './ProjectSidebarMenu' import ProjectSidebarMenu from './ProjectSidebarMenu'
import { SettingsAuthProviderJest } from './SettingsAuthProvider' import { SettingsAuthProviderJest } from './SettingsAuthProvider'
import { CommandBarProvider } from './CommandBar/CommandBarProvider'
import { Project } from 'lib/project' import { Project } from 'lib/project'
const now = new Date() const now = new Date()
@ -33,11 +32,9 @@ describe('ProjectSidebarMenu tests', () => {
test('Disables popover menu by default', () => { test('Disables popover menu by default', () => {
render( render(
<BrowserRouter> <BrowserRouter>
<CommandBarProvider>
<SettingsAuthProviderJest> <SettingsAuthProviderJest>
<ProjectSidebarMenu project={projectWellFormed} /> <ProjectSidebarMenu project={projectWellFormed} />
</SettingsAuthProviderJest> </SettingsAuthProviderJest>
</CommandBarProvider>
</BrowserRouter> </BrowserRouter>
) )

View File

@ -7,7 +7,6 @@ import { Link, useLocation, useNavigate } from 'react-router-dom'
import { Fragment, useMemo, useContext } from 'react' import { Fragment, useMemo, useContext } from 'react'
import { Logo } from './Logo' import { Logo } from './Logo'
import { APP_NAME } from 'lib/constants' import { APP_NAME } from 'lib/constants'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { CustomIcon } from './CustomIcon' import { CustomIcon } from './CustomIcon'
import { useLspContext } from './LspProvider' import { useLspContext } from './LspProvider'
import { engineCommandManager, kclManager } from 'lib/singletons' import { engineCommandManager, kclManager } from 'lib/singletons'
@ -15,6 +14,9 @@ import { MachineManagerContext } from 'components/MachineManagerProvider'
import usePlatform from 'hooks/usePlatform' import usePlatform from 'hooks/usePlatform'
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath' import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
import Tooltip from './Tooltip' import Tooltip from './Tooltip'
import { SnapshotFrom } from 'xstate'
import { commandBarActor } from 'machines/commandBarMachine'
import { useSelector } from '@xstate/react'
const ProjectSidebarMenu = ({ const ProjectSidebarMenu = ({
project, project,
@ -84,6 +86,9 @@ function AppLogoLink({
) )
} }
const commandsSelector = (state: SnapshotFrom<typeof commandBarActor>) =>
state.context.commands
function ProjectMenuPopover({ function ProjectMenuPopover({
project, project,
file, file,
@ -96,16 +101,14 @@ function ProjectMenuPopover({
const navigate = useNavigate() const navigate = useNavigate()
const filePath = useAbsoluteFilePath() const filePath = useAbsoluteFilePath()
const machineManager = useContext(MachineManagerContext) const machineManager = useContext(MachineManagerContext)
const commands = useSelector(commandBarActor, commandsSelector)
const { commandBarState, commandBarSend } = useCommandsContext()
const { onProjectClose } = useLspContext() const { onProjectClose } = useLspContext()
const exportCommandInfo = { name: 'Export', groupId: 'modeling' } const exportCommandInfo = { name: 'Export', groupId: 'modeling' }
const makeCommandInfo = { name: 'Make', groupId: 'modeling' } const makeCommandInfo = { name: 'Make', groupId: 'modeling' }
const findCommand = (obj: { name: string; groupId: string }) => const findCommand = (obj: { name: string; groupId: string }) =>
Boolean( Boolean(
commandBarState.context.commands.find( commands.find((c) => c.name === obj.name && c.groupId === obj.groupId)
(c) => c.name === obj.name && c.groupId === obj.groupId
)
) )
const machineCount = machineManager.machines.length const machineCount = machineManager.machines.length
@ -150,7 +153,7 @@ function ProjectMenuPopover({
), ),
disabled: !findCommand(exportCommandInfo), disabled: !findCommand(exportCommandInfo),
onClick: () => onClick: () =>
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: exportCommandInfo, data: exportCommandInfo,
}), }),
@ -175,7 +178,7 @@ function ProjectMenuPopover({
), ),
disabled: !findCommand(makeCommandInfo) || machineCount === 0, disabled: !findCommand(makeCommandInfo) || machineCount === 0,
onClick: () => { onClick: () => {
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: makeCommandInfo, data: makeCommandInfo,
}) })
@ -200,7 +203,7 @@ function ProjectMenuPopover({
[ [
platform, platform,
findCommand, findCommand,
commandBarSend, commandBarActor.send,
engineCommandManager, engineCommandManager,
onProjectClose, onProjectClose,
isDesktop, isDesktop,

View File

@ -1,5 +1,4 @@
import { useMachine } from '@xstate/react' import { useMachine } from '@xstate/react'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher' import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher'
import { useProjectsLoader } from 'hooks/useProjectsLoader' import { useProjectsLoader } from 'hooks/useProjectsLoader'
import { projectsMachine } from 'machines/projectsMachine' import { projectsMachine } from 'machines/projectsMachine'
@ -24,6 +23,7 @@ import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import useStateMachineCommands from 'hooks/useStateMachineCommands' import useStateMachineCommands from 'hooks/useStateMachineCommands'
import { projectsCommandBarConfig } from 'lib/commandBarConfigs/projectsCommandConfig' import { projectsCommandBarConfig } from 'lib/commandBarConfigs/projectsCommandConfig'
import { isDesktop } from 'lib/isDesktop' import { isDesktop } from 'lib/isDesktop'
import { commandBarActor } from 'machines/commandBarMachine'
type MachineContext<T extends AnyStateMachine> = { type MachineContext<T extends AnyStateMachine> = {
state?: StateFrom<T> state?: StateFrom<T>
@ -73,7 +73,6 @@ const ProjectsContextDesktop = ({
}) => { }) => {
const navigate = useNavigate() const navigate = useNavigate()
const location = useLocation() const location = useLocation()
const { commandBarSend } = useCommandsContext()
const { onProjectOpen } = useLspContext() const { onProjectOpen } = useLspContext()
const { const {
settings: { context: settings }, settings: { context: settings },
@ -126,7 +125,7 @@ const ProjectsContextDesktop = ({
}, },
null null
) )
commandBarSend({ type: 'Close' }) commandBarActor.send({ type: 'Close' })
const newPathName = `${PATHS.FILE}/${encodeURIComponent( const newPathName = `${PATHS.FILE}/${encodeURIComponent(
projectPath projectPath
)}` )}`

View File

@ -29,7 +29,6 @@ import {
createSettingsCommand, createSettingsCommand,
settingsWithCommandConfigs, settingsWithCommandConfigs,
} from 'lib/commandBarConfigs/settingsCommandConfig' } from 'lib/commandBarConfigs/settingsCommandConfig'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { Command } from 'lib/commandTypes' import { Command } from 'lib/commandTypes'
import { BaseUnit } from 'lib/settings/settingsTypes' import { BaseUnit } from 'lib/settings/settingsTypes'
import { import {
@ -42,6 +41,7 @@ import { isDesktop } from 'lib/isDesktop'
import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher' import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher'
import { codeManager } from 'lib/singletons' import { codeManager } from 'lib/singletons'
import { createRouteCommands } from 'lib/commandBarConfigs/routeCommandConfig' import { createRouteCommands } from 'lib/commandBarConfigs/routeCommandConfig'
import { commandBarActor } from 'machines/commandBarMachine'
type MachineContext<T extends AnyStateMachine> = { type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T> state: StateFrom<T>
@ -109,7 +109,6 @@ export const SettingsAuthProviderBase = ({
}) => { }) => {
const location = useLocation() const location = useLocation()
const navigate = useNavigate() const navigate = useNavigate()
const { commandBarSend } = useCommandsContext()
const [settingsPath, setSettingsPath] = useState<string | undefined>( const [settingsPath, setSettingsPath] = useState<string | undefined>(
undefined undefined
) )
@ -278,10 +277,10 @@ export const SettingsAuthProviderBase = ({
) )
.filter((c) => c !== null) as Command[] .filter((c) => c !== null) as Command[]
commandBarSend({ type: 'Add commands', data: { commands: commands } }) commandBarActor.send({ type: 'Add commands', data: { commands: commands } })
return () => { return () => {
commandBarSend({ commandBarActor.send({
type: 'Remove commands', type: 'Remove commands',
data: { commands }, data: { commands },
}) })
@ -290,7 +289,7 @@ export const SettingsAuthProviderBase = ({
settingsState, settingsState,
settingsSend, settingsSend,
settingsActor, settingsActor,
commandBarSend, commandBarActor.send,
settingsWithCommandConfigs, settingsWithCommandConfigs,
]) ])
@ -303,7 +302,7 @@ export const SettingsAuthProviderBase = ({
encodeURIComponent(loadedProject?.file?.path || BROWSER_PATH) encodeURIComponent(loadedProject?.file?.path || BROWSER_PATH)
const { RouteTelemetryCommand, RouteHomeCommand, RouteSettingsCommand } = const { RouteTelemetryCommand, RouteHomeCommand, RouteSettingsCommand } =
createRouteCommands(navigate, location, filePath) createRouteCommands(navigate, location, filePath)
commandBarSend({ commandBarActor.send({
type: 'Remove commands', type: 'Remove commands',
data: { data: {
commands: [ commands: [
@ -314,12 +313,12 @@ export const SettingsAuthProviderBase = ({
}, },
}) })
if (location.pathname === PATHS.HOME) { if (location.pathname === PATHS.HOME) {
commandBarSend({ commandBarActor.send({
type: 'Add commands', type: 'Add commands',
data: { commands: [RouteTelemetryCommand, RouteSettingsCommand] }, data: { commands: [RouteTelemetryCommand, RouteSettingsCommand] },
}) })
} else if (location.pathname.includes(PATHS.FILE)) { } else if (location.pathname.includes(PATHS.FILE)) {
commandBarSend({ commandBarActor.send({
type: 'Add commands', type: 'Add commands',
data: { data: {
commands: [ commands: [

View File

@ -17,10 +17,11 @@ import {
import { useRouteLoaderData } from 'react-router-dom' import { useRouteLoaderData } from 'react-router-dom'
import { PATHS } from 'lib/paths' import { PATHS } from 'lib/paths'
import { IndexLoaderData } from 'lib/types' import { IndexLoaderData } from 'lib/types'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { err, reportRejection } from 'lib/trap' import { err, reportRejection } from 'lib/trap'
import { getArtifactOfTypes } from 'lang/std/artifactGraph' import { getArtifactOfTypes } from 'lang/std/artifactGraph'
import { ViewControlContextMenu } from './ViewControlMenu' import { ViewControlContextMenu } from './ViewControlMenu'
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
import { useSelector } from '@xstate/react'
enum StreamState { enum StreamState {
Playing = 'playing', Playing = 'playing',
@ -35,7 +36,7 @@ export const Stream = () => {
const videoRef = useRef<HTMLVideoElement>(null) const videoRef = useRef<HTMLVideoElement>(null)
const { settings } = useSettingsAuthContext() const { settings } = useSettingsAuthContext()
const { state, send } = useModelingContext() const { state, send } = useModelingContext()
const { commandBarState } = useCommandsContext() const commandBarState = useCommandBarState()
const { mediaStream } = useAppStream() const { mediaStream } = useAppStream()
const { overallState, immediateState } = useNetworkContext() const { overallState, immediateState } = useNetworkContext()
const [streamState, setStreamState] = useState(StreamState.Unset) const [streamState, setStreamState] = useState(StreamState.Unset)

View File

@ -28,7 +28,7 @@ import { base64Decode } from 'lang/wasm'
import { sendTelemetry } from 'lib/textToCad' import { sendTelemetry } from 'lib/textToCad'
import { Themes } from 'lib/theme' import { Themes } from 'lib/theme'
import { ActionButton } from './ActionButton' import { ActionButton } from './ActionButton'
import { commandBarMachine } from 'machines/commandBarMachine' import { commandBarActor, commandBarMachine } from 'machines/commandBarMachine'
import { EventFrom } from 'xstate' import { EventFrom } from 'xstate'
import { fileMachine } from 'machines/fileMachine' import { fileMachine } from 'machines/fileMachine'
import { reportRejection } from 'lib/trap' import { reportRejection } from 'lib/trap'
@ -43,15 +43,10 @@ export function ToastTextToCadError({
toastId, toastId,
message, message,
prompt, prompt,
commandBarSend,
}: { }: {
toastId: string toastId: string
message: string message: string
prompt: string prompt: string
commandBarSend: (
event: EventFrom<typeof commandBarMachine>,
data?: unknown
) => void
}) { }) {
return ( return (
<div className="flex flex-col justify-between gap-6"> <div className="flex flex-col justify-between gap-6">
@ -81,7 +76,7 @@ export function ToastTextToCadError({
}} }}
name="Edit prompt" name="Edit prompt"
onClick={() => { onClick={() => {
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: { data: {
groupId: 'modeling', groupId: 'modeling',

View File

@ -8,7 +8,6 @@ import {
} from 'react-router-dom' } from 'react-router-dom'
import { Models } from '@kittycad/lib' import { Models } from '@kittycad/lib'
import { SettingsAuthProviderJest } from './SettingsAuthProvider' import { SettingsAuthProviderJest } from './SettingsAuthProvider'
import { CommandBarProvider } from './CommandBar/CommandBarProvider'
type User = Models['User_type'] type User = Models['User_type']
@ -124,9 +123,7 @@ function TestWrap({ children }: { children: React.ReactNode }) {
<Route <Route
path="/file/:id" path="/file/:id"
element={ element={
<CommandBarProvider>
<SettingsAuthProviderJest>{children}</SettingsAuthProviderJest> <SettingsAuthProviderJest>{children}</SettingsAuthProviderJest>
</CommandBarProvider>
} }
/> />
), ),

View File

@ -5,7 +5,6 @@ import { engineCommandManager, kclManager } from 'lib/singletons'
import { modelingMachine, ModelingMachineEvent } from 'machines/modelingMachine' import { modelingMachine, ModelingMachineEvent } from 'machines/modelingMachine'
import { Selections, Selection, processCodeMirrorRanges } from 'lib/selections' import { Selections, Selection, processCodeMirrorRanges } from 'lib/selections'
import { undo, redo } from '@codemirror/commands' import { undo, redo } from '@codemirror/commands'
import { CommandBarMachineEvent } from 'machines/commandBarMachine'
import { addLineHighlight, addLineHighlightEvent } from './highlightextension' import { addLineHighlight, addLineHighlightEvent } from './highlightextension'
import { import {
Diagnostic, Diagnostic,
@ -52,9 +51,6 @@ export default class EditorManager {
private _modelingSend: (eventInfo: ModelingMachineEvent) => void = () => {} private _modelingSend: (eventInfo: ModelingMachineEvent) => void = () => {}
private _modelingState: StateFrom<typeof modelingMachine> | null = null private _modelingState: StateFrom<typeof modelingMachine> | null = null
private _commandBarSend: (eventInfo: CommandBarMachineEvent) => void =
() => {}
private _convertToVariableEnabled: boolean = false private _convertToVariableEnabled: boolean = false
private _convertToVariableCallback: () => void = () => {} private _convertToVariableCallback: () => void = () => {}
@ -161,14 +157,6 @@ export default class EditorManager {
this._modelingState = state this._modelingState = state
} }
setCommandBarSend(send: (eventInfo: CommandBarMachineEvent) => void) {
this._commandBarSend = send
}
commandBarSend(eventInfo: CommandBarMachineEvent): void {
return this._commandBarSend(eventInfo)
}
get highlightRange(): Array<[number, number]> { get highlightRange(): Array<[number, number]> {
return this._highlightRange return this._highlightRange
} }
@ -315,6 +303,21 @@ export default class EditorManager {
if (selections?.graphSelections?.length === 0) { if (selections?.graphSelections?.length === 0) {
return return
} }
if (!this._editorView) {
return
}
const codeBaseSelections = this.createEditorSelection(selections)
this._editorView.dispatch({
selection: codeBaseSelections,
annotations: [
updateOutsideEditorEvent,
Transaction.addToHistory.of(false),
],
})
}
createEditorSelection(selections: Selections) {
let codeBasedSelections = [] let codeBasedSelections = []
for (const selection of selections.graphSelections) { for (const selection of selections.graphSelections) {
const safeEnd = Math.min( const safeEnd = Math.min(
@ -331,18 +334,7 @@ export default class EditorManager {
.range[1] .range[1]
const safeEnd = Math.min(end, this._editorView?.state.doc.length || end) const safeEnd = Math.min(end, this._editorView?.state.doc.length || end)
codeBasedSelections.push(EditorSelection.cursor(safeEnd)) codeBasedSelections.push(EditorSelection.cursor(safeEnd))
return EditorSelection.create(codeBasedSelections, 1)
if (!this._editorView) {
return
}
this._editorView.dispatch({
selection: EditorSelection.create(codeBasedSelections, 1),
annotations: [
updateOutsideEditorEvent,
Transaction.addToHistory.of(false),
],
})
} }
// We will ONLY get here if the user called a select event. // We will ONLY get here if the user called a select event.

View File

@ -1,10 +0,0 @@
import { CommandsContext } from 'components/CommandBar/CommandBarProvider'
export const useCommandsContext = () => {
const commandBarActor = CommandsContext.useActorRef()
const commandBarState = CommandsContext.useSelector((state) => state)
return {
commandBarSend: commandBarActor.send,
commandBarState,
}
}

View File

@ -1,7 +1,6 @@
import { useEffect } from 'react' import { useEffect } from 'react'
import { AnyStateMachine, Actor, StateFrom, EventFrom } from 'xstate' import { AnyStateMachine, Actor, StateFrom, EventFrom } from 'xstate'
import { createMachineCommand } from '../lib/createMachineCommand' import { createMachineCommand } from '../lib/createMachineCommand'
import { useCommandsContext } from './useCommandsContext'
import { modelingMachine } from 'machines/modelingMachine' import { modelingMachine } from 'machines/modelingMachine'
import { authMachine } from 'machines/authMachine' import { authMachine } from 'machines/authMachine'
import { settingsMachine } from 'machines/settingsMachine' import { settingsMachine } from 'machines/settingsMachine'
@ -15,6 +14,7 @@ import { useKclContext } from 'lang/KclProvider'
import { useNetworkContext } from 'hooks/useNetworkContext' import { useNetworkContext } from 'hooks/useNetworkContext'
import { NetworkHealthState } from 'hooks/useNetworkStatus' import { NetworkHealthState } from 'hooks/useNetworkStatus'
import { useAppState } from 'AppState' import { useAppState } from 'AppState'
import { commandBarActor } from 'machines/commandBarMachine'
// This might not be necessary, AnyStateMachine from xstate is working // This might not be necessary, AnyStateMachine from xstate is working
export type AllMachines = export type AllMachines =
@ -48,7 +48,6 @@ export default function useStateMachineCommands<
allCommandsRequireNetwork = false, allCommandsRequireNetwork = false,
onCancel, onCancel,
}: UseStateMachineCommandsArgs<T, S>) { }: UseStateMachineCommandsArgs<T, S>) {
const { commandBarSend } = useCommandsContext()
const { overallState } = useNetworkContext() const { overallState } = useNetworkContext()
const { isExecuting } = useKclContext() const { isExecuting } = useKclContext()
const { isStreamReady } = useAppState() const { isStreamReady } = useAppState()
@ -76,10 +75,13 @@ export default function useStateMachineCommands<
}) })
.filter((c) => c !== null) as Command[] // TS isn't smart enough to know this filter removes nulls .filter((c) => c !== null) as Command[] // TS isn't smart enough to know this filter removes nulls
commandBarSend({ type: 'Add commands', data: { commands: newCommands } }) commandBarActor.send({
type: 'Add commands',
data: { commands: newCommands },
})
return () => { return () => {
commandBarSend({ commandBarActor.send({
type: 'Remove commands', type: 'Remove commands',
data: { commands: newCommands }, data: { commands: newCommands },
}) })

View File

@ -24,7 +24,10 @@ describe('testing AST', () => {
type: 'Literal', type: 'Literal',
start: 0, start: 0,
end: 1, end: 1,
value: {
suffix: 'None',
value: 5, value: 5,
},
raw: '5', raw: '5',
}, },
operator: '+', operator: '+',
@ -32,7 +35,10 @@ describe('testing AST', () => {
type: 'Literal', type: 'Literal',
start: 3, start: 3,
end: 4, end: 4,
value: {
suffix: 'None',
value: 6, value: 6,
},
raw: '6', raw: '6',
}, },
}, },

View File

@ -54,6 +54,9 @@ const mySketch001 = startSketchOn('XY')
}, },
], ],
id: expect.any(String), id: expect.any(String),
units: {
type: 'Mm',
},
__meta: [{ sourceRange: [46, 71, 0] }], __meta: [{ sourceRange: [46, 71, 0] }],
}, },
}) })
@ -71,6 +74,8 @@ const mySketch001 = startSketchOn('XY')
// @ts-ignore // @ts-ignore
const sketch001 = execState.memory.get('mySketch001') const sketch001 = execState.memory.get('mySketch001')
expect(sketch001).toEqual({ expect(sketch001).toEqual({
type: 'Solid',
value: {
type: 'Solid', type: 'Solid',
id: expect.any(String), id: expect.any(String),
value: [ value: [
@ -91,6 +96,9 @@ const mySketch001 = startSketchOn('XY')
], ],
sketch: { sketch: {
id: expect.any(String), id: expect.any(String),
units: {
type: 'Mm',
},
__meta: expect.any(Array), __meta: expect.any(Array),
on: expect.any(Object), on: expect.any(Object),
start: expect.any(Object), start: expect.any(Object),
@ -121,7 +129,11 @@ const mySketch001 = startSketchOn('XY')
height: 2, height: 2,
startCapId: expect.any(String), startCapId: expect.any(String),
endCapId: expect.any(String), endCapId: expect.any(String),
units: {
type: 'Mm',
},
__meta: [{ sourceRange: [46, 71, 0] }], __meta: [{ sourceRange: [46, 71, 0] }],
},
}) })
}) })
test('sketch extrude and sketch on one of the faces', async () => { test('sketch extrude and sketch on one of the faces', async () => {
@ -153,6 +165,8 @@ const sk2 = startSketchOn('XY')
const geos = [programMemory.get('theExtrude'), programMemory.get('sk2')] const geos = [programMemory.get('theExtrude'), programMemory.get('sk2')]
expect(geos).toEqual([ expect(geos).toEqual([
{ {
type: 'Solid',
value: {
type: 'Solid', type: 'Solid',
id: expect.any(String), id: expect.any(String),
value: [ value: [
@ -189,6 +203,9 @@ const sk2 = startSketchOn('XY')
on: expect.any(Object), on: expect.any(Object),
start: expect.any(Object), start: expect.any(Object),
type: 'Sketch', type: 'Sketch',
units: {
type: 'Mm',
},
tags: { tags: {
p: { p: {
__meta: [ __meta: [
@ -242,9 +259,15 @@ const sk2 = startSketchOn('XY')
height: 2, height: 2,
startCapId: expect.any(String), startCapId: expect.any(String),
endCapId: expect.any(String), endCapId: expect.any(String),
units: {
type: 'Mm',
},
__meta: [{ sourceRange: [38, 63, 0] }], __meta: [{ sourceRange: [38, 63, 0] }],
}, },
},
{ {
type: 'Solid',
value: {
type: 'Solid', type: 'Solid',
id: expect.any(String), id: expect.any(String),
value: [ value: [
@ -277,6 +300,9 @@ const sk2 = startSketchOn('XY')
], ],
sketch: { sketch: {
id: expect.any(String), id: expect.any(String),
units: {
type: 'Mm',
},
__meta: expect.any(Array), __meta: expect.any(Array),
on: expect.any(Object), on: expect.any(Object),
start: expect.any(Object), start: expect.any(Object),
@ -335,6 +361,10 @@ const sk2 = startSketchOn('XY')
startCapId: expect.any(String), startCapId: expect.any(String),
endCapId: expect.any(String), endCapId: expect.any(String),
__meta: [{ sourceRange: [342, 367, 0] }], __meta: [{ sourceRange: [342, 367, 0] }],
units: {
type: 'Mm',
},
},
}, },
]) ])
}) })

View File

@ -221,6 +221,9 @@ const newVar = myVar + 1`
}, },
], ],
id: expect.any(String), id: expect.any(String),
units: {
type: 'Mm',
},
__meta: [{ sourceRange: [39, 63, 0] }], __meta: [{ sourceRange: [39, 63, 0] }],
}, },
}) })

View File

@ -39,7 +39,7 @@ describe('Testing createLiteral', () => {
it('should create a literal', () => { it('should create a literal', () => {
const result = createLiteral(5) const result = createLiteral(5)
expect(result.type).toBe('Literal') expect(result.type).toBe('Literal')
expect(result.value).toBe(5) expect((result as any).value.value).toBe(5)
}) })
}) })
describe('Testing createIdentifier', () => { describe('Testing createIdentifier', () => {
@ -56,7 +56,7 @@ describe('Testing createCallExpression', () => {
expect(result.callee.type).toBe('Identifier') expect(result.callee.type).toBe('Identifier')
expect(result.callee.name).toBe('myFunc') expect(result.callee.name).toBe('myFunc')
expect(result.arguments[0].type).toBe('Literal') expect(result.arguments[0].type).toBe('Literal')
expect((result.arguments[0] as any).value).toBe(5) expect((result.arguments[0] as any).value.value).toBe(5)
}) })
}) })
describe('Testing createObjectExpression', () => { describe('Testing createObjectExpression', () => {
@ -68,7 +68,7 @@ describe('Testing createObjectExpression', () => {
expect(result.properties[0].type).toBe('ObjectProperty') expect(result.properties[0].type).toBe('ObjectProperty')
expect(result.properties[0].key.name).toBe('myProp') expect(result.properties[0].key.name).toBe('myProp')
expect(result.properties[0].value.type).toBe('Literal') expect(result.properties[0].value.type).toBe('Literal')
expect((result.properties[0].value as any).value).toBe(5) expect((result.properties[0].value as any).value.value).toBe(5)
}) })
}) })
describe('Testing createArrayExpression', () => { describe('Testing createArrayExpression', () => {
@ -76,7 +76,7 @@ describe('Testing createArrayExpression', () => {
const result = createArrayExpression([createLiteral(5)]) const result = createArrayExpression([createLiteral(5)])
expect(result.type).toBe('ArrayExpression') expect(result.type).toBe('ArrayExpression')
expect(result.elements[0].type).toBe('Literal') expect(result.elements[0].type).toBe('Literal')
expect((result.elements[0] as any).value).toBe(5) expect((result.elements[0] as any).value.value).toBe(5)
}) })
}) })
describe('Testing createPipeSubstitution', () => { describe('Testing createPipeSubstitution', () => {
@ -93,7 +93,7 @@ describe('Testing createVariableDeclaration', () => {
expect(result.declaration.id.type).toBe('Identifier') expect(result.declaration.id.type).toBe('Identifier')
expect(result.declaration.id.name).toBe('myVar') expect(result.declaration.id.name).toBe('myVar')
expect(result.declaration.init.type).toBe('Literal') expect(result.declaration.init.type).toBe('Literal')
expect((result.declaration.init as any).value).toBe(5) expect((result.declaration.init as any).value.value).toBe(5)
}) })
}) })
describe('Testing createPipeExpression', () => { describe('Testing createPipeExpression', () => {
@ -101,7 +101,7 @@ describe('Testing createPipeExpression', () => {
const result = createPipeExpression([createLiteral(5)]) const result = createPipeExpression([createLiteral(5)])
expect(result.type).toBe('PipeExpression') expect(result.type).toBe('PipeExpression')
expect(result.body[0].type).toBe('Literal') expect(result.body[0].type).toBe('Literal')
expect((result.body[0] as any).value).toBe(5) expect((result.body[0] as any).value.value).toBe(5)
}) })
}) })

View File

@ -743,14 +743,18 @@ export function splitPathAtPipeExpression(pathToNode: PathToNode): {
return splitPathAtPipeExpression(pathToNode.slice(0, -1)) return splitPathAtPipeExpression(pathToNode.slice(0, -1))
} }
export function createLiteral(value: LiteralValue): Node<Literal> { export function createLiteral(value: LiteralValue | number): Node<Literal> {
const raw = `${value}`
if (typeof value === 'number') {
value = { value, suffix: 'None' }
}
return { return {
type: 'Literal', type: 'Literal',
start: 0, start: 0,
end: 0, end: 0,
moduleId: 0, moduleId: 0,
value, value,
raw: `${value}`, raw,
} }
} }

View File

@ -660,7 +660,7 @@ myNestedVar = [
enter: (node, path) => { enter: (node, path) => {
if ( if (
node.type === 'Literal' && node.type === 'Literal' &&
String(node.value) === literalOfInterest String((node as any).value.value) === literalOfInterest
) { ) {
pathToNode = path pathToNode = path
} else if ( } else if (

View File

@ -717,16 +717,6 @@ function isTypeInArrayExp(
return node.elements.some((el) => isTypeInValue(el, syntaxType)) return node.elements.some((el) => isTypeInValue(el, syntaxType))
} }
export function isValueZero(val?: Expr): boolean {
return (
(val?.type === 'Literal' && Number(val.value) === 0) ||
(val?.type === 'UnaryExpression' &&
val.operator === '-' &&
val.argument.type === 'Literal' &&
Number(val.argument.value) === 0)
)
}
export function isLinesParallelAndConstrained( export function isLinesParallelAndConstrained(
ast: Program, ast: Program,
artifactGraph: ArtifactGraph, artifactGraph: ArtifactGraph,

View File

@ -1014,6 +1014,11 @@ class EngineConnection extends EventTarget {
this.pingPongSpan.pong = new Date() this.pingPongSpan.pong = new Date()
break break
case 'modeling_session_data':
let api_call_id = resp.data?.session?.api_call_id
console.log(`API Call ID: ${api_call_id}`)
break
// Only fires on successful authentication. // Only fires on successful authentication.
case 'ice_server_info': case 'ice_server_info':
let ice_servers = resp.data?.ice_servers let ice_servers = resp.data?.ice_servers

View File

@ -20,12 +20,12 @@ import {
sketchFromKclValue, sketchFromKclValue,
Literal, Literal,
SourceRange, SourceRange,
LiteralValue,
} from '../wasm' } from '../wasm'
import { import {
getNodeFromPath, getNodeFromPath,
getNodeFromPathCurry, getNodeFromPathCurry,
getNodePathFromSourceRange, getNodePathFromSourceRange,
isValueZero,
} from '../queryAst' } from '../queryAst'
import { import {
createArrayExpression, createArrayExpression,
@ -79,11 +79,32 @@ export type ConstraintType =
| 'setAngleBetween' | 'setAngleBetween'
const REF_NUM_ERR = new Error('Referenced segment does not have a to value') const REF_NUM_ERR = new Error('Referenced segment does not have a to value')
function asNum(val: LiteralValue): number | Error {
if (typeof val === 'object') return val.value
return REF_NUM_ERR
}
function forceNum(arg: Literal): number {
if (typeof arg.value === 'boolean' || typeof arg.value === 'string') {
return Number(arg.value)
} else {
return arg.value.value
}
}
function isUndef(val: any): val is undefined { function isUndef(val: any): val is undefined {
return typeof val === 'undefined' return typeof val === 'undefined'
} }
function isNum(val: any): val is number {
return typeof val === 'number' function isValueZero(val?: Expr): boolean {
return (
(val?.type === 'Literal' && forceNum(val) === 0) ||
(val?.type === 'UnaryExpression' &&
val.operator === '-' &&
val.argument.type === 'Literal' &&
Number(val.argument.value) === 0)
)
} }
function createCallWrapper( function createCallWrapper(
@ -190,7 +211,7 @@ const xyLineSetLength =
: referenceSeg : referenceSeg
? segRef ? segRef
: args[0].expr : args[0].expr
const literalARg = getArgLiteralVal(args[0].expr) const literalARg = asNum(args[0].expr.value)
if (err(literalARg)) return literalARg if (err(literalARg)) return literalARg
return createCallWrapper(xOrY, lineVal, tag, literalARg) return createCallWrapper(xOrY, lineVal, tag, literalARg)
} }
@ -211,13 +232,14 @@ const basicAngledLineCreateNode =
referencedSegment: path, referencedSegment: path,
}) => { }) => {
const refAng = path ? getAngle(path?.from, path?.to) : 0 const refAng = path ? getAngle(path?.from, path?.to) : 0
if (!isNum(args[0].expr.value)) return REF_NUM_ERR const argValue = asNum(args[0].expr.value)
if (err(argValue)) return argValue
const nonForcedAng = const nonForcedAng =
varValToUse === 'ang' varValToUse === 'ang'
? inputs[0].expr ? inputs[0].expr
: referenceSeg === 'ang' : referenceSeg === 'ang'
? getClosesAngleDirection( ? getClosesAngleDirection(
args[0].expr.value, argValue,
refAng, refAng,
createSegAngle(referenceSegName) createSegAngle(referenceSegName)
) )
@ -230,8 +252,8 @@ const basicAngledLineCreateNode =
: args[1].expr : args[1].expr
const shouldForceAng = valToForce === 'ang' && forceValueUsedInTransform const shouldForceAng = valToForce === 'ang' && forceValueUsedInTransform
const shouldForceLen = valToForce === 'len' && forceValueUsedInTransform const shouldForceLen = valToForce === 'len' && forceValueUsedInTransform
const literalArg = getArgLiteralVal( const literalArg = asNum(
valToForce === 'ang' ? args[0].expr : args[1].expr valToForce === 'ang' ? args[0].expr.value : args[1].expr.value
) )
if (err(literalArg)) return literalArg if (err(literalArg)) return literalArg
return createCallWrapper( return createCallWrapper(
@ -283,7 +305,7 @@ const getMinAndSegAngVals = (
} }
const getSignedLeg = (arg: Literal, legLenVal: BinaryPart) => const getSignedLeg = (arg: Literal, legLenVal: BinaryPart) =>
Number(arg.value) < 0 ? createUnaryExpression(legLenVal) : legLenVal forceNum(arg) < 0 ? createUnaryExpression(legLenVal) : legLenVal
const getLegAng = (ang: number, legAngleVal: BinaryPart) => { const getLegAng = (ang: number, legAngleVal: BinaryPart) => {
const normalisedAngle = ((ang % 360) + 360) % 360 // between 0 and 360 const normalisedAngle = ((ang % 360) + 360) % 360 // between 0 and 360
@ -322,8 +344,7 @@ const setHorzVertDistanceCreateNode =
referencedSegment, referencedSegment,
}) => { }) => {
const refNum = referencedSegment?.to?.[index] const refNum = referencedSegment?.to?.[index]
const literalArg = getArgLiteralVal(args?.[index].expr) const literalArg = asNum(args?.[index].expr.value)
if (err(literalArg)) return literalArg
if (isUndef(refNum) || err(literalArg)) return REF_NUM_ERR if (isUndef(refNum) || err(literalArg)) return REF_NUM_ERR
const valueUsedInTransform = roundOff(literalArg - refNum, 2) const valueUsedInTransform = roundOff(literalArg - refNum, 2)
@ -352,7 +373,7 @@ const setHorzVertDistanceForAngleLineCreateNode =
referencedSegment, referencedSegment,
}) => { }) => {
const refNum = referencedSegment?.to?.[index] const refNum = referencedSegment?.to?.[index]
const literalArg = getArgLiteralVal(args?.[1].expr) const literalArg = asNum(args?.[1].expr.value)
if (isUndef(refNum) || err(literalArg)) return REF_NUM_ERR if (isUndef(refNum) || err(literalArg)) return REF_NUM_ERR
const valueUsedInTransform = roundOff(literalArg - refNum, 2) const valueUsedInTransform = roundOff(literalArg - refNum, 2)
const binExp = createBinaryExpressionWithUnary([ const binExp = createBinaryExpressionWithUnary([
@ -374,8 +395,8 @@ const setAbsDistanceCreateNode =
index = xOrY === 'x' ? 0 : 1 index = xOrY === 'x' ? 0 : 1
): CreateStdLibSketchCallExpr => ): CreateStdLibSketchCallExpr =>
({ tag, forceValueUsedInTransform, rawArgs: args }) => { ({ tag, forceValueUsedInTransform, rawArgs: args }) => {
const literalArg = getArgLiteralVal(args?.[index].expr) const literalArg = asNum(args?.[index].expr.value)
if (err(literalArg)) return REF_NUM_ERR if (err(literalArg)) return literalArg
const valueUsedInTransform = roundOff(literalArg, 2) const valueUsedInTransform = roundOff(literalArg, 2)
const val = forceValueUsedInTransform || createLiteral(valueUsedInTransform) const val = forceValueUsedInTransform || createLiteral(valueUsedInTransform)
if (isXOrYLine) { if (isXOrYLine) {
@ -396,8 +417,8 @@ const setAbsDistanceCreateNode =
const setAbsDistanceForAngleLineCreateNode = const setAbsDistanceForAngleLineCreateNode =
(xOrY: 'x' | 'y'): CreateStdLibSketchCallExpr => (xOrY: 'x' | 'y'): CreateStdLibSketchCallExpr =>
({ tag, forceValueUsedInTransform, inputs, rawArgs: args }) => { ({ tag, forceValueUsedInTransform, inputs, rawArgs: args }) => {
const literalArg = getArgLiteralVal(args?.[1].expr) const literalArg = asNum(args?.[1].expr.value)
if (err(literalArg)) return REF_NUM_ERR if (err(literalArg)) return literalArg
const valueUsedInTransform = roundOff(literalArg, 2) const valueUsedInTransform = roundOff(literalArg, 2)
const val = forceValueUsedInTransform || createLiteral(valueUsedInTransform) const val = forceValueUsedInTransform || createLiteral(valueUsedInTransform)
return createCallWrapper( return createCallWrapper(
@ -419,7 +440,7 @@ const setHorVertDistanceForXYLines =
}) => { }) => {
const index = xOrY === 'x' ? 0 : 1 const index = xOrY === 'x' ? 0 : 1
const refNum = referencedSegment?.to?.[index] const refNum = referencedSegment?.to?.[index]
const literalArg = getArgLiteralVal(args?.[index].expr) const literalArg = asNum(args?.[index].expr.value)
if (isUndef(refNum) || err(literalArg)) return REF_NUM_ERR if (isUndef(refNum) || err(literalArg)) return REF_NUM_ERR
const valueUsedInTransform = roundOff(literalArg - refNum, 2) const valueUsedInTransform = roundOff(literalArg - refNum, 2)
const makeBinExp = createBinaryExpressionWithUnary([ const makeBinExp = createBinaryExpressionWithUnary([
@ -445,9 +466,9 @@ const setHorzVertDistanceConstraintLineCreateNode =
]) ])
const makeBinExp = (index: 0 | 1) => { const makeBinExp = (index: 0 | 1) => {
const arg = getArgLiteralVal(args?.[index].expr) const arg = asNum(args?.[index].expr.value)
const refNum = referencedSegment?.to?.[index] const refNum = referencedSegment?.to?.[index]
if (err(arg) || !isNum(refNum)) return REF_NUM_ERR if (err(arg) || isUndef(refNum)) return REF_NUM_ERR
return createBinaryExpressionWithUnary([ return createBinaryExpressionWithUnary([
createSegEnd(referenceSegName, isX), createSegEnd(referenceSegName, isX),
createLiteral(roundOff(arg - refNum, 2)), createLiteral(roundOff(arg - refNum, 2)),
@ -468,9 +489,9 @@ const setAngledIntersectLineForLines: CreateStdLibSketchCallExpr = ({
forceValueUsedInTransform, forceValueUsedInTransform,
rawArgs: args, rawArgs: args,
}) => { }) => {
const val = args[1].expr.value, const val = asNum(args[1].expr.value),
angle = args[0].expr.value angle = asNum(args[0].expr.value)
if (!isNum(val) || !isNum(angle)) return REF_NUM_ERR if (err(val) || err(angle)) return REF_NUM_ERR
const valueUsedInTransform = roundOff(val, 2) const valueUsedInTransform = roundOff(val, 2)
const varNamMap: { [key: number]: string } = { const varNamMap: { [key: number]: string } = {
0: 'ZERO', 0: 'ZERO',
@ -498,8 +519,8 @@ const setAngledIntersectForAngledLines: CreateStdLibSketchCallExpr = ({
inputs, inputs,
rawArgs: args, rawArgs: args,
}) => { }) => {
const val = args[1].expr.value const val = asNum(args[1].expr.value)
if (!isNum(val)) return REF_NUM_ERR if (err(val)) return val
const valueUsedInTransform = roundOff(val, 2) const valueUsedInTransform = roundOff(val, 2)
return intersectCallWrapper({ return intersectCallWrapper({
fnName: 'angledLineThatIntersects', fnName: 'angledLineThatIntersects',
@ -524,8 +545,8 @@ const setAngleBetweenCreateNode =
const refAngle = referencedSegment const refAngle = referencedSegment
? getAngle(referencedSegment?.from, referencedSegment?.to) ? getAngle(referencedSegment?.from, referencedSegment?.to)
: 0 : 0
const val = args[0].expr.value const val = asNum(args[0].expr.value)
if (!isNum(val)) return REF_NUM_ERR if (err(val)) return val
let valueUsedInTransform = roundOff(normaliseAngle(val - refAngle)) let valueUsedInTransform = roundOff(normaliseAngle(val - refAngle))
let firstHalfValue = createSegAngle(referenceSegName) let firstHalfValue = createSegAngle(referenceSegName)
if (Math.abs(valueUsedInTransform) > 90) { if (Math.abs(valueUsedInTransform) > 90) {
@ -706,13 +727,11 @@ const transformMap: TransformMap = {
createPipeSubstitution(), createPipeSubstitution(),
] ]
) )
if (!isNum(args[0].expr.value)) return REF_NUM_ERR const val = asNum(args[0].expr.value)
if (err(val)) return val
return createCallWrapper( return createCallWrapper(
'angledLineToX', 'angledLineToX',
[ [getAngleLengthSign(val, angleToMatchLengthXCall), inputs[0].expr],
getAngleLengthSign(args[0].expr.value, angleToMatchLengthXCall),
inputs[0].expr,
],
tag tag
) )
}, },
@ -739,13 +758,11 @@ const transformMap: TransformMap = {
createPipeSubstitution(), createPipeSubstitution(),
] ]
) )
if (!isNum(args[0].expr.value)) return REF_NUM_ERR const val = asNum(args[0].expr.value)
if (err(val)) return val
return createCallWrapper( return createCallWrapper(
'angledLineToY', 'angledLineToY',
[ [getAngleLengthSign(val, angleToMatchLengthYCall), inputs[1].expr],
getAngleLengthSign(args[0].expr.value, angleToMatchLengthYCall),
inputs[1].expr,
],
tag tag
) )
}, },
@ -763,7 +780,7 @@ const transformMap: TransformMap = {
forceValueUsedInTransform, forceValueUsedInTransform,
rawArgs: args, rawArgs: args,
}) => { }) => {
const val = getArgLiteralVal(args[0].expr) const val = asNum(args[0].expr.value)
if (err(val)) return val if (err(val)) return val
return createCallWrapper( return createCallWrapper(
'angledLineToY', 'angledLineToY',
@ -844,7 +861,7 @@ const transformMap: TransformMap = {
tooltip: 'yLine', tooltip: 'yLine',
createNode: ({ inputs, tag, rawArgs: args }) => { createNode: ({ inputs, tag, rawArgs: args }) => {
const expr = inputs[1].expr const expr = inputs[1].expr
if (Number(args[0].expr.value) >= 0) if (forceNum(args[0].expr) >= 0)
return createCallWrapper('yLine', expr, tag) return createCallWrapper('yLine', expr, tag)
if (isExprBinaryPart(expr)) if (isExprBinaryPart(expr))
return createCallWrapper('yLine', createUnaryExpression(expr), tag) return createCallWrapper('yLine', createUnaryExpression(expr), tag)
@ -856,7 +873,7 @@ const transformMap: TransformMap = {
tooltip: 'xLine', tooltip: 'xLine',
createNode: ({ inputs, tag, rawArgs: args }) => { createNode: ({ inputs, tag, rawArgs: args }) => {
const expr = inputs[1].expr const expr = inputs[1].expr
if (Number(args[0].expr.value) >= 0) if (forceNum(args[0].expr) >= 0)
return createCallWrapper('xLine', expr, tag) return createCallWrapper('xLine', expr, tag)
if (isExprBinaryPart(expr)) if (isExprBinaryPart(expr))
return createCallWrapper('xLine', createUnaryExpression(expr), tag) return createCallWrapper('xLine', createUnaryExpression(expr), tag)
@ -900,10 +917,11 @@ const transformMap: TransformMap = {
referenceSegName, referenceSegName,
getInputOfType(inputs, 'xRelative').expr getInputOfType(inputs, 'xRelative').expr
) )
if (!isNum(args[0].expr.value)) return REF_NUM_ERR const val = asNum(args[0].expr.value)
if (err(val)) return val
return createCallWrapper( return createCallWrapper(
'angledLineOfXLength', 'angledLineOfXLength',
[getLegAng(args[0].expr.value, legAngle), minVal], [getLegAng(val, legAngle), minVal],
tag tag
) )
}, },
@ -912,7 +930,7 @@ const transformMap: TransformMap = {
tooltip: 'xLine', tooltip: 'xLine',
createNode: ({ inputs, tag, rawArgs: args }) => { createNode: ({ inputs, tag, rawArgs: args }) => {
const expr = inputs[1].expr const expr = inputs[1].expr
if (Number(args[0].expr.value) >= 0) if (forceNum(args[0].expr) >= 0)
return createCallWrapper('xLine', expr, tag) return createCallWrapper('xLine', expr, tag)
if (isExprBinaryPart(expr)) if (isExprBinaryPart(expr))
return createCallWrapper('xLine', createUnaryExpression(expr), tag) return createCallWrapper('xLine', createUnaryExpression(expr), tag)
@ -953,10 +971,11 @@ const transformMap: TransformMap = {
inputs[1].expr, inputs[1].expr,
'legAngY' 'legAngY'
) )
if (!isNum(args[0].expr.value)) return REF_NUM_ERR const val = asNum(args[0].expr.value)
if (err(val)) return val
return createCallWrapper( return createCallWrapper(
'angledLineOfXLength', 'angledLineOfXLength',
[getLegAng(args[0].expr.value, legAngle), minVal], [getLegAng(val, legAngle), minVal],
tag tag
) )
}, },
@ -965,7 +984,7 @@ const transformMap: TransformMap = {
tooltip: 'yLine', tooltip: 'yLine',
createNode: ({ inputs, tag, rawArgs: args }) => { createNode: ({ inputs, tag, rawArgs: args }) => {
const expr = inputs[1].expr const expr = inputs[1].expr
if (Number(args[0].expr.value) >= 0) if (forceNum(args[0].expr) >= 0)
return createCallWrapper('yLine', expr, tag) return createCallWrapper('yLine', expr, tag)
if (isExprBinaryPart(expr)) if (isExprBinaryPart(expr))
return createCallWrapper('yLine', createUnaryExpression(expr), tag) return createCallWrapper('yLine', createUnaryExpression(expr), tag)
@ -1005,13 +1024,11 @@ const transformMap: TransformMap = {
createPipeSubstitution(), createPipeSubstitution(),
] ]
) )
if (!isNum(args[0].expr.value)) return REF_NUM_ERR const val = asNum(args[0].expr.value)
if (err(val)) return val
return createCallWrapper( return createCallWrapper(
'angledLineToX', 'angledLineToX',
[ [getAngleLengthSign(val, angleToMatchLengthXCall), inputs[1].expr],
getAngleLengthSign(args[0].expr.value, angleToMatchLengthXCall),
inputs[1].expr,
],
tag tag
) )
}, },
@ -1057,13 +1074,11 @@ const transformMap: TransformMap = {
createPipeSubstitution(), createPipeSubstitution(),
] ]
) )
if (!isNum(args[0].expr.value)) return REF_NUM_ERR const val = asNum(args[0].expr.value)
if (err(val)) return val
return createCallWrapper( return createCallWrapper(
'angledLineToY', 'angledLineToY',
[ [getAngleLengthSign(val, angleToMatchLengthXCall), inputs[1].expr],
getAngleLengthSign(args[0].expr.value, angleToMatchLengthXCall),
inputs[1].expr,
],
tag tag
) )
}, },
@ -1080,7 +1095,7 @@ const transformMap: TransformMap = {
equalLength: { equalLength: {
tooltip: 'xLine', tooltip: 'xLine',
createNode: ({ referenceSegName, tag, rawArgs: args }) => { createNode: ({ referenceSegName, tag, rawArgs: args }) => {
const argVal = getArgLiteralVal(args[0].expr) const argVal = asNum(args[0].expr.value)
if (err(argVal)) return argVal if (err(argVal)) return argVal
const segLen = createSegLen(referenceSegName) const segLen = createSegLen(referenceSegName)
if (argVal > 0) return createCallWrapper('xLine', segLen, tag, argVal) if (argVal > 0) return createCallWrapper('xLine', segLen, tag, argVal)
@ -1118,7 +1133,7 @@ const transformMap: TransformMap = {
equalLength: { equalLength: {
tooltip: 'yLine', tooltip: 'yLine',
createNode: ({ referenceSegName, tag, rawArgs: args }) => { createNode: ({ referenceSegName, tag, rawArgs: args }) => {
const argVal = getArgLiteralVal(args[0].expr) const argVal = asNum(args[0].expr.value)
if (err(argVal)) return argVal if (err(argVal)) return argVal
let segLen = createSegLen(referenceSegName) let segLen = createSegLen(referenceSegName)
if (argVal < 0) segLen = createUnaryExpression(segLen) if (argVal < 0) segLen = createUnaryExpression(segLen)
@ -1714,7 +1729,7 @@ export function transformAstSketchLines({
let kclVal = programMemory.get(varName) let kclVal = programMemory.get(varName)
let sketch let sketch
if (kclVal?.type === 'Solid') { if (kclVal?.type === 'Solid') {
sketch = kclVal.sketch sketch = kclVal.value.sketch
} else { } else {
sketch = sketchFromKclValue(kclVal, varName) sketch = sketchFromKclValue(kclVal, varName)
if (err(sketch)) { if (err(sketch)) {
@ -1823,11 +1838,6 @@ function createLastSeg(isX: boolean): Node<CallExpression> {
]) ])
} }
function getArgLiteralVal(arg: Literal): number | Error {
if (!isNum(arg.value)) return REF_NUM_ERR
return arg.value
}
export type ConstraintLevel = 'free' | 'partial' | 'full' export type ConstraintLevel = 'free' | 'partial' | 'full'
export function getConstraintLevelFromSourceRange( export function getConstraintLevelFromSourceRange(

View File

@ -539,7 +539,8 @@ export function sketchFromKclValueOptional(
): Sketch | Reason { ): Sketch | Reason {
if (obj?.value?.type === 'Sketch') return obj.value if (obj?.value?.type === 'Sketch') return obj.value
if (obj?.value?.type === 'Solid') return obj.value.sketch if (obj?.value?.type === 'Solid') return obj.value.sketch
if (obj?.type === 'Solid') return obj.sketch if (obj?.type === 'Sketch') return obj.value
if (obj?.type === 'Solid') return obj.value.sketch
if (!varName) { if (!varName) {
varName = 'a KCL value' varName = 'a KCL value'
} }

View File

@ -13,6 +13,7 @@ import {
loftValidator, loftValidator,
revolveAxisValidator, revolveAxisValidator,
shellValidator, shellValidator,
sweepValidator,
} from './validators' } from './validators'
type OutputFormat = Models['OutputFormat_type'] type OutputFormat = Models['OutputFormat_type']
@ -42,8 +43,8 @@ export type ModelingCommandSchema = {
distance: KclCommandValue distance: KclCommandValue
} }
Sweep: { Sweep: {
path: Selections target: Selections
profile: Selections trajectory: Selections
} }
Loft: { Loft: {
selection: Selections selection: Selections
@ -308,25 +309,24 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
'Create a 3D body by moving a sketch region along an arbitrary path.', 'Create a 3D body by moving a sketch region along an arbitrary path.',
icon: 'sweep', icon: 'sweep',
status: 'development', status: 'development',
needsReview: true, needsReview: false,
args: { args: {
profile: { target: {
inputType: 'selection', inputType: 'selection',
selectionTypes: ['solid2d'], selectionTypes: ['solid2d', 'plane'],
required: true, required: true,
skip: true, skip: true,
multiple: false, multiple: false,
// TODO: add dry-run validation
warningMessage: warningMessage:
'The sweep workflow is new and under tested. Please break it and report issues.', 'The sweep workflow is new and under tested. Please break it and report issues.',
}, },
path: { trajectory: {
inputType: 'selection', inputType: 'selection',
selectionTypes: ['segment', 'path'], selectionTypes: ['segment', 'plane'],
required: true, required: true,
skip: true, skip: false,
multiple: false, multiple: false,
// TODO: add dry-run validation validation: sweepValidator,
}, },
}, },
}, },

View File

@ -207,3 +207,71 @@ export const shellValidator = async ({
return 'Unable to shell with the provided selection' return 'Unable to shell with the provided selection'
} }
export const sweepValidator = async ({
context,
data,
}: {
context: CommandBarContext
data: { trajectory: Selections }
}): Promise<boolean | string> => {
if (!isSelections(data.trajectory)) {
console.log('Unable to sweep, selections are missing')
return 'Unable to sweep, selections are missing'
}
// Retrieve the parent path from the segment selection directly
const trajectoryArtifact = data.trajectory.graphSelections[0].artifact
let trajectory: string | undefined = undefined
if (trajectoryArtifact && trajectoryArtifact.type === 'segment') {
trajectory = trajectoryArtifact.pathId
} else if (trajectoryArtifact && trajectoryArtifact.type === 'plane') {
// TODO: check again after multi profile
trajectory = trajectoryArtifact.pathIds[0]
}
if (!trajectory) {
return "Unable to sweep, couldn't find the trajectory artifact"
}
// Get the former arg in the command bar flow, and retrieve the path from the solid2d directly
const targetArg = context.argumentsToSubmit['target'] as Selections
const targetArtifact = targetArg.graphSelections[0].artifact
let target: string | undefined = undefined
if (targetArtifact && targetArtifact.type === 'solid2D') {
target = targetArtifact.pathId
} else if (targetArtifact && targetArtifact.type === 'plane') {
target = targetArtifact.pathIds[0]
}
if (!target) {
return "Unable to sweep, couldn't find the profile artifact"
}
const sweepCommand = async () => {
// TODO: second look on defaults here
const DEFAULT_TOLERANCE: Models['LengthUnit_type'] = 1e-7
const DEFAULT_SECTIONAL = false
const cmdArgs = {
target,
trajectory,
sectional: DEFAULT_SECTIONAL,
tolerance: DEFAULT_TOLERANCE,
}
return await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'sweep',
...cmdArgs,
},
})
}
const attemptSweep = await dryRunWrapper(sweepCommand)
if (attemptSweep?.success) {
return true
}
return 'Unable to sweep with the provided selection'
}

View File

@ -68,10 +68,6 @@ interface TextToKclProps {
data?: unknown data?: unknown
) => unknown ) => unknown
navigate: NavigateFunction navigate: NavigateFunction
commandBarSend: (
type: EventFrom<typeof commandBarMachine>,
data?: unknown
) => unknown
context: ContextFrom<typeof fileMachine> context: ContextFrom<typeof fileMachine>
token?: string token?: string
settings: { settings: {
@ -84,7 +80,6 @@ export async function submitAndAwaitTextToKcl({
trimmedPrompt, trimmedPrompt,
fileMachineSend, fileMachineSend,
navigate, navigate,
commandBarSend,
context, context,
token, token,
settings, settings,
@ -96,7 +91,6 @@ export async function submitAndAwaitTextToKcl({
ToastTextToCadError({ ToastTextToCadError({
toastId, toastId,
message, message,
commandBarSend,
prompt: trimmedPrompt, prompt: trimmedPrompt,
}), }),
{ {
@ -195,7 +189,7 @@ export async function submitAndAwaitTextToKcl({
.toLowerCase()}${FILE_EXT}` .toLowerCase()}${FILE_EXT}`
if (isDesktop()) { if (isDesktop()) {
// We have to pre-emptively run our unique file name logic, // We have to preemptively run our unique file name logic,
// so that we can pass the unique file name to the toast, // so that we can pass the unique file name to the toast,
// and by extension the file-deletion-on-reject logic. // and by extension the file-deletion-on-reject logic.
newFileName = getNextFileName({ newFileName = getNextFileName({

View File

@ -1,6 +1,6 @@
import { CustomIconName } from 'components/CustomIcon' import { CustomIconName } from 'components/CustomIcon'
import { DEV } from 'env' import { DEV } from 'env'
import { commandBarMachine } from 'machines/commandBarMachine' import { commandBarActor, commandBarMachine } from 'machines/commandBarMachine'
import { import {
canRectangleOrCircleTool, canRectangleOrCircleTool,
isClosedSketch, isClosedSketch,
@ -21,7 +21,6 @@ type ToolbarMode = {
export interface ToolbarItemCallbackProps { export interface ToolbarItemCallbackProps {
modelingState: StateFrom<typeof modelingMachine> modelingState: StateFrom<typeof modelingMachine>
modelingSend: (event: EventFrom<typeof modelingMachine>) => void modelingSend: (event: EventFrom<typeof modelingMachine>) => void
commandBarSend: (event: EventFrom<typeof commandBarMachine>) => void
sketchPathId: string | false sketchPathId: string | false
} }
@ -84,8 +83,8 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
'break', 'break',
{ {
id: 'extrude', id: 'extrude',
onClick: ({ commandBarSend }) => onClick: () =>
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: { name: 'Extrude', groupId: 'modeling' }, data: { name: 'Extrude', groupId: 'modeling' },
}), }),
@ -98,8 +97,8 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
}, },
{ {
id: 'revolve', id: 'revolve',
onClick: ({ commandBarSend }) => onClick: () =>
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: { name: 'Revolve', groupId: 'modeling' }, data: { name: 'Revolve', groupId: 'modeling' },
}), }),
@ -119,8 +118,8 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
}, },
{ {
id: 'sweep', id: 'sweep',
onClick: ({ commandBarSend }) => onClick: () =>
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: { name: 'Sweep', groupId: 'modeling' }, data: { name: 'Sweep', groupId: 'modeling' },
}), }),
@ -139,8 +138,8 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
}, },
{ {
id: 'loft', id: 'loft',
onClick: ({ commandBarSend }) => onClick: () =>
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: { name: 'Loft', groupId: 'modeling' }, data: { name: 'Loft', groupId: 'modeling' },
}), }),
@ -160,8 +159,8 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
'break', 'break',
{ {
id: 'fillet3d', id: 'fillet3d',
onClick: ({ commandBarSend }) => onClick: () =>
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: { name: 'Fillet', groupId: 'modeling' }, data: { name: 'Fillet', groupId: 'modeling' },
}), }),
@ -174,8 +173,8 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
}, },
{ {
id: 'chamfer3d', id: 'chamfer3d',
onClick: ({ commandBarSend }) => onClick: () =>
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: { name: 'Chamfer', groupId: 'modeling' }, data: { name: 'Chamfer', groupId: 'modeling' },
}), }),
@ -188,8 +187,8 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
}, },
{ {
id: 'shell', id: 'shell',
onClick: ({ commandBarSend }) => { onClick: () => {
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: { name: 'Shell', groupId: 'modeling' }, data: { name: 'Shell', groupId: 'modeling' },
}) })
@ -269,8 +268,8 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
[ [
{ {
id: 'plane-offset', id: 'plane-offset',
onClick: ({ commandBarSend }) => { onClick: () => {
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: { name: 'Offset plane', groupId: 'modeling' }, data: { name: 'Offset plane', groupId: 'modeling' },
}) })
@ -280,7 +279,12 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
status: 'available', status: 'available',
title: 'Offset plane', title: 'Offset plane',
description: 'Create a plane parallel to an existing plane.', description: 'Create a plane parallel to an existing plane.',
links: [], links: [
{
label: 'KCL docs',
url: 'https://zoo.dev/docs/kcl/offsetPlane',
},
],
}, },
{ {
id: 'plane-points', id: 'plane-points',
@ -296,8 +300,8 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
[ [
{ {
id: 'text-to-cad', id: 'text-to-cad',
onClick: ({ commandBarSend }) => onClick: () =>
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: { name: 'Text-to-CAD', groupId: 'modeling' }, data: { name: 'Text-to-CAD', groupId: 'modeling' },
}), }),
@ -305,12 +309,17 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
status: 'available', status: 'available',
title: 'Text-to-CAD', title: 'Text-to-CAD',
description: 'Generate geometry from a text prompt.', description: 'Generate geometry from a text prompt.',
links: [], links: [
{
label: 'API docs',
url: 'https://zoo.dev/docs/api/ml/generate-a-cad-model-from-text',
},
],
}, },
{ {
id: 'prompt-to-edit', id: 'prompt-to-edit',
onClick: ({ commandBarSend }) => onClick: () =>
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: { name: 'Prompt-to-edit', groupId: 'modeling' }, data: { name: 'Prompt-to-edit', groupId: 'modeling' },
}), }),
@ -583,8 +592,8 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
{ {
id: 'constraint-length', id: 'constraint-length',
disabled: (state) => !state.matches({ Sketch: 'SketchIdle' }), disabled: (state) => !state.matches({ Sketch: 'SketchIdle' }),
onClick: ({ commandBarSend }) => onClick: () =>
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: { data: {
name: 'Constrain length', name: 'Constrain length',

View File

@ -1,4 +1,4 @@
import { assign, fromPromise, setup } from 'xstate' import { assign, createActor, fromPromise, setup, SnapshotFrom } from 'xstate'
import { import {
Command, Command,
CommandArgument, CommandArgument,
@ -9,6 +9,7 @@ import { Selections__old } from 'lib/selections'
import { getCommandArgumentKclValuesOnly } from 'lib/commandUtils' import { getCommandArgumentKclValuesOnly } from 'lib/commandUtils'
import { MachineManager } from 'components/MachineManagerProvider' import { MachineManager } from 'components/MachineManagerProvider'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import { useSelector } from '@xstate/react'
export type CommandBarContext = { export type CommandBarContext = {
commands: Command[] commands: Command[]
@ -247,8 +248,17 @@ export const commandBarMachine = setup({
guards: { guards: {
'Command needs review': ({ context }) => 'Command needs review': ({ context }) =>
context.selectedCommand?.needsReview || false, context.selectedCommand?.needsReview || false,
'Command has no arguments': () => false, 'Command has no arguments': ({ context }) => {
'All arguments are skippable': () => false, return (
!context.selectedCommand?.args ||
Object.keys(context.selectedCommand?.args).length === 0
)
},
'All arguments are skippable': ({ context }) => {
return Object.values(context.selectedCommand!.args!).every(
(argConfig) => argConfig.skip
)
},
'Has selected command': ({ context }) => !!context.selectedCommand, 'Has selected command': ({ context }) => !!context.selectedCommand,
}, },
actors: { actors: {
@ -620,3 +630,12 @@ function sortCommands(a: Command, b: Command) {
if (a.groupId === 'settings' && !(b.groupId === 'settings')) return 1 if (a.groupId === 'settings' && !(b.groupId === 'settings')) return 1
return a.name.localeCompare(b.name) return a.name.localeCompare(b.name)
} }
export const commandBarActor = createActor(commandBarMachine).start()
/** Basic state snapshot selector */
const cmdBarStateSelector = (state: SnapshotFrom<typeof commandBarActor>) =>
state
export const useCommandBarState = () => {
return useSelector(commandBarActor, cmdBarStateSelector)
}

View File

@ -1561,40 +1561,40 @@ export const modelingMachine = setup({
if (!input) return new Error('No input provided') if (!input) return new Error('No input provided')
// Extract inputs // Extract inputs
const ast = kclManager.ast const ast = kclManager.ast
const { profile, path } = input const { target, trajectory } = input
// Find the profile declaration // Find the profile declaration
const profileNodePath = getNodePathFromSourceRange( const targetNodePath = getNodePathFromSourceRange(
ast, ast,
profile.graphSelections[0].codeRef.range target.graphSelections[0].codeRef.range
) )
const profileNode = getNodeFromPath<VariableDeclarator>( const targetNode = getNodeFromPath<VariableDeclarator>(
ast, ast,
profileNodePath, targetNodePath,
'VariableDeclarator' 'VariableDeclarator'
) )
if (err(profileNode)) { if (err(targetNode)) {
return new Error("Couldn't parse profile selection") return new Error("Couldn't parse profile selection")
} }
const profileDeclarator = profileNode.node const targetDeclarator = targetNode.node
// Find the path declaration // Find the path declaration
const pathNodePath = getNodePathFromSourceRange( const trajectoryNodePath = getNodePathFromSourceRange(
ast, ast,
path.graphSelections[0].codeRef.range trajectory.graphSelections[0].codeRef.range
) )
const pathNode = getNodeFromPath<VariableDeclarator>( const trajectoryNode = getNodeFromPath<VariableDeclarator>(
ast, ast,
pathNodePath, trajectoryNodePath,
'VariableDeclarator' 'VariableDeclarator'
) )
if (err(pathNode)) { if (err(trajectoryNode)) {
return new Error("Couldn't parse path selection") return new Error("Couldn't parse path selection")
} }
const pathDeclarator = pathNode.node const trajectoryDeclarator = trajectoryNode.node
// Perform the sweep // Perform the sweep
const sweepRes = addSweep(ast, profileDeclarator, pathDeclarator) const sweepRes = addSweep(ast, targetDeclarator, trajectoryDeclarator)
const updateAstResult = await kclManager.updateAst( const updateAstResult = await kclManager.updateAst(
sweepRes.modifiedAst, sweepRes.modifiedAst,
true, true,

View File

@ -24,13 +24,12 @@ import { markOnce } from 'lib/performance'
import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher' import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher'
import { useProjectsLoader } from 'hooks/useProjectsLoader' import { useProjectsLoader } from 'hooks/useProjectsLoader'
import { useProjectsContext } from 'hooks/useProjectsContext' import { useProjectsContext } from 'hooks/useProjectsContext'
import { useCommandsContext } from 'hooks/useCommandsContext' import { commandBarActor } from 'machines/commandBarMachine'
// This route only opens in the desktop context for now, // This route only opens in the desktop context for now,
// as defined in Router.tsx, so we can use the desktop APIs and types. // as defined in Router.tsx, so we can use the desktop APIs and types.
const Home = () => { const Home = () => {
const { state, send } = useProjectsContext() const { state, send } = useProjectsContext()
const { commandBarSend } = useCommandsContext()
const [projectsLoaderTrigger, setProjectsLoaderTrigger] = useState(0) const [projectsLoaderTrigger, setProjectsLoaderTrigger] = useState(0)
const { projectsDir } = useProjectsLoader([projectsLoaderTrigger]) const { projectsDir } = useProjectsLoader([projectsLoaderTrigger])
@ -128,7 +127,7 @@ const Home = () => {
<ActionButton <ActionButton
Element="button" Element="button"
onClick={() => onClick={() =>
commandBarSend({ commandBarActor.send({
type: 'Find and select command', type: 'Find and select command',
data: { data: {
groupId: 'projects', groupId: 'projects',

View File

@ -370,8 +370,6 @@ impl From<KclError> for pyo3::PyErr {
pub struct CompilationError { pub struct CompilationError {
#[serde(rename = "sourceRange")] #[serde(rename = "sourceRange")]
pub source_range: SourceRange, pub source_range: SourceRange,
#[serde(rename = "contextRange")]
pub context_range: Option<SourceRange>,
pub message: String, pub message: String,
pub suggestion: Option<Suggestion>, pub suggestion: Option<Suggestion>,
pub severity: Severity, pub severity: Severity,
@ -382,7 +380,6 @@ impl CompilationError {
pub(crate) fn err(source_range: SourceRange, message: impl ToString) -> CompilationError { pub(crate) fn err(source_range: SourceRange, message: impl ToString) -> CompilationError {
CompilationError { CompilationError {
source_range, source_range,
context_range: None,
message: message.to_string(), message: message.to_string(),
suggestion: None, suggestion: None,
severity: Severity::Error, severity: Severity::Error,
@ -393,7 +390,6 @@ impl CompilationError {
pub(crate) fn fatal(source_range: SourceRange, message: impl ToString) -> CompilationError { pub(crate) fn fatal(source_range: SourceRange, message: impl ToString) -> CompilationError {
CompilationError { CompilationError {
source_range, source_range,
context_range: None,
message: message.to_string(), message: message.to_string(),
suggestion: None, suggestion: None,
severity: Severity::Fatal, severity: Severity::Fatal,
@ -402,22 +398,18 @@ impl CompilationError {
} }
pub(crate) fn with_suggestion( pub(crate) fn with_suggestion(
source_range: SourceRange, self,
context_range: Option<SourceRange>, suggestion_title: impl ToString,
message: impl ToString, suggestion_insert: impl ToString,
suggestion: Option<(impl ToString, impl ToString)>,
tag: Tag, tag: Tag,
) -> CompilationError { ) -> CompilationError {
CompilationError { CompilationError {
source_range, suggestion: Some(Suggestion {
context_range, title: suggestion_title.to_string(),
message: message.to_string(), insert: suggestion_insert.to_string(),
suggestion: suggestion.map(|(t, i)| Suggestion {
title: t.to_string(),
insert: i.to_string(),
}), }),
severity: Severity::Error,
tag, tag,
..self
} }
} }

View File

@ -11,6 +11,11 @@ pub(super) const SETTINGS: &str = "settings";
pub(super) const SETTINGS_UNIT_LENGTH: &str = "defaultLengthUnit"; pub(super) const SETTINGS_UNIT_LENGTH: &str = "defaultLengthUnit";
pub(super) const SETTINGS_UNIT_ANGLE: &str = "defaultAngleUnit"; pub(super) const SETTINGS_UNIT_ANGLE: &str = "defaultAngleUnit";
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(super) enum AnnotationScope {
Module,
}
pub(super) fn expect_properties<'a>( pub(super) fn expect_properties<'a>(
for_key: &'static str, for_key: &'static str,
annotation: &'a NonCodeValue, annotation: &'a NonCodeValue,

View File

@ -121,8 +121,8 @@ impl Node<MemberExpression> {
source_ranges: vec![self.clone().into()], source_ranges: vec![self.clone().into()],
})) }))
} }
(KclValue::Solid(solid), Property::String(prop)) if prop == "sketch" => Ok(KclValue::Sketch { (KclValue::Solid { value }, Property::String(prop)) if prop == "sketch" => Ok(KclValue::Sketch {
value: Box::new(solid.sketch), value: Box::new(value.sketch),
}), }),
(KclValue::Sketch { value: sk }, Property::String(prop)) if prop == "tags" => Ok(KclValue::Object { (KclValue::Sketch { value: sk }, Property::String(prop)) if prop == "tags" => Ok(KclValue::Object {
meta: vec![Metadata { meta: vec![Metadata {
@ -662,11 +662,11 @@ fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut Ex
exec_state.mut_memory().update_tag(&tag.value, tag.clone())?; exec_state.mut_memory().update_tag(&tag.value, tag.clone())?;
} }
} }
KclValue::Solid(ref mut solid) => { KclValue::Solid { ref mut value } => {
for value in &solid.value { for v in &value.value {
if let Some(tag) = value.get_tag() { if let Some(tag) = v.get_tag() {
// Get the past tag and update it. // Get the past tag and update it.
let mut t = if let Some(t) = solid.sketch.tags.get(&tag.name) { let mut t = if let Some(t) = value.sketch.tags.get(&tag.name) {
t.clone() t.clone()
} else { } else {
// It's probably a fillet or a chamfer. // It's probably a fillet or a chamfer.
@ -674,10 +674,10 @@ fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut Ex
TagIdentifier { TagIdentifier {
value: tag.name.clone(), value: tag.name.clone(),
info: Some(TagEngineInfo { info: Some(TagEngineInfo {
id: value.get_id(), id: v.get_id(),
surface: Some(value.clone()), surface: Some(v.clone()),
path: None, path: None,
sketch: solid.id, sketch: value.id,
}), }),
meta: vec![Metadata { meta: vec![Metadata {
source_range: tag.clone().into(), source_range: tag.clone().into(),
@ -693,21 +693,21 @@ fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut Ex
}; };
let mut info = info.clone(); let mut info = info.clone();
info.surface = Some(value.clone()); info.surface = Some(v.clone());
info.sketch = solid.id; info.sketch = value.id;
t.info = Some(info); t.info = Some(info);
exec_state.mut_memory().update_tag(&tag.name, t.clone())?; exec_state.mut_memory().update_tag(&tag.name, t.clone())?;
// update the sketch tags. // update the sketch tags.
solid.sketch.tags.insert(tag.name.clone(), t); value.sketch.tags.insert(tag.name.clone(), t);
} }
} }
// Find the stale sketch in memory and update it. // Find the stale sketch in memory and update it.
let cur_env_index = exec_state.memory().current_env.index(); let cur_env_index = exec_state.memory().current_env.index();
if let Some(current_env) = exec_state.mut_memory().environments.get_mut(cur_env_index) { if let Some(current_env) = exec_state.mut_memory().environments.get_mut(cur_env_index) {
current_env.update_sketch_tags(&solid.sketch); current_env.update_sketch_tags(&value.sketch);
} }
} }
_ => {} _ => {}
@ -929,13 +929,13 @@ impl Property {
LiteralIdentifier::Literal(literal) => { LiteralIdentifier::Literal(literal) => {
let value = literal.value.clone(); let value = literal.value.clone();
match value { match value {
LiteralValue::Number(x) => { LiteralValue::Number { value, .. } => {
if let Some(x) = crate::try_f64_to_usize(x) { if let Some(x) = crate::try_f64_to_usize(value) {
Ok(Property::UInt(x)) Ok(Property::UInt(x))
} else { } else {
Err(KclError::Semantic(KclErrorDetails { Err(KclError::Semantic(KclErrorDetails {
source_ranges: property_sr, source_ranges: property_sr,
message: format!("{x} is not a valid index, indices must be whole numbers >= 0"), message: format!("{value} is not a valid index, indices must be whole numbers >= 0"),
})) }))
} }
} }

View File

@ -62,19 +62,27 @@ pub enum KclValue {
}, },
TagIdentifier(Box<TagIdentifier>), TagIdentifier(Box<TagIdentifier>),
TagDeclarator(crate::parsing::ast::types::BoxNode<TagDeclarator>), TagDeclarator(crate::parsing::ast::types::BoxNode<TagDeclarator>),
Plane(Box<Plane>), Plane {
Face(Box<Face>), value: Box<Plane>,
},
Face {
value: Box<Face>,
},
Sketch { Sketch {
value: Box<Sketch>, value: Box<Sketch>,
}, },
Sketches { Sketches {
value: Vec<Box<Sketch>>, value: Vec<Box<Sketch>>,
}, },
Solid(Box<Solid>), Solid {
value: Box<Solid>,
},
Solids { Solids {
value: Vec<Box<Solid>>, value: Vec<Box<Solid>>,
}, },
Helix(Box<Helix>), Helix {
value: Box<Helix>,
},
ImportedGeometry(ImportedGeometry), ImportedGeometry(ImportedGeometry),
#[ts(skip)] #[ts(skip)]
Function { Function {
@ -120,7 +128,7 @@ impl From<Vec<Box<Sketch>>> for KclValue {
impl From<SolidSet> for KclValue { impl From<SolidSet> for KclValue {
fn from(eg: SolidSet) -> Self { fn from(eg: SolidSet) -> Self {
match eg { match eg {
SolidSet::Solid(eg) => KclValue::Solid(eg), SolidSet::Solid(eg) => KclValue::Solid { value: eg },
SolidSet::Solids(egs) => KclValue::Solids { value: egs }, SolidSet::Solids(egs) => KclValue::Solids { value: egs },
} }
} }
@ -129,7 +137,7 @@ impl From<SolidSet> for KclValue {
impl From<Vec<Box<Solid>>> for KclValue { impl From<Vec<Box<Solid>>> for KclValue {
fn from(eg: Vec<Box<Solid>>) -> Self { fn from(eg: Vec<Box<Solid>>) -> Self {
if eg.len() == 1 { if eg.len() == 1 {
KclValue::Solid(eg[0].clone()) KclValue::Solid { value: eg[0].clone() }
} else { } else {
KclValue::Solids { value: eg } KclValue::Solids { value: eg }
} }
@ -140,15 +148,15 @@ impl From<KclValue> for Vec<SourceRange> {
match item { match item {
KclValue::TagDeclarator(t) => vec![SourceRange::new(t.start, t.end, t.module_id)], KclValue::TagDeclarator(t) => vec![SourceRange::new(t.start, t.end, t.module_id)],
KclValue::TagIdentifier(t) => to_vec_sr(&t.meta), KclValue::TagIdentifier(t) => to_vec_sr(&t.meta),
KclValue::Solid(e) => to_vec_sr(&e.meta), KclValue::Solid { value } => to_vec_sr(&value.meta),
KclValue::Solids { value } => value.iter().flat_map(|eg| to_vec_sr(&eg.meta)).collect(), KclValue::Solids { value } => value.iter().flat_map(|eg| to_vec_sr(&eg.meta)).collect(),
KclValue::Sketch { value } => to_vec_sr(&value.meta), KclValue::Sketch { value } => to_vec_sr(&value.meta),
KclValue::Sketches { value } => value.iter().flat_map(|eg| to_vec_sr(&eg.meta)).collect(), KclValue::Sketches { value } => value.iter().flat_map(|eg| to_vec_sr(&eg.meta)).collect(),
KclValue::Helix(e) => to_vec_sr(&e.meta), KclValue::Helix { value } => to_vec_sr(&value.meta),
KclValue::ImportedGeometry(i) => to_vec_sr(&i.meta), KclValue::ImportedGeometry(i) => to_vec_sr(&i.meta),
KclValue::Function { meta, .. } => to_vec_sr(&meta), KclValue::Function { meta, .. } => to_vec_sr(&meta),
KclValue::Plane(p) => to_vec_sr(&p.meta), KclValue::Plane { value } => to_vec_sr(&value.meta),
KclValue::Face(f) => to_vec_sr(&f.meta), KclValue::Face { value } => to_vec_sr(&value.meta),
KclValue::Bool { meta, .. } => to_vec_sr(&meta), KclValue::Bool { meta, .. } => to_vec_sr(&meta),
KclValue::Number { meta, .. } => to_vec_sr(&meta), KclValue::Number { meta, .. } => to_vec_sr(&meta),
KclValue::Int { meta, .. } => to_vec_sr(&meta), KclValue::Int { meta, .. } => to_vec_sr(&meta),
@ -171,15 +179,15 @@ impl From<&KclValue> for Vec<SourceRange> {
match item { match item {
KclValue::TagDeclarator(t) => vec![SourceRange::new(t.start, t.end, t.module_id)], KclValue::TagDeclarator(t) => vec![SourceRange::new(t.start, t.end, t.module_id)],
KclValue::TagIdentifier(t) => to_vec_sr(&t.meta), KclValue::TagIdentifier(t) => to_vec_sr(&t.meta),
KclValue::Solid(e) => to_vec_sr(&e.meta), KclValue::Solid { value } => to_vec_sr(&value.meta),
KclValue::Solids { value } => value.iter().flat_map(|eg| to_vec_sr(&eg.meta)).collect(), KclValue::Solids { value } => value.iter().flat_map(|eg| to_vec_sr(&eg.meta)).collect(),
KclValue::Sketch { value } => to_vec_sr(&value.meta), KclValue::Sketch { value } => to_vec_sr(&value.meta),
KclValue::Sketches { value } => value.iter().flat_map(|eg| to_vec_sr(&eg.meta)).collect(), KclValue::Sketches { value } => value.iter().flat_map(|eg| to_vec_sr(&eg.meta)).collect(),
KclValue::Helix(x) => to_vec_sr(&x.meta), KclValue::Helix { value } => to_vec_sr(&value.meta),
KclValue::ImportedGeometry(i) => to_vec_sr(&i.meta), KclValue::ImportedGeometry(i) => to_vec_sr(&i.meta),
KclValue::Function { meta, .. } => to_vec_sr(meta), KclValue::Function { meta, .. } => to_vec_sr(meta),
KclValue::Plane(p) => to_vec_sr(&p.meta), KclValue::Plane { value } => to_vec_sr(&value.meta),
KclValue::Face(f) => to_vec_sr(&f.meta), KclValue::Face { value } => to_vec_sr(&value.meta),
KclValue::Bool { meta, .. } => to_vec_sr(meta), KclValue::Bool { meta, .. } => to_vec_sr(meta),
KclValue::Number { meta, .. } => to_vec_sr(meta), KclValue::Number { meta, .. } => to_vec_sr(meta),
KclValue::Int { meta, .. } => to_vec_sr(meta), KclValue::Int { meta, .. } => to_vec_sr(meta),
@ -205,13 +213,13 @@ impl KclValue {
KclValue::Object { value: _, meta } => meta.clone(), KclValue::Object { value: _, meta } => meta.clone(),
KclValue::TagIdentifier(x) => x.meta.clone(), KclValue::TagIdentifier(x) => x.meta.clone(),
KclValue::TagDeclarator(x) => vec![x.metadata()], KclValue::TagDeclarator(x) => vec![x.metadata()],
KclValue::Plane(x) => x.meta.clone(), KclValue::Plane { value } => value.meta.clone(),
KclValue::Face(x) => x.meta.clone(), KclValue::Face { value } => value.meta.clone(),
KclValue::Sketch { value } => value.meta.clone(), KclValue::Sketch { value } => value.meta.clone(),
KclValue::Sketches { value } => value.iter().flat_map(|sketch| &sketch.meta).copied().collect(), KclValue::Sketches { value } => value.iter().flat_map(|sketch| &sketch.meta).copied().collect(),
KclValue::Solid(x) => x.meta.clone(), KclValue::Solid { value } => value.meta.clone(),
KclValue::Solids { value } => value.iter().flat_map(|sketch| &sketch.meta).copied().collect(), KclValue::Solids { value } => value.iter().flat_map(|sketch| &sketch.meta).copied().collect(),
KclValue::Helix(x) => x.meta.clone(), KclValue::Helix { value } => value.meta.clone(),
KclValue::ImportedGeometry(x) => x.meta.clone(), KclValue::ImportedGeometry(x) => x.meta.clone(),
KclValue::Function { meta, .. } => meta.clone(), KclValue::Function { meta, .. } => meta.clone(),
KclValue::Module { meta, .. } => meta.clone(), KclValue::Module { meta, .. } => meta.clone(),
@ -230,7 +238,7 @@ impl KclValue {
pub(crate) fn get_solid_set(&self) -> Result<SolidSet> { pub(crate) fn get_solid_set(&self) -> Result<SolidSet> {
match self { match self {
KclValue::Solid(e) => Ok(SolidSet::Solid(e.clone())), KclValue::Solid { value } => Ok(SolidSet::Solid(value.clone())),
KclValue::Solids { value } => Ok(SolidSet::Solids(value.clone())), KclValue::Solids { value } => Ok(SolidSet::Solids(value.clone())),
KclValue::Array { value, .. } => { KclValue::Array { value, .. } => {
let solids: Vec<_> = value let solids: Vec<_> = value
@ -266,15 +274,15 @@ impl KclValue {
KclValue::Uuid { .. } => "Unique ID (uuid)", KclValue::Uuid { .. } => "Unique ID (uuid)",
KclValue::TagDeclarator(_) => "TagDeclarator", KclValue::TagDeclarator(_) => "TagDeclarator",
KclValue::TagIdentifier(_) => "TagIdentifier", KclValue::TagIdentifier(_) => "TagIdentifier",
KclValue::Solid(_) => "Solid", KclValue::Solid { .. } => "Solid",
KclValue::Solids { .. } => "Solids", KclValue::Solids { .. } => "Solids",
KclValue::Sketch { .. } => "Sketch", KclValue::Sketch { .. } => "Sketch",
KclValue::Sketches { .. } => "Sketches", KclValue::Sketches { .. } => "Sketches",
KclValue::Helix(_) => "Helix", KclValue::Helix { .. } => "Helix",
KclValue::ImportedGeometry(_) => "ImportedGeometry", KclValue::ImportedGeometry(_) => "ImportedGeometry",
KclValue::Function { .. } => "Function", KclValue::Function { .. } => "Function",
KclValue::Plane(_) => "Plane", KclValue::Plane { .. } => "Plane",
KclValue::Face(_) => "Face", KclValue::Face { .. } => "Face",
KclValue::Bool { .. } => "boolean (true/false value)", KclValue::Bool { .. } => "boolean (true/false value)",
KclValue::Number { .. } => "number", KclValue::Number { .. } => "number",
KclValue::Int { .. } => "integer", KclValue::Int { .. } => "integer",
@ -288,7 +296,7 @@ impl KclValue {
pub(crate) fn from_literal(literal: LiteralValue, meta: Vec<Metadata>) -> Self { pub(crate) fn from_literal(literal: LiteralValue, meta: Vec<Metadata>) -> Self {
match literal { match literal {
LiteralValue::Number(value) => KclValue::Number { value, meta }, LiteralValue::Number { value, .. } => KclValue::Number { value, meta },
LiteralValue::String(value) => KclValue::String { value, meta }, LiteralValue::String(value) => KclValue::String { value, meta },
LiteralValue::Bool(value) => KclValue::Bool { value, meta }, LiteralValue::Bool(value) => KclValue::Bool { value, meta },
} }
@ -383,7 +391,7 @@ impl KclValue {
} }
pub fn as_plane(&self) -> Option<&Plane> { pub fn as_plane(&self) -> Option<&Plane> {
if let KclValue::Plane(value) = &self { if let KclValue::Plane { value } = &self {
Some(value) Some(value)
} else { } else {
None None
@ -391,7 +399,7 @@ impl KclValue {
} }
pub fn as_solid(&self) -> Option<&Solid> { pub fn as_solid(&self) -> Option<&Solid> {
if let KclValue::Solid(value) = &self { if let KclValue::Solid { value } = &self {
Some(value) Some(value)
} else { } else {
None None
@ -614,6 +622,19 @@ impl From<crate::UnitLength> for UnitLen {
} }
} }
impl From<UnitLen> for crate::UnitLength {
fn from(unit: UnitLen) -> Self {
match unit {
UnitLen::Cm => crate::UnitLength::Cm,
UnitLen::Feet => crate::UnitLength::Ft,
UnitLen::Inches => crate::UnitLength::In,
UnitLen::M => crate::UnitLength::M,
UnitLen::Mm => crate::UnitLength::Mm,
UnitLen::Yards => crate::UnitLength::Yd,
}
}
}
#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)] #[derive(Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)]
#[ts(export)] #[ts(export)]
#[serde(tag = "type")] #[serde(tag = "type")]

View File

@ -2,6 +2,7 @@
use std::{path::PathBuf, sync::Arc}; use std::{path::PathBuf, sync::Arc};
use annotations::AnnotationScope;
use anyhow::Result; use anyhow::Result;
use artifact::build_artifact_graph; use artifact::build_artifact_graph;
use async_recursion::async_recursion; use async_recursion::async_recursion;
@ -391,7 +392,7 @@ impl ProgramMemory {
env.bindings env.bindings
.values() .values()
.filter_map(|item| match item { .filter_map(|item| match item {
KclValue::Solid(eg) if eg.sketch.id == sketch_id => Some(eg.clone()), KclValue::Solid { value } if value.sketch.id == sketch_id => Some(value.clone()),
_ => None, _ => None,
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
@ -505,8 +506,8 @@ impl DynamicState {
fn append(&mut self, memory: &ProgramMemory) { fn append(&mut self, memory: &ProgramMemory) {
for env in &memory.environments { for env in &memory.environments {
for item in env.bindings.values() { for item in env.bindings.values() {
if let KclValue::Solid(eg) = item { if let KclValue::Solid { value } = item {
self.solid_ids.push(SolidLazyIds::from(eg.as_ref())); self.solid_ids.push(SolidLazyIds::from(value.as_ref()));
} }
} }
} }
@ -759,6 +760,7 @@ pub struct Helix {
pub angle_start: f64, pub angle_start: f64,
/// Is the helix rotation counter clockwise? /// Is the helix rotation counter clockwise?
pub ccw: bool, pub ccw: bool,
pub units: UnitLen,
#[serde(rename = "__meta")] #[serde(rename = "__meta")]
pub meta: Vec<Metadata>, pub meta: Vec<Metadata>,
} }
@ -780,6 +782,7 @@ pub struct Plane {
pub y_axis: Point3d, pub y_axis: Point3d,
/// The z-axis (normal). /// The z-axis (normal).
pub z_axis: Point3d, pub z_axis: Point3d,
pub units: UnitLen,
#[serde(rename = "__meta")] #[serde(rename = "__meta")]
pub meta: Vec<Metadata>, pub meta: Vec<Metadata>,
} }
@ -795,6 +798,7 @@ impl Plane {
y_axis: Point3d::new(0.0, 1.0, 0.0), y_axis: Point3d::new(0.0, 1.0, 0.0),
z_axis: Point3d::new(0.0, 0.0, 1.0), z_axis: Point3d::new(0.0, 0.0, 1.0),
value: PlaneType::XY, value: PlaneType::XY,
units: exec_state.length_unit(),
meta: vec![], meta: vec![],
}, },
crate::std::sketch::PlaneData::NegXY => Plane { crate::std::sketch::PlaneData::NegXY => Plane {
@ -804,6 +808,7 @@ impl Plane {
y_axis: Point3d::new(0.0, 1.0, 0.0), y_axis: Point3d::new(0.0, 1.0, 0.0),
z_axis: Point3d::new(0.0, 0.0, -1.0), z_axis: Point3d::new(0.0, 0.0, -1.0),
value: PlaneType::XY, value: PlaneType::XY,
units: exec_state.length_unit(),
meta: vec![], meta: vec![],
}, },
crate::std::sketch::PlaneData::XZ => Plane { crate::std::sketch::PlaneData::XZ => Plane {
@ -813,6 +818,7 @@ impl Plane {
y_axis: Point3d::new(0.0, 0.0, 1.0), y_axis: Point3d::new(0.0, 0.0, 1.0),
z_axis: Point3d::new(0.0, -1.0, 0.0), z_axis: Point3d::new(0.0, -1.0, 0.0),
value: PlaneType::XZ, value: PlaneType::XZ,
units: exec_state.length_unit(),
meta: vec![], meta: vec![],
}, },
crate::std::sketch::PlaneData::NegXZ => Plane { crate::std::sketch::PlaneData::NegXZ => Plane {
@ -822,6 +828,7 @@ impl Plane {
y_axis: Point3d::new(0.0, 0.0, 1.0), y_axis: Point3d::new(0.0, 0.0, 1.0),
z_axis: Point3d::new(0.0, 1.0, 0.0), z_axis: Point3d::new(0.0, 1.0, 0.0),
value: PlaneType::XZ, value: PlaneType::XZ,
units: exec_state.length_unit(),
meta: vec![], meta: vec![],
}, },
crate::std::sketch::PlaneData::YZ => Plane { crate::std::sketch::PlaneData::YZ => Plane {
@ -831,6 +838,7 @@ impl Plane {
y_axis: Point3d::new(0.0, 0.0, 1.0), y_axis: Point3d::new(0.0, 0.0, 1.0),
z_axis: Point3d::new(1.0, 0.0, 0.0), z_axis: Point3d::new(1.0, 0.0, 0.0),
value: PlaneType::YZ, value: PlaneType::YZ,
units: exec_state.length_unit(),
meta: vec![], meta: vec![],
}, },
crate::std::sketch::PlaneData::NegYZ => Plane { crate::std::sketch::PlaneData::NegYZ => Plane {
@ -840,6 +848,7 @@ impl Plane {
y_axis: Point3d::new(0.0, 0.0, 1.0), y_axis: Point3d::new(0.0, 0.0, 1.0),
z_axis: Point3d::new(-1.0, 0.0, 0.0), z_axis: Point3d::new(-1.0, 0.0, 0.0),
value: PlaneType::YZ, value: PlaneType::YZ,
units: exec_state.length_unit(),
meta: vec![], meta: vec![],
}, },
crate::std::sketch::PlaneData::Plane { crate::std::sketch::PlaneData::Plane {
@ -854,6 +863,7 @@ impl Plane {
y_axis: *y_axis, y_axis: *y_axis,
z_axis: *z_axis, z_axis: *z_axis,
value: PlaneType::Custom, value: PlaneType::Custom,
units: exec_state.length_unit(),
meta: vec![], meta: vec![],
}, },
} }
@ -900,6 +910,7 @@ pub struct Face {
pub z_axis: Point3d, pub z_axis: Point3d,
/// The solid the face is on. /// The solid the face is on.
pub solid: Box<Solid>, pub solid: Box<Solid>,
pub units: UnitLen,
#[serde(rename = "__meta")] #[serde(rename = "__meta")]
pub meta: Vec<Metadata>, pub meta: Vec<Metadata>,
} }
@ -1018,6 +1029,7 @@ pub struct Sketch {
/// is sketched on face etc. /// is sketched on face etc.
#[serde(skip)] #[serde(skip)]
pub original_id: uuid::Uuid, pub original_id: uuid::Uuid,
pub units: UnitLen,
/// Metadata. /// Metadata.
#[serde(rename = "__meta")] #[serde(rename = "__meta")]
pub meta: Vec<Metadata>, pub meta: Vec<Metadata>,
@ -1141,6 +1153,7 @@ pub struct Solid {
/// Chamfers or fillets on this solid. /// Chamfers or fillets on this solid.
#[serde(default, skip_serializing_if = "Vec::is_empty")] #[serde(default, skip_serializing_if = "Vec::is_empty")]
pub edge_cuts: Vec<EdgeCut>, pub edge_cuts: Vec<EdgeCut>,
pub units: UnitLen,
/// Metadata. /// Metadata.
#[serde(rename = "__meta")] #[serde(rename = "__meta")]
pub meta: Vec<Metadata>, pub meta: Vec<Metadata>,
@ -2304,6 +2317,36 @@ impl ExecutorContext {
} }
} }
async fn handle_annotations(
&self,
annotations: impl Iterator<Item = (&NonCodeValue, SourceRange)>,
scope: AnnotationScope,
exec_state: &mut ExecState,
) -> Result<(), KclError> {
for (annotation, source_range) in annotations {
if annotation.annotation_name() == Some(annotations::SETTINGS) {
if scope == AnnotationScope::Module {
let old_units = exec_state.length_unit();
exec_state
.mod_local
.settings
.update_from_annotation(annotation, source_range)?;
let new_units = exec_state.length_unit();
if old_units != new_units {
self.engine.set_units(new_units.into(), source_range).await?;
}
} else {
return Err(KclError::Semantic(KclErrorDetails {
message: "Settings can only be modified at the top level scope of a file".to_owned(),
source_ranges: vec![source_range],
}));
}
}
// TODO warn on unknown annotations
}
Ok(())
}
/// Execute an AST's program. /// Execute an AST's program.
#[async_recursion] #[async_recursion]
pub(crate) async fn inner_execute<'a>( pub(crate) async fn inner_execute<'a>(
@ -2312,21 +2355,16 @@ impl ExecutorContext {
exec_state: &mut ExecState, exec_state: &mut ExecState,
body_type: BodyType, body_type: BodyType,
) -> Result<Option<KclValue>, KclError> { ) -> Result<Option<KclValue>, KclError> {
if let Some((annotation, source_range)) = program self.handle_annotations(
program
.non_code_meta .non_code_meta
.start_nodes .start_nodes
.iter() .iter()
.filter_map(|n| { .filter_map(|n| n.annotation().map(|result| (result, n.as_source_range()))),
n.annotation(annotations::SETTINGS) AnnotationScope::Module,
.map(|result| (result, n.as_source_range())) exec_state,
}) )
.next() .await?;
{
exec_state
.mod_local
.settings
.update_from_annotation(annotation, source_range)?;
}
let mut last_expr = None; let mut last_expr = None;
// Iterate over the body of the program. // Iterate over the body of the program.
@ -2509,6 +2547,7 @@ impl ExecutorContext {
exec_kind: ExecutionKind, exec_kind: ExecutionKind,
source_range: SourceRange, source_range: SourceRange,
) -> Result<(Option<KclValue>, ProgramMemory, Vec<String>), KclError> { ) -> Result<(Option<KclValue>, ProgramMemory, Vec<String>), KclError> {
let old_units = exec_state.length_unit();
// TODO It sucks that we have to clone the whole module AST here // TODO It sucks that we have to clone the whole module AST here
let info = exec_state.global.module_infos[&module_id].clone(); let info = exec_state.global.module_infos[&module_id].clone();
@ -2525,7 +2564,11 @@ impl ExecutorContext {
.inner_execute(&info.parsed.unwrap(), exec_state, crate::execution::BodyType::Root) .inner_execute(&info.parsed.unwrap(), exec_state, crate::execution::BodyType::Root)
.await; .await;
let new_units = exec_state.length_unit();
std::mem::swap(&mut exec_state.mod_local, &mut local_state); std::mem::swap(&mut exec_state.mod_local, &mut local_state);
if new_units != old_units {
self.engine.set_units(old_units.into(), Default::default()).await?;
}
self.engine.replace_execution_kind(original_execution); self.engine.replace_execution_kind(original_execution);
let result = result.map_err(|err| { let result = result.map_err(|err| {

View File

@ -163,7 +163,7 @@ fn get_xyz(point: &ObjectExpression) -> Option<(f64, f64, f64)> {
fn unlitafy(lit: &LiteralValue) -> Option<f64> { fn unlitafy(lit: &LiteralValue) -> Option<f64> {
Some(match lit { Some(match lit {
LiteralValue::Number(value) => *value, LiteralValue::Number { value, .. } => *value,
_ => { _ => {
return None; return None;
} }

View File

@ -1,6 +1,6 @@
use sha2::{Digest as DigestTrait, Sha256}; use sha2::{Digest as DigestTrait, Sha256};
use super::types::{DefaultParamVal, ItemVisibility, LabelledExpression, VariableKind}; use super::types::{DefaultParamVal, ItemVisibility, LabelledExpression, LiteralValue, VariableKind};
use crate::parsing::ast::types::{ use crate::parsing::ast::types::{
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryPart, BodyItem, CallExpression, CallExpressionKw, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryPart, BodyItem, CallExpression, CallExpressionKw,
ElseIf, Expr, ExpressionStatement, FnArgType, FunctionExpression, Identifier, IfExpression, ImportItem, ElseIf, Expr, ExpressionStatement, FnArgType, FunctionExpression, Identifier, IfExpression, ImportItem,
@ -277,6 +277,26 @@ impl Literal {
}); });
} }
impl LiteralValue {
fn digestable_id(&self) -> Vec<u8> {
match self {
LiteralValue::Number { value, suffix } => {
let mut result: Vec<u8> = value.to_ne_bytes().into();
result.extend((*suffix as u32).to_ne_bytes());
result
}
LiteralValue::String(st) => st.as_bytes().into(),
LiteralValue::Bool(b) => {
if *b {
vec![1]
} else {
vec![0]
}
}
}
}
}
impl Identifier { impl Identifier {
compute_digest!(|slf, hasher| { compute_digest!(|slf, hasher| {
let name = slf.name.as_bytes(); let name = slf.name.as_bytes();

View File

@ -18,6 +18,8 @@ use crate::{
Program, Program,
}; };
use super::types::LiteralValue;
type Point3d = kcmc::shared::Point3d<f64>; type Point3d = kcmc::shared::Point3d<f64>;
#[derive(Debug)] #[derive(Debug)]
@ -201,8 +203,8 @@ fn create_start_sketch_on(
"startProfileAt", "startProfileAt",
vec![ vec![
ArrayExpression::new(vec![ ArrayExpression::new(vec![
Literal::new(round_before_recast(start[0]).into()).into(), Literal::new(LiteralValue::from_f64_no_uom(round_before_recast(start[0]))).into(),
Literal::new(round_before_recast(start[1]).into()).into(), Literal::new(LiteralValue::from_f64_no_uom(round_before_recast(start[1]))).into(),
]) ])
.into(), .into(),
PipeSubstitution::new().into(), PipeSubstitution::new().into(),
@ -221,8 +223,8 @@ fn create_start_sketch_on(
"line", "line",
vec![ vec![
ArrayExpression::new(vec![ ArrayExpression::new(vec![
Literal::new(round_before_recast(end[0]).into()).into(), Literal::new(LiteralValue::from_f64_no_uom(round_before_recast(end[0]))).into(),
Literal::new(round_before_recast(end[1]).into()).into(), Literal::new(LiteralValue::from_f64_no_uom(round_before_recast(end[1]))).into(),
]) ])
.into(), .into(),
PipeSubstitution::new().into(), PipeSubstitution::new().into(),
@ -254,8 +256,8 @@ fn create_start_sketch_on(
"line", "line",
vec![ vec![
ArrayExpression::new(vec![ ArrayExpression::new(vec![
Literal::new(round_before_recast(line[0]).into()).into(), Literal::new(LiteralValue::from_f64_no_uom(round_before_recast(line[0]))).into(),
Literal::new(round_before_recast(line[1]).into()).into(), Literal::new(LiteralValue::from_f64_no_uom(round_before_recast(line[1]))).into(),
]) ])
.into(), .into(),
PipeSubstitution::new().into(), PipeSubstitution::new().into(),

View File

@ -1,31 +1,49 @@
use std::fmt;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value as JValue;
use super::Node; use super::Node;
use crate::parsing::ast::types::{Expr, Literal}; use crate::parsing::{
ast::types::{Expr, Literal},
token::NumericSuffix,
};
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]
#[serde(untagged, rename_all = "snake_case")] #[serde(untagged, rename_all = "snake_case")]
pub enum LiteralValue { pub enum LiteralValue {
Number(f64), Number { value: f64, suffix: NumericSuffix },
String(String), String(String),
Bool(bool), Bool(bool),
} }
impl LiteralValue { impl LiteralValue {
pub fn digestable_id(&self) -> Vec<u8> { pub fn from_f64_no_uom(value: f64) -> Self {
LiteralValue::Number {
value,
suffix: NumericSuffix::None,
}
}
}
impl fmt::Display for LiteralValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
LiteralValue::Number(frac) => frac.to_ne_bytes().into(), LiteralValue::Number { value, suffix } => {
LiteralValue::String(st) => st.as_bytes().into(), let int_value = *value as u64;
LiteralValue::Bool(b) => { if int_value as f64 == *value {
if *b { write!(f, "{int_value}")?;
vec![1]
} else { } else {
vec![0] write!(f, "{value}")?;
} }
if *suffix != NumericSuffix::None {
write!(f, "{suffix}")?;
} }
Ok(())
}
LiteralValue::String(s) => write!(f, "\"{s}\""),
LiteralValue::Bool(b) => write!(f, "{b}"),
} }
} }
} }
@ -36,49 +54,12 @@ impl From<Node<Literal>> for Expr {
} }
} }
impl From<LiteralValue> for JValue {
fn from(value: LiteralValue) -> Self {
match value {
LiteralValue::Number(x) => x.into(),
LiteralValue::String(x) => x.into(),
LiteralValue::Bool(b) => b.into(),
}
}
}
impl From<f64> for LiteralValue {
fn from(value: f64) -> Self {
Self::Number(value)
}
}
impl From<i64> for LiteralValue {
fn from(value: i64) -> Self {
Self::Number(value as f64)
}
}
impl From<String> for LiteralValue { impl From<String> for LiteralValue {
fn from(value: String) -> Self { fn from(value: String) -> Self {
Self::String(value) Self::String(value)
} }
} }
impl From<u32> for LiteralValue {
fn from(value: u32) -> Self {
Self::Number(value as f64)
}
}
impl From<u16> for LiteralValue {
fn from(value: u16) -> Self {
Self::Number(value as f64)
}
}
impl From<u8> for LiteralValue {
fn from(value: u8) -> Self {
Self::Number(value as f64)
}
}
impl From<&'static str> for LiteralValue { impl From<&'static str> for LiteralValue {
fn from(value: &'static str) -> Self { fn from(value: &'static str) -> Self {
// TODO: Make this Cow<str> // TODO: Make this Cow<str>

View File

@ -13,7 +13,6 @@ use anyhow::Result;
use parse_display::{Display, FromStr}; use parse_display::{Display, FromStr};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value as JValue;
use tower_lsp::lsp_types::{ use tower_lsp::lsp_types::{
CompletionItem, CompletionItemKind, DocumentSymbol, FoldingRange, FoldingRangeKind, Range as LspRange, SymbolKind, CompletionItem, CompletionItemKind, DocumentSymbol, FoldingRange, FoldingRangeKind, Range as LspRange, SymbolKind,
}; };
@ -1012,9 +1011,9 @@ impl NonCodeNode {
} }
} }
pub fn annotation(&self, expected_name: &str) -> Option<&NonCodeValue> { pub fn annotation(&self) -> Option<&NonCodeValue> {
match &self.value { match &self.value {
a @ NonCodeValue::Annotation { name, .. } if name.name == expected_name => Some(a), a @ NonCodeValue::Annotation { .. } => Some(a),
_ => None, _ => None,
} }
} }
@ -1072,6 +1071,15 @@ pub enum NonCodeValue {
}, },
} }
impl NonCodeValue {
pub fn annotation_name(&self) -> Option<&str> {
match self {
NonCodeValue::Annotation { name, .. } => Some(&name.name),
_ => None,
}
}
}
#[derive(Debug, Default, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[derive(Debug, Default, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@ -1867,7 +1875,7 @@ impl Node<Literal> {
impl Literal { impl Literal {
pub fn new(value: LiteralValue) -> Node<Self> { pub fn new(value: LiteralValue) -> Node<Self> {
Node::no_src(Self { Node::no_src(Self {
raw: JValue::from(value.clone()).to_string(), raw: value.to_string(),
value, value,
digest: None, digest: None,
}) })
@ -1878,7 +1886,7 @@ impl From<Node<Literal>> for KclValue {
fn from(literal: Node<Literal>) -> Self { fn from(literal: Node<Literal>) -> Self {
let meta = vec![literal.metadata()]; let meta = vec![literal.metadata()];
match literal.inner.value { match literal.inner.value {
LiteralValue::Number(value) => KclValue::Number { value, meta }, LiteralValue::Number { value, .. } => KclValue::Number { value, meta },
LiteralValue::String(value) => KclValue::String { value, meta }, LiteralValue::String(value) => KclValue::String { value, meta },
LiteralValue::Bool(value) => KclValue::Bool { value, meta }, LiteralValue::Bool(value) => KclValue::Bool { value, meta },
} }

View File

@ -126,7 +126,13 @@ impl From<BinaryOperator> for BinaryExpressionToken {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::{parsing::ast::types::Literal, source_range::ModuleId}; use crate::{
parsing::{
ast::types::{Literal, LiteralValue},
token::NumericSuffix,
},
source_range::ModuleId,
};
#[test] #[test]
fn parse_and_evaluate() { fn parse_and_evaluate() {
@ -134,7 +140,10 @@ mod tests {
fn lit(n: u8) -> BinaryPart { fn lit(n: u8) -> BinaryPart {
BinaryPart::Literal(Box::new(Node::new( BinaryPart::Literal(Box::new(Node::new(
Literal { Literal {
value: n.into(), value: LiteralValue::Number {
value: n as f64,
suffix: NumericSuffix::None,
},
raw: n.to_string(), raw: n.to_string(),
digest: None, digest: None,
}, },

View File

@ -483,7 +483,7 @@ pub(crate) fn unsigned_number_literal(i: &mut TokenSlice) -> PResult<Node<Litera
let (value, token) = any let (value, token) = any
.try_map(|token: Token| match token.token_type { .try_map(|token: Token| match token.token_type {
TokenType::Number => { TokenType::Number => {
let x: f64 = token.numeric_value().ok_or_else(|| { let value: f64 = token.numeric_value().ok_or_else(|| {
CompilationError::fatal(token.as_source_range(), format!("Invalid float: {}", token.value)) CompilationError::fatal(token.as_source_range(), format!("Invalid float: {}", token.value))
})?; })?;
@ -494,7 +494,13 @@ pub(crate) fn unsigned_number_literal(i: &mut TokenSlice) -> PResult<Node<Litera
)); ));
} }
Ok((LiteralValue::Number(x), token)) Ok((
LiteralValue::Number {
value,
suffix: token.numeric_suffix(),
},
token,
))
} }
_ => Err(CompilationError::fatal(token.as_source_range(), "invalid literal")), _ => Err(CompilationError::fatal(token.as_source_range(), "invalid literal")),
}) })
@ -844,13 +850,13 @@ fn object_property(i: &mut TokenSlice) -> PResult<Node<ObjectProperty>> {
}; };
if sep.token_type == TokenType::Colon { if sep.token_type == TokenType::Colon {
ParseContext::warn(CompilationError::with_suggestion( ParseContext::warn(
CompilationError::err(
sep.into(), sep.into(),
Some(result.as_source_range()),
"Using `:` to initialize objects is deprecated, prefer using `=`.", "Using `:` to initialize objects is deprecated, prefer using `=`.",
Some(("Replace `:` with `=`", " =")), )
Tag::Deprecated, .with_suggestion("Replace `:` with `=`", " =", Tag::Deprecated),
)); );
} }
Ok(result) Ok(result)
@ -1069,10 +1075,20 @@ fn function_expr(i: &mut TokenSlice) -> PResult<Expr> {
let fn_tok = opt(fun).parse_next(i)?; let fn_tok = opt(fun).parse_next(i)?;
ignore_whitespace(i); ignore_whitespace(i);
let (result, has_arrow) = function_decl.parse_next(i)?; let (result, has_arrow) = function_decl.parse_next(i)?;
if fn_tok.is_none() && !has_arrow { if fn_tok.is_none() {
if has_arrow {
ParseContext::warn(
CompilationError::err(
result.as_source_range().start_as_range(),
"Missing `fn` in function declaration",
)
.with_suggestion("Add `fn`", "fn", Tag::None),
);
} else {
let err = CompilationError::fatal(result.as_source_range(), "Anonymous function requires `fn` before `(`"); let err = CompilationError::fatal(result.as_source_range(), "Anonymous function requires `fn` before `(`");
return Err(ErrMode::Cut(err.into())); return Err(ErrMode::Cut(err.into()));
} }
}
Ok(Expr::FunctionExpression(Box::new(result))) Ok(Expr::FunctionExpression(Box::new(result)))
} }
@ -1113,14 +1129,12 @@ fn function_decl(i: &mut TokenSlice) -> PResult<(Node<FunctionExpression>, bool)
open.module_id, open.module_id,
); );
let has_arrow = if let Some(arrow) = arrow { let has_arrow =
ParseContext::warn(CompilationError::with_suggestion( if let Some(arrow) = arrow {
arrow.as_source_range(), ParseContext::warn(
Some(result.as_source_range()), CompilationError::err(arrow.as_source_range(), "Unnecessary `=>` in function declaration")
"Unnecessary `=>` in function declaration", .with_suggestion("Remove `=>`", "", Tag::Unnecessary),
Some(("Remove `=>`", "")), );
Tag::Unnecessary,
));
true true
} else { } else {
false false
@ -1825,7 +1839,8 @@ fn declaration(i: &mut TokenSlice) -> PResult<BoxNode<VariableDeclaration>> {
ignore_whitespace(i); ignore_whitespace(i);
let val = if kind == VariableKind::Fn { let val =
if kind == VariableKind::Fn {
let eq = opt(equals).parse_next(i)?; let eq = opt(equals).parse_next(i)?;
ignore_whitespace(i); ignore_whitespace(i);
@ -1836,14 +1851,10 @@ fn declaration(i: &mut TokenSlice) -> PResult<BoxNode<VariableDeclaration>> {
.parse_next(i); .parse_next(i);
if let Some(t) = eq { if let Some(t) = eq {
let ctxt_end = val.as_ref().map(|e| e.end()).unwrap_or(t.end); ParseContext::warn(
ParseContext::warn(CompilationError::with_suggestion( CompilationError::err(t.as_source_range(), "Unnecessary `=` in function declaration")
t.as_source_range(), .with_suggestion("Remove `=`", "", Tag::Unnecessary),
Some(SourceRange::new(id.start, ctxt_end, id.module_id)), );
"Unnecessary `=` in function declaration",
Some(("Remove `=`", "")),
Tag::Unnecessary,
));
} }
val val
@ -1867,20 +1878,16 @@ fn declaration(i: &mut TokenSlice) -> PResult<BoxNode<VariableDeclaration>> {
.parse_next(i); .parse_next(i);
if let Some((_, tok)) = decl_token { if let Some((_, tok)) = decl_token {
ParseContext::warn(CompilationError::with_suggestion( ParseContext::warn(
CompilationError::err(
tok.as_source_range(), tok.as_source_range(),
Some(SourceRange::new(
id.start,
val.as_ref().map(|e| e.end()).unwrap_or(dec_end),
id.module_id,
)),
format!( format!(
"Using `{}` to declare constants is deprecated; no keyword is required", "Using `{}` to declare constants is deprecated; no keyword is required",
tok.value tok.value
), ),
Some((format!("Remove `{}`", tok.value), "")), )
Tag::Deprecated, .with_suggestion(format!("Remove `{}`", tok.value), "", Tag::Deprecated),
)); );
} }
val val
@ -2856,7 +2863,10 @@ mySk1 = startSketchAt([0, 0])"#;
ReturnStatement { ReturnStatement {
argument: Expr::Literal(Box::new(Node::new( argument: Expr::Literal(Box::new(Node::new(
Literal { Literal {
value: 2u32.into(), value: LiteralValue::Number {
value: 2.0,
suffix: NumericSuffix::None
},
raw: "2".to_owned(), raw: "2".to_owned(),
digest: None, digest: None,
}, },
@ -3057,7 +3067,15 @@ mySk1 = startSketchAt([0, 0])"#;
match &rhs.right { match &rhs.right {
BinaryPart::Literal(lit) => { BinaryPart::Literal(lit) => {
assert!(lit.start == 9 && lit.end == 10); assert!(lit.start == 9 && lit.end == 10);
assert!(lit.value == 3u32.into() && &lit.raw == "3" && lit.digest.is_none()); assert!(
lit.value
== LiteralValue::Number {
value: 3.0,
suffix: NumericSuffix::None
}
&& &lit.raw == "3"
&& lit.digest.is_none()
);
} }
_ => panic!(), _ => panic!(),
} }
@ -3128,11 +3146,23 @@ mySk1 = startSketchAt([0, 0])"#;
let BinaryPart::Literal(left) = actual.inner.left else { let BinaryPart::Literal(left) = actual.inner.left else {
panic!("should be expression"); panic!("should be expression");
}; };
assert_eq!(left.value, 1u32.into()); assert_eq!(
left.value,
LiteralValue::Number {
value: 1.0,
suffix: NumericSuffix::None
}
);
let BinaryPart::Literal(right) = actual.inner.right else { let BinaryPart::Literal(right) = actual.inner.right else {
panic!("should be expression"); panic!("should be expression");
}; };
assert_eq!(right.value, 2u32.into()); assert_eq!(
right.value,
LiteralValue::Number {
value: 2.0,
suffix: NumericSuffix::None
}
);
} }
} }
@ -3449,7 +3479,10 @@ mySk1 = startSketchAt([0, 0])"#;
operator: BinaryOperator::Add, operator: BinaryOperator::Add,
left: BinaryPart::Literal(Box::new(Node::new( left: BinaryPart::Literal(Box::new(Node::new(
Literal { Literal {
value: 5u32.into(), value: LiteralValue::Number {
value: 5.0,
suffix: NumericSuffix::None,
},
raw: "5".to_owned(), raw: "5".to_owned(),
digest: None, digest: None,
}, },
@ -3498,7 +3531,10 @@ mySk1 = startSketchAt([0, 0])"#;
BinaryExpression { BinaryExpression {
left: BinaryPart::Literal(Box::new(Node::new( left: BinaryPart::Literal(Box::new(Node::new(
Literal { Literal {
value: 5u32.into(), value: LiteralValue::Number {
value: 5.0,
suffix: NumericSuffix::None,
},
raw: "5".to_string(), raw: "5".to_string(),
digest: None, digest: None,
}, },
@ -3509,7 +3545,10 @@ mySk1 = startSketchAt([0, 0])"#;
operator: BinaryOperator::Add, operator: BinaryOperator::Add,
right: BinaryPart::Literal(Box::new(Node::new( right: BinaryPart::Literal(Box::new(Node::new(
Literal { Literal {
value: 6u32.into(), value: LiteralValue::Number {
value: 6.0,
suffix: NumericSuffix::None,
},
raw: "6".to_string(), raw: "6".to_string(),
digest: None, digest: None,
}, },
@ -4345,6 +4384,20 @@ sketch001 = startSketchOn('XZ') |> startProfileAt([90.45, 119.09, %)"#;
return 0 return 0
}"# }"#
); );
let some_program_string = r#"myMap = map([0..5], (n) => {
return n * 2
})"#;
let (_, errs) = assert_no_err(some_program_string);
assert_eq!(errs.len(), 2);
let replaced = errs[0].apply_suggestion(some_program_string).unwrap();
let replaced = errs[1].apply_suggestion(&replaced).unwrap();
assert_eq!(
replaced,
r#"myMap = map([0..5], fn(n) {
return n * 2
})"#
);
} }
#[test] #[test]

View File

@ -1,8 +1,6 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 3851
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"type": "BinaryExpression", "type": "BinaryExpression",
@ -10,7 +8,10 @@ snapshot_kind: text
"left": { "left": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": {
"value": 1.0, "value": 1.0,
"suffix": "None"
},
"raw": "1", "raw": "1",
"start": 0, "start": 0,
"end": 1 "end": 1
@ -18,7 +19,10 @@ snapshot_kind: text
"right": { "right": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": {
"value": 2.0, "value": 2.0,
"suffix": "None"
},
"raw": "2", "raw": "2",
"start": 4, "start": 4,
"end": 5 "end": 5

View File

@ -1,8 +1,6 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 3852
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"type": "BinaryExpression", "type": "BinaryExpression",
@ -10,7 +8,10 @@ snapshot_kind: text
"left": { "left": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": {
"value": 1.0, "value": 1.0,
"suffix": "None"
},
"raw": "1", "raw": "1",
"start": 0, "start": 0,
"end": 1 "end": 1
@ -18,7 +19,10 @@ snapshot_kind: text
"right": { "right": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": {
"value": 2.0, "value": 2.0,
"suffix": "None"
},
"raw": "2", "raw": "2",
"start": 2, "start": 2,
"end": 3 "end": 3

View File

@ -1,8 +1,6 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 3853
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"type": "BinaryExpression", "type": "BinaryExpression",
@ -10,7 +8,10 @@ snapshot_kind: text
"left": { "left": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": {
"value": 1.0, "value": 1.0,
"suffix": "None"
},
"raw": "1", "raw": "1",
"start": 0, "start": 0,
"end": 1 "end": 1
@ -18,7 +19,10 @@ snapshot_kind: text
"right": { "right": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": {
"value": 2.0, "value": 2.0,
"suffix": "None"
},
"raw": "2", "raw": "2",
"start": 3, "start": 3,
"end": 4 "end": 4

View File

@ -1,8 +1,6 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 3854
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"type": "BinaryExpression", "type": "BinaryExpression",
@ -10,7 +8,10 @@ snapshot_kind: text
"left": { "left": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": {
"value": 1.0, "value": 1.0,
"suffix": "None"
},
"raw": "1", "raw": "1",
"start": 0, "start": 0,
"end": 1 "end": 1
@ -22,7 +23,10 @@ snapshot_kind: text
"left": { "left": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": {
"value": 2.0, "value": 2.0,
"suffix": "None"
},
"raw": "2", "raw": "2",
"start": 4, "start": 4,
"end": 5 "end": 5
@ -30,7 +34,10 @@ snapshot_kind: text
"right": { "right": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": {
"value": 3.0, "value": 3.0,
"suffix": "None"
},
"raw": "3", "raw": "3",
"start": 8, "start": 8,
"end": 9 "end": 9

View File

@ -1,8 +1,6 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 3855
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"type": "BinaryExpression", "type": "BinaryExpression",
@ -10,7 +8,10 @@ snapshot_kind: text
"left": { "left": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": {
"value": 1.0, "value": 1.0,
"suffix": "None"
},
"raw": "1", "raw": "1",
"start": 0, "start": 0,
"end": 1 "end": 1
@ -22,7 +23,10 @@ snapshot_kind: text
"left": { "left": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": {
"value": 2.0, "value": 2.0,
"suffix": "None"
},
"raw": "2", "raw": "2",
"start": 6, "start": 6,
"end": 7 "end": 7
@ -30,7 +34,10 @@ snapshot_kind: text
"right": { "right": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": {
"value": 3.0, "value": 3.0,
"suffix": "None"
},
"raw": "3", "raw": "3",
"start": 10, "start": 10,
"end": 11 "end": 11

View File

@ -1,8 +1,6 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 3856
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"type": "BinaryExpression", "type": "BinaryExpression",
@ -14,7 +12,10 @@ snapshot_kind: text
"left": { "left": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": {
"value": 1.0, "value": 1.0,
"suffix": "None"
},
"raw": "1", "raw": "1",
"start": 0, "start": 0,
"end": 1 "end": 1
@ -26,7 +27,10 @@ snapshot_kind: text
"left": { "left": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": {
"value": 2.0, "value": 2.0,
"suffix": "None"
},
"raw": "2", "raw": "2",
"start": 6, "start": 6,
"end": 7 "end": 7
@ -34,7 +38,10 @@ snapshot_kind: text
"right": { "right": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": {
"value": 3.0, "value": 3.0,
"suffix": "None"
},
"raw": "3", "raw": "3",
"start": 10, "start": 10,
"end": 11 "end": 11
@ -48,7 +55,10 @@ snapshot_kind: text
"right": { "right": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": {
"value": 4.0, "value": 4.0,
"suffix": "None"
},
"raw": "4", "raw": "4",
"start": 16, "start": 16,
"end": 17 "end": 17

View File

@ -1,8 +1,6 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 3857
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"type": "BinaryExpression", "type": "BinaryExpression",
@ -10,7 +8,10 @@ snapshot_kind: text
"left": { "left": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": {
"value": 1.0, "value": 1.0,
"suffix": "None"
},
"raw": "1", "raw": "1",
"start": 0, "start": 0,
"end": 1 "end": 1
@ -26,7 +27,10 @@ snapshot_kind: text
"left": { "left": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": {
"value": 2.0, "value": 2.0,
"suffix": "None"
},
"raw": "2", "raw": "2",
"start": 6, "start": 6,
"end": 7 "end": 7
@ -34,7 +38,10 @@ snapshot_kind: text
"right": { "right": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": {
"value": 3.0, "value": 3.0,
"suffix": "None"
},
"raw": "3", "raw": "3",
"start": 10, "start": 10,
"end": 11 "end": 11
@ -45,7 +52,10 @@ snapshot_kind: text
"right": { "right": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": {
"value": 4.0, "value": 4.0,
"suffix": "None"
},
"raw": "4", "raw": "4",
"start": 16, "start": 16,
"end": 17 "end": 17

View File

@ -1,8 +1,6 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 3858
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"type": "BinaryExpression", "type": "BinaryExpression",
@ -10,7 +8,10 @@ snapshot_kind: text
"left": { "left": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": {
"value": 1.0, "value": 1.0,
"suffix": "None"
},
"raw": "1", "raw": "1",
"start": 0, "start": 0,
"end": 1 "end": 1
@ -30,7 +31,10 @@ snapshot_kind: text
"left": { "left": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": {
"value": 2.0, "value": 2.0,
"suffix": "None"
},
"raw": "2", "raw": "2",
"start": 7, "start": 7,
"end": 8 "end": 8
@ -38,7 +42,10 @@ snapshot_kind: text
"right": { "right": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": {
"value": 3.0, "value": 3.0,
"suffix": "None"
},
"raw": "3", "raw": "3",
"start": 11, "start": 11,
"end": 12 "end": 12
@ -49,7 +56,10 @@ snapshot_kind: text
"right": { "right": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": {
"value": 4.0, "value": 4.0,
"suffix": "None"
},
"raw": "4", "raw": "4",
"start": 17, "start": 17,
"end": 18 "end": 18
@ -60,7 +70,10 @@ snapshot_kind: text
"right": { "right": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": {
"value": 5.0, "value": 5.0,
"suffix": "None"
},
"raw": "5", "raw": "5",
"start": 21, "start": 21,
"end": 22 "end": 22

View File

@ -1,8 +1,6 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 3859
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"type": "BinaryExpression", "type": "BinaryExpression",
@ -10,7 +8,10 @@ snapshot_kind: text
"left": { "left": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": {
"value": 1.0, "value": 1.0,
"suffix": "None"
},
"raw": "1", "raw": "1",
"start": 0, "start": 0,
"end": 1 "end": 1
@ -22,7 +23,10 @@ snapshot_kind: text
"left": { "left": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": {
"value": 2.0, "value": 2.0,
"suffix": "None"
},
"raw": "2", "raw": "2",
"start": 8, "start": 8,
"end": 9 "end": 9
@ -30,7 +34,10 @@ snapshot_kind: text
"right": { "right": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": {
"value": 3.0, "value": 3.0,
"suffix": "None"
},
"raw": "3", "raw": "3",
"start": 12, "start": 12,
"end": 13 "end": 13

View File

@ -1,8 +1,6 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 3860
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"type": "BinaryExpression", "type": "BinaryExpression",
@ -49,7 +47,10 @@ snapshot_kind: text
"right": { "right": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": {
"value": 6.0, "value": 6.0,
"suffix": "None"
},
"raw": "6", "raw": "6",
"start": 21, "start": 21,
"end": 22 "end": 22

View File

@ -1,8 +1,6 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 3861
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"type": "BinaryExpression", "type": "BinaryExpression",
@ -10,7 +8,10 @@ snapshot_kind: text
"left": { "left": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": {
"value": 2.0, "value": 2.0,
"suffix": "None"
},
"raw": "2", "raw": "2",
"start": 0, "start": 0,
"end": 1 "end": 1
@ -18,7 +19,10 @@ snapshot_kind: text
"right": { "right": {
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": {
"value": 3.0, "value": 3.0,
"suffix": "None"
},
"raw": "3", "raw": "3",
"start": 7, "start": 7,
"end": 8 "end": 8

View File

@ -25,7 +25,10 @@ expression: actual
"start": 27, "start": 27,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": {
"value": 0.0,
"suffix": "None"
}
}, },
{ {
"end": 31, "end": 31,
@ -33,7 +36,10 @@ expression: actual
"start": 30, "start": 30,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": {
"value": 0.0,
"suffix": "None"
}
} }
], ],
"end": 32, "end": 32,
@ -63,7 +69,10 @@ expression: actual
"start": 47, "start": 47,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": {
"value": 0.0,
"suffix": "None"
}
}, },
{ {
"end": 52, "end": 52,
@ -71,7 +80,10 @@ expression: actual
"start": 50, "start": 50,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 10.0 "value": {
"value": 10.0,
"suffix": "None"
}
} }
], ],
"end": 53, "end": 53,
@ -108,7 +120,10 @@ expression: actual
"start": 81, "start": 81,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 5.0 "value": {
"value": 5.0,
"suffix": "None"
}
}, },
"end": 82, "end": 82,
"operator": "-", "operator": "-",
@ -122,7 +137,10 @@ expression: actual
"start": 84, "start": 84,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 5.0 "value": {
"value": 5.0,
"suffix": "None"
}
} }
], ],
"end": 86, "end": 86,
@ -158,7 +176,10 @@ expression: actual
"start": 104, "start": 104,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 5.0 "value": {
"value": 5.0,
"suffix": "None"
}
}, },
{ {
"argument": { "argument": {
@ -167,7 +188,10 @@ expression: actual
"start": 108, "start": 108,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 15.0 "value": {
"value": 15.0,
"suffix": "None"
}
}, },
"end": 110, "end": 110,
"operator": "-", "operator": "-",
@ -207,7 +231,10 @@ expression: actual
"start": 131, "start": 131,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 10.0 "value": {
"value": 10.0,
"suffix": "None"
}
}, },
{ {
"end": 136, "end": 136,

View File

@ -1,8 +1,6 @@
--- ---
source: kcl/src/parsing/parser.rs source: kcl/src/parsing/parser.rs
assertion_line: 3964
expression: actual expression: actual
snapshot_kind: text
--- ---
{ {
"body": [ "body": [
@ -31,7 +29,10 @@ snapshot_kind: text
"start": 14, "start": 14,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": {
"value": 0.0,
"suffix": "None"
}
}, },
{ {
"argument": { "argument": {
@ -40,7 +41,10 @@ snapshot_kind: text
"start": 18, "start": 18,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 1.0 "value": {
"value": 1.0,
"suffix": "None"
}
}, },
"end": 19, "end": 19,
"operator": "-", "operator": "-",

View File

@ -21,7 +21,10 @@ expression: actual
"start": 14, "start": 14,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 10.0 "value": {
"value": 10.0,
"suffix": "None"
}
}, },
"endInclusive": true, "endInclusive": true,
"start": 10, "start": 10,
@ -31,7 +34,10 @@ expression: actual
"start": 11, "start": 11,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": {
"value": 0.0,
"suffix": "None"
}
}, },
"type": "ArrayRangeExpression", "type": "ArrayRangeExpression",
"type": "ArrayRangeExpression" "type": "ArrayRangeExpression"

View File

@ -23,7 +23,10 @@ expression: actual
"start": 50, "start": 50,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 2.0 "value": {
"value": 2.0,
"suffix": "None"
}
}, },
"end": 51, "end": 51,
"start": 43, "start": 43,

View File

@ -25,7 +25,10 @@ expression: actual
"start": 26, "start": 26,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": {
"value": 0.0,
"suffix": "None"
}
}, },
{ {
"end": 29, "end": 29,
@ -33,7 +36,10 @@ expression: actual
"start": 28, "start": 28,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": {
"value": 0.0,
"suffix": "None"
}
} }
], ],
"end": 30, "end": 30,
@ -63,7 +69,10 @@ expression: actual
"start": 51, "start": 51,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": {
"value": 0.0,
"suffix": "None"
}
}, },
{ {
"end": 55, "end": 55,
@ -71,7 +80,10 @@ expression: actual
"start": 54, "start": 54,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 1.0 "value": {
"value": 1.0,
"suffix": "None"
}
} }
], ],
"end": 56, "end": 56,
@ -114,7 +126,10 @@ expression: actual
"start": 89, "start": 89,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 1.0 "value": {
"value": 1.0,
"suffix": "None"
}
}, },
{ {
"end": 93, "end": 93,
@ -122,7 +137,10 @@ expression: actual
"start": 92, "start": 92,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 1.0 "value": {
"value": 1.0,
"suffix": "None"
}
} }
], ],
"end": 94, "end": 94,
@ -158,7 +176,10 @@ expression: actual
"start": 118, "start": 118,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 1.0 "value": {
"value": 1.0,
"suffix": "None"
}
}, },
{ {
"end": 122, "end": 122,
@ -166,7 +187,10 @@ expression: actual
"start": 121, "start": 121,
"type": "Literal", "type": "Literal",
"type": "Literal", "type": "Literal",
"value": 0.0 "value": {
"value": 0.0,
"suffix": "None"
}
} }
], ],
"end": 123, "end": 123,

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