Compare commits
128 Commits
v0.44.0
...
franknoiro
Author | SHA1 | Date | |
---|---|---|---|
df8977407c | |||
42016b7335 | |||
09f3e6e170 | |||
3747db01b8 | |||
bf1a42f6c0 | |||
b4d1a36bd9 | |||
b937934834 | |||
0208eecefa | |||
9999e4e62f | |||
b390e3e083 | |||
9a89926749 | |||
d7f834f23d | |||
500d92d1fc | |||
bd10c65bdb | |||
039092ec24 | |||
8a920b6cb7 | |||
c1db093e0f | |||
5f8ae22b04 | |||
dc7b901eb9 | |||
60dcf9d79e | |||
520f899ad4 | |||
41340c8bf0 | |||
a78ec6cd17 | |||
de526ae36e | |||
4a8e582865 | |||
1854064274 | |||
4a8897be4b | |||
83f458fc36 | |||
a686fe914b | |||
cba2349064 | |||
5ae92bcf5c | |||
ad8e306bdb | |||
508e1c919c | |||
58ec6100c4 | |||
11eceefedf | |||
11a678df5b | |||
22c0003eb1 | |||
8f0a40ba6e | |||
89b0ccb090 | |||
5d22308ad2 | |||
5580631c8f | |||
e1494c9f16 | |||
4a0d852a3c | |||
b213834316 | |||
1d8348c2cf | |||
2227287c9d | |||
680fc30682 | |||
40fb6a44d3 | |||
5713bfd9fa | |||
77902d550f | |||
db895d6801 | |||
3e1f8584ea | |||
2501a98cd9 | |||
e60b0e64ba | |||
3379cc489a | |||
a8b7328f65 | |||
ab6995bde3 | |||
6df5e70186 | |||
6b1cc36911 | |||
6a16e47491 | |||
f4f0533179 | |||
6360b8acac | |||
064a41d675 | |||
e075622a7f | |||
7a7929211a | |||
1f5f42963d | |||
235e6a1056 | |||
09cfbc1837 | |||
319029235c | |||
a7f4b0f037 | |||
d3afa38bd5 | |||
45416df215 | |||
ee54cdd27a | |||
84ae567f37 | |||
f94671f1bb | |||
bcf3790266 | |||
d70ebca165 | |||
d8a9abba69 | |||
0fd18c14ef | |||
36d4830c34 | |||
4ce6054e64 | |||
ced49f8ddc | |||
e063622139 | |||
42178fa649 | |||
4bb23bc917 | |||
72272d5d98 | |||
5ef0a1e75f | |||
d8dc49b08a | |||
87eabef450 | |||
40e4f2236f | |||
663076f790 | |||
f2c76b0509 | |||
481bef859a | |||
1a67d344ee | |||
774e3efcb7 | |||
4ec44690bf | |||
d2f0865f95 | |||
84d17454e9 | |||
5a5138a703 | |||
33468c4c96 | |||
b3467bbe5a | |||
90086488b5 | |||
32e8975799 | |||
648616c667 | |||
482487cf57 | |||
5fe3023be9 | |||
30397ba7ab | |||
3344208c63 | |||
fcf3272ad2 | |||
d3e4b123d0 | |||
2bb548c000 | |||
b09c240e36 | |||
6c9d14af93 | |||
0642e49189 | |||
6add1d73ad | |||
68c89746c7 | |||
9f323c207c | |||
7197b6c85d | |||
913f2641c3 | |||
e9086c54ba | |||
9f93346dc6 | |||
1b9f5f20f5 | |||
3865637c61 | |||
2c40e8a97c | |||
c696f0837a | |||
30edf2ad56 | |||
e60cabb193 | |||
1e9cf6f256 |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
7746
docs/kcl/std.json
7746
docs/kcl/std.json
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -59,7 +59,23 @@ Any KCL value.
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Number`| | No |
|
||||
| `value` |`number`| | No |
|
||||
| `ty` |[`NumericType`](/docs/kcl/types/NumericType)| Any KCL value. | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Int`| | No |
|
||||
| `value` |`integer`| | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
|
||||
|
||||
|
@ -1,250 +0,0 @@
|
||||
---
|
||||
title: "NumericType"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**This schema accepts exactly one of the following:**
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
**This schema accepts exactly one of the following:**
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Count`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
**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 |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Length`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
**This schema accepts exactly one of the following:**
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Degrees`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Radians`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Angle`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Known`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Default`| | No |
|
||||
| `len` |[`UnitLen`](/docs/kcl/types/UnitLen)| | No |
|
||||
| `angle` |[`UnitAngle`](/docs/kcl/types/UnitAngle)| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Unknown`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Any`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
@ -1,47 +0,0 @@
|
||||
---
|
||||
title: "UnitAngle"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**This schema accepts exactly one of the following:**
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Degrees`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Radians`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
@ -1,186 +0,0 @@
|
||||
---
|
||||
title: "UnitType"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**This schema accepts exactly one of the following:**
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Count`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
**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 |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Length`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
**This schema accepts exactly one of the following:**
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Degrees`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Radians`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Angle`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
@ -24,7 +24,7 @@ sketch001 = startSketchOn('XZ')
|
||||
revolve001 = revolve({ axis = "X" }, sketch001)
|
||||
triangle()
|
||||
|> extrude(length = 30)
|
||||
plane001 = offsetPlane('XY', offset = 10)
|
||||
plane001 = offsetPlane('XY', 10)
|
||||
sketch002 = startSketchOn(plane001)
|
||||
|> startProfileAt([-20, 0], %)
|
||||
|> line(end = [5, -15])
|
||||
@ -35,7 +35,7 @@ sketch002 = startSketchOn(plane001)
|
||||
extrude001 = extrude(sketch002, length = 10)
|
||||
`
|
||||
|
||||
const FEATURE_TREE_SKETCH_CODE = `sketch001 = startSketchOn('XZ')
|
||||
const FEAUTRE_TREE_SKETCH_CODE = `sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> angledLine([0, 4], %, $rectangleSegmentA001)
|
||||
|> angledLine([
|
||||
@ -54,7 +54,7 @@ sketch002 = startSketchOn(extrude001, rectangleSegmentB001)
|
||||
center = [-1, 2],
|
||||
radius = .5
|
||||
}, %)
|
||||
plane001 = offsetPlane('XZ', offset = -5)
|
||||
plane001 = offsetPlane('XZ', -5)
|
||||
sketch003 = startSketchOn(plane001)
|
||||
|> circle({ center = [0, 0], radius = 5 }, %)
|
||||
`
|
||||
@ -116,7 +116,7 @@ test.describe('Feature Tree pane', () => {
|
||||
await testViewSource({
|
||||
operationName: 'Offset Plane',
|
||||
operationIndex: 0,
|
||||
expectedActiveLine: "plane001 = offsetPlane('XY', offset = 10)",
|
||||
expectedActiveLine: "plane001 = offsetPlane('XY', 10)",
|
||||
})
|
||||
await testViewSource({
|
||||
operationName: 'Extrude',
|
||||
@ -153,16 +153,33 @@ test.describe('Feature Tree pane', () => {
|
||||
`User can edit sketch (but not on offset plane yet) from the feature tree`,
|
||||
{ tag: '@electron' },
|
||||
async ({ context, homePage, scene, editor, toolbar, page }) => {
|
||||
await context.addInitScript((initialCode) => {
|
||||
localStorage.setItem('persistCode', initialCode)
|
||||
}, FEATURE_TREE_SKETCH_CODE)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
const unavailableToastMessage = page.getByText(
|
||||
'Editing sketches on faces or offset planes through the feature tree is not yet supported'
|
||||
)
|
||||
|
||||
await test.step('force re-exe', async () => {
|
||||
await page.waitForTimeout(1000)
|
||||
await editor.replaceCode('90', '91')
|
||||
await page.waitForTimeout(1500)
|
||||
await context.folderSetupFn(async (dir) => {
|
||||
const bracketDir = join(dir, 'test-sample')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.writeFile(
|
||||
join(bracketDir, 'main.kcl'),
|
||||
FEAUTRE_TREE_SKETCH_CODE,
|
||||
'utf-8'
|
||||
)
|
||||
})
|
||||
|
||||
await test.step('setup test', async () => {
|
||||
await homePage.expectState({
|
||||
projectCards: [
|
||||
{
|
||||
title: 'test-sample',
|
||||
fileCount: 1,
|
||||
},
|
||||
],
|
||||
sortBy: 'last-modified-desc',
|
||||
})
|
||||
await homePage.openProject('test-sample')
|
||||
await scene.waitForExecutionDone()
|
||||
await toolbar.openFeatureTreePane()
|
||||
})
|
||||
|
||||
await test.step('On a default plane should work', async () => {
|
||||
@ -182,23 +199,24 @@ test.describe('Feature Tree pane', () => {
|
||||
await test.step('On an extrude face should *not* work', async () => {
|
||||
// Tooltip is getting in the way of clicking, so I'm first closing the pane
|
||||
await toolbar.closeFeatureTreePane()
|
||||
await page.waitForTimeout(1000)
|
||||
await editor.replaceCode('91', '90')
|
||||
await page.waitForTimeout(2000)
|
||||
await (await toolbar.getFeatureTreeOperation('Sketch', 1)).dblclick()
|
||||
|
||||
await expect(
|
||||
toolbar.exitSketchBtn,
|
||||
'We should be in sketch mode now'
|
||||
unavailableToastMessage,
|
||||
'We should see a toast message about this'
|
||||
).toBeVisible()
|
||||
await editor.expectState({
|
||||
highlightedCode: '',
|
||||
diagnostics: [],
|
||||
activeLines: [
|
||||
'sketch002=startSketchOn(extrude001,rectangleSegmentB001)',
|
||||
],
|
||||
})
|
||||
await toolbar.exitSketchBtn.click()
|
||||
await unavailableToastMessage.waitFor({ state: 'detached' })
|
||||
// TODO - turn on once we update the artifactGraph in Rust
|
||||
// to include the proper source location for the extrude face
|
||||
// await expect(
|
||||
// toolbar.exitSketchBtn,
|
||||
// 'We should be in sketch mode now'
|
||||
// ).toBeVisible()
|
||||
// await editor.expectState({
|
||||
// highlightedCode: '',
|
||||
// diagnostics: [],
|
||||
// activeLines: ['|>circle({center=[-1,2],radius=.5},%)'],
|
||||
// })
|
||||
// await toolbar.exitSketchBtn.click()
|
||||
})
|
||||
|
||||
await test.step('On an offset plane should *not* work', async () => {
|
||||
@ -208,7 +226,7 @@ test.describe('Feature Tree pane', () => {
|
||||
await editor.expectState({
|
||||
highlightedCode: '',
|
||||
diagnostics: [],
|
||||
activeLines: ['sketch003=startSketchOn(plane001)'],
|
||||
activeLines: ['|>circle({center=[0,0],radius=5},%)'],
|
||||
})
|
||||
await expect(
|
||||
toolbar.exitSketchBtn,
|
||||
@ -324,8 +342,7 @@ test.describe('Feature Tree pane', () => {
|
||||
toolbar,
|
||||
cmdBar,
|
||||
}) => {
|
||||
const testCode = (value: string) =>
|
||||
`p = offsetPlane('XY', offset = ${value})`
|
||||
const testCode = (value: string) => `p = offsetPlane('XY', ${value})`
|
||||
const initialInput = '10'
|
||||
const initialCode = testCode(initialInput)
|
||||
const newInput = '5 + 10'
|
||||
|
@ -1028,7 +1028,7 @@ openSketch = startSketchOn('XY')
|
||||
// One dumb hardcoded screen pixel value
|
||||
const testPoint = { x: 700, y: 150 }
|
||||
const [clickOnXzPlane] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
||||
const expectedOutput = `plane001 = offsetPlane('XZ', offset = 5)`
|
||||
const expectedOutput = `plane001 = offsetPlane('XZ', 5)`
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
// FIXME: Since there is no KCL code loaded. We need to wait for the scene to load before we continue.
|
||||
@ -1164,7 +1164,7 @@ openSketch = startSketchOn('XY')
|
||||
}) => {
|
||||
const initialCode = `sketch001 = startSketchOn('XZ')
|
||||
|> circle({ center = [0, 0], radius = 30 }, %)
|
||||
plane001 = offsetPlane('XZ', offset = 50)
|
||||
plane001 = offsetPlane('XZ', 50)
|
||||
sketch002 = startSketchOn(plane001)
|
||||
|> circle({ center = [0, 0], radius = 20 }, %)
|
||||
`
|
||||
@ -1250,7 +1250,7 @@ openSketch = startSketchOn('XY')
|
||||
}) => {
|
||||
const initialCode = `sketch001 = startSketchOn('XZ')
|
||||
|> circle({ center = [0, 0], radius = 30 }, %)
|
||||
plane001 = offsetPlane('XZ', offset = 50)
|
||||
plane001 = offsetPlane('XZ', 50)
|
||||
sketch002 = startSketchOn(plane001)
|
||||
|> circle({ center = [0, 0], radius = 20 }, %)
|
||||
loft001 = loft([sketch001, sketch002])
|
||||
@ -1297,7 +1297,7 @@ loft001 = loft([sketch001, sketch002])
|
||||
await page.waitForTimeout(1000)
|
||||
await clickOnSketch2()
|
||||
await expect(page.locator('.cm-activeLine')).toHaveText(`
|
||||
plane001 = offsetPlane('XZ', offset = 50)
|
||||
plane001 = offsetPlane('XZ', 50)
|
||||
`)
|
||||
await page.keyboard.press('Backspace')
|
||||
// Check for sketch 1
|
||||
@ -2448,18 +2448,19 @@ extrude002 = extrude(sketch002, length = 50)
|
||||
await context.addInitScript((initialCode) => {
|
||||
localStorage.setItem('persistCode', initialCode)
|
||||
}, initialCode)
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
await scene.waitForExecutionDone()
|
||||
|
||||
// One dumb hardcoded screen pixel value
|
||||
const testPoint = { x: 580, y: 320 }
|
||||
const testPoint = { x: 550, y: 295 }
|
||||
const [clickOnCap] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
||||
const shellTarget = hasExtrudesInPipe ? 'sketch002' : 'extrude002'
|
||||
const shellDeclaration = `shell001 = shell(${shellTarget}, faces = ['end'], thickness = 5)`
|
||||
|
||||
await test.step(`Look for the grey of the shape`, async () => {
|
||||
await scene.expectPixelColor([113, 113, 113], testPoint, 15)
|
||||
await toolbar.closePane('code')
|
||||
await scene.expectPixelColor([128, 128, 128], testPoint, 15)
|
||||
})
|
||||
|
||||
await test.step(`Go through the command bar flow, selecting a cap and keeping default thickness`, async () => {
|
||||
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Binary file not shown.
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 127 KiB |
@ -390,47 +390,43 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
|
||||
`rev = revolve({ axis: 'y' }, part009)`
|
||||
)
|
||||
|
||||
// FIXME (commented section below), this test would select a wall that had a sketch on it, and delete the underlying extrude
|
||||
// and replace the sketch on face with a hard coded custom plane, but since there was a sketch on that plane maybe it
|
||||
// should have delete the sketch? it's broken atm, but not sure if worth fixing since desired behaviour is a little
|
||||
// vague
|
||||
// // DELETE PARENT EXTRUDE
|
||||
// await page.mouse.click(parentExtrude.x, parentExtrude.y)
|
||||
// await page.waitForTimeout(100)
|
||||
// await expect(page.locator('.cm-activeLine')).toHaveText(
|
||||
// '|> line(end = [170.36, -121.61], tag = $seg01)'
|
||||
// )
|
||||
// await u.clearCommandLogs()
|
||||
// await page.keyboard.press('Backspace')
|
||||
// await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
|
||||
// await page.waitForTimeout(200)
|
||||
// await expect(u.codeLocator).not.toContainText(
|
||||
// `extrude001 = extrude(sketch001, length = 50)`
|
||||
// )
|
||||
// await expect(u.codeLocator).toContainText(`sketch005 = startSketchOn({
|
||||
// plane = {
|
||||
// origin = { x = 0, y = -50, z = 0 },
|
||||
// xAxis = { x = 1, y = 0, z = 0 },
|
||||
// yAxis = { x = 0, y = 0, z = 1 },
|
||||
// zAxis = { x = 0, y = -1, z = 0 }
|
||||
// }
|
||||
// })`)
|
||||
// await expect(u.codeLocator).toContainText(`sketch003 = startSketchOn({
|
||||
// plane = {
|
||||
// origin = { x = 116.53, y = 0, z = 163.25 },
|
||||
// xAxis = { x = -0.81, y = 0, z = 0.58 },
|
||||
// yAxis = { x = 0, y = -1, z = 0 },
|
||||
// zAxis = { x = 0.58, y = 0, z = 0.81 }
|
||||
// }
|
||||
// })`)
|
||||
// await expect(u.codeLocator).toContainText(`sketch002 = startSketchOn({
|
||||
// plane = {
|
||||
// origin = { x = -91.74, y = 0, z = 80.89 },
|
||||
// xAxis = { x = -0.66, y = 0, z = -0.75 },
|
||||
// yAxis = { x = 0, y = -1, z = 0 },
|
||||
// zAxis = { x = -0.75, y = 0, z = 0.66 }
|
||||
// }
|
||||
// })`)
|
||||
// DELETE PARENT EXTRUDE
|
||||
await page.mouse.click(parentExtrude.x, parentExtrude.y)
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-activeLine')).toHaveText(
|
||||
'|> line(end = [170.36, -121.61], tag = $seg01)'
|
||||
)
|
||||
await u.clearCommandLogs()
|
||||
await page.keyboard.press('Backspace')
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
|
||||
await page.waitForTimeout(200)
|
||||
await expect(u.codeLocator).not.toContainText(
|
||||
`extrude001 = extrude(sketch001, length = 50)`
|
||||
)
|
||||
await expect(u.codeLocator).toContainText(`sketch005 = startSketchOn({
|
||||
plane = {
|
||||
origin = { x = 0, y = -50, z = 0 },
|
||||
xAxis = { x = 1, y = 0, z = 0 },
|
||||
yAxis = { x = 0, y = 0, z = 1 },
|
||||
zAxis = { x = 0, y = -1, z = 0 }
|
||||
}
|
||||
})`)
|
||||
await expect(u.codeLocator).toContainText(`sketch003 = startSketchOn({
|
||||
plane = {
|
||||
origin = { x = 116.53, y = 0, z = 163.25 },
|
||||
xAxis = { x = -0.81, y = 0, z = 0.58 },
|
||||
yAxis = { x = 0, y = -1, z = 0 },
|
||||
zAxis = { x = 0.58, y = 0, z = 0.81 }
|
||||
}
|
||||
})`)
|
||||
await expect(u.codeLocator).toContainText(`sketch002 = startSketchOn({
|
||||
plane = {
|
||||
origin = { x = -91.74, y = 0, z = 80.89 },
|
||||
xAxis = { x = -0.66, y = 0, z = -0.75 },
|
||||
yAxis = { x = 0, y = -1, z = 0 },
|
||||
zAxis = { x = -0.75, y = 0, z = 0.66 }
|
||||
}
|
||||
})`)
|
||||
|
||||
// DELETE SOLID 2D
|
||||
await page.mouse.click(solid2d.x, solid2d.y)
|
||||
@ -458,14 +454,15 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
|
||||
await page.waitForTimeout(200)
|
||||
await expect(u.codeLocator).not.toContainText(codeToBeDeletedSnippet)
|
||||
})
|
||||
test.fixme(
|
||||
"Deleting solid that the AST mod can't handle results in a toast message",
|
||||
async ({ page, homePage }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
test("Deleting solid that the AST mod can't handle results in a toast message", async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-79.26, 95.04], %)
|
||||
|> line(end = [112.54, 127.64], tag = $seg02)
|
||||
|> line(end = [170.36, -121.61], tag = $seg01)
|
||||
@ -480,49 +477,48 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
`
|
||||
)
|
||||
}, KCL_DEFAULT_LENGTH)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await u.openAndClearDebugPanel()
|
||||
await u.sendCustomCmd({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_look_at',
|
||||
vantage: { x: 1139.49, y: -7053, z: 8597.31 },
|
||||
center: { x: -2206.68, y: -1298.36, z: 60 },
|
||||
up: { x: 0, y: 0, z: 1 },
|
||||
},
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
await u.sendCustomCmd({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_get_settings',
|
||||
},
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// attempt delete
|
||||
await page.mouse.click(930, 139)
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-activeLine')).toHaveText(
|
||||
'|> line(end = [170.36, -121.61], tag = $seg01)'
|
||||
)
|
||||
await u.clearCommandLogs()
|
||||
await page.keyboard.press('Backspace')
|
||||
}, KCL_DEFAULT_LENGTH)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
|
||||
await expect(page.getByText('Unable to delete selection')).toBeVisible()
|
||||
}
|
||||
)
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await u.openAndClearDebugPanel()
|
||||
await u.sendCustomCmd({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_look_at',
|
||||
vantage: { x: 1139.49, y: -7053, z: 8597.31 },
|
||||
center: { x: -2206.68, y: -1298.36, z: 60 },
|
||||
up: { x: 0, y: 0, z: 1 },
|
||||
},
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
await u.sendCustomCmd({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_get_settings',
|
||||
},
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// attempt delete
|
||||
await page.mouse.click(930, 139)
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-activeLine')).toHaveText(
|
||||
'|> line(end = [170.36, -121.61], tag = $seg01)'
|
||||
)
|
||||
await u.clearCommandLogs()
|
||||
await page.keyboard.press('Backspace')
|
||||
|
||||
await expect(page.getByText('Unable to delete selection')).toBeVisible()
|
||||
})
|
||||
test('Hovering over 3d features highlights code, clicking puts the cursor in the right place and sends selection id to engine', async ({
|
||||
page,
|
||||
homePage,
|
||||
|
@ -16,7 +16,8 @@ mac:
|
||||
arch:
|
||||
- x64
|
||||
- arm64
|
||||
notarize: true
|
||||
notarize:
|
||||
teamId: 92H8YB3B95
|
||||
fileAssociations:
|
||||
- ext: kcl
|
||||
name: kcl
|
||||
@ -31,11 +32,10 @@ win:
|
||||
arch:
|
||||
- x64
|
||||
- arm64
|
||||
signtoolOptions:
|
||||
sign: "./scripts/sign-win.js"
|
||||
signingHashAlgorithms:
|
||||
- sha256
|
||||
publisherName: "KittyCAD Inc" # needs to be exactly like on Digicert
|
||||
signingHashAlgorithms:
|
||||
- sha256
|
||||
sign: "./scripts/sign-win.js"
|
||||
publisherName: "KittyCAD Inc" # needs to be exactly like on Digicert
|
||||
icon: "assets/icon.ico"
|
||||
fileAssociations:
|
||||
- ext: kcl
|
||||
|
18
package.json
18
package.json
@ -40,7 +40,7 @@
|
||||
"codemirror": "^6.0.1",
|
||||
"decamelize": "^6.0.0",
|
||||
"diff": "^7.0.0",
|
||||
"electron-updater": "^6.5.0",
|
||||
"electron-updater": "6.3.0",
|
||||
"fuse.js": "^7.0.0",
|
||||
"html2canvas-pro": "^1.5.8",
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
@ -85,7 +85,7 @@
|
||||
"fmt": "prettier --write ./src *.ts *.json *.js ./e2e ./packages",
|
||||
"fmt-check": "prettier --check ./src *.ts *.json *.js ./e2e ./packages",
|
||||
"fetch:wasm": "./get-latest-wasm-bundle.sh",
|
||||
"fetch:samples": "echo \"Fetching latest KCL samples...\" && curl -o public/kcl-samples-manifest-fallback.json https://raw.githubusercontent.com/KittyCAD/kcl-samples/next/manifest.json",
|
||||
"fetch:samples": "echo \"Fetching latest KCL samples...\" && curl -o public/kcl-samples-manifest-fallback.json https://raw.githubusercontent.com/KittyCAD/kcl-samples/achalmers/kw-pattern-transform2/manifest.json",
|
||||
"isomorphic-copy-wasm": "(copy src/wasm-lib/pkg/wasm_lib_bg.wasm public || cp src/wasm-lib/pkg/wasm_lib_bg.wasm public)",
|
||||
"build:wasm-dev": "yarn wasm-prep && (cd src/wasm-lib && wasm-pack build --dev --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && yarn isomorphic-copy-wasm && yarn fmt",
|
||||
"build:wasm": "yarn wasm-prep && cd src/wasm-lib && wasm-pack build --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings && cd ../.. && yarn isomorphic-copy-wasm && yarn fmt",
|
||||
@ -145,11 +145,10 @@
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||
"@babel/preset-env": "^7.25.4",
|
||||
"@electron-forge/cli": "^7.6.1",
|
||||
"@electron-forge/plugin-fuses": "^7.6.1",
|
||||
"@electron-forge/plugin-vite": "^7.6.1",
|
||||
"@electron/fuses": "^1.8.0",
|
||||
"@electron/notarize": "^2.5.0",
|
||||
"@electron-forge/cli": "7.4.0",
|
||||
"@electron-forge/plugin-fuses": "7.4.0",
|
||||
"@electron-forge/plugin-vite": "7.4.0",
|
||||
"@electron/fuses": "1.8.0",
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"@lezer/generator": "^1.7.2",
|
||||
"@nabla/vite-plugin-eslint": "^2.0.5",
|
||||
@ -176,8 +175,9 @@
|
||||
"@vitest/web-worker": "^1.5.0",
|
||||
"@xstate/cli": "^0.5.17",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"electron": "^34.1.1",
|
||||
"electron-builder": "^26.0.6",
|
||||
"electron": "32.1.2",
|
||||
"electron-builder": "24.13.3",
|
||||
"electron-notarize": "1.2.2",
|
||||
"eslint": "^8.0.1",
|
||||
"eslint-plugin-css-modules": "^2.12.0",
|
||||
"eslint-plugin-import": "^2.30.0",
|
||||
|
@ -1,5 +1,4 @@
|
||||
@precedence {
|
||||
annotation
|
||||
member
|
||||
call
|
||||
exp @left
|
||||
@ -21,12 +20,9 @@ statement[@isGroup=Statement] {
|
||||
FunctionDeclaration { kw<"export">? kw<"fn"> VariableDefinition Equals? ParamList Arrow? Body } |
|
||||
VariableDeclaration { kw<"export">? (kw<"var"> | kw<"let"> | kw<"const">)? VariableDefinition Equals expression } |
|
||||
ReturnStatement { kw<"return"> expression } |
|
||||
ExpressionStatement { expression } |
|
||||
Annotation { AnnotationName AnnotationList? }
|
||||
ExpressionStatement { expression }
|
||||
}
|
||||
|
||||
AnnotationList { !annotation "(" commaSep<AnnotationProperty> ")" }
|
||||
|
||||
ParamList { "(" commaSep<Parameter { VariableDefinition "?"? (":" type)? }> ")" }
|
||||
|
||||
Body { "{" statement* "}" }
|
||||
@ -63,12 +59,6 @@ UnaryOp { AddOp | BangOp }
|
||||
|
||||
ObjectProperty { PropertyName (":" | Equals) expression }
|
||||
|
||||
AnnotationProperty {
|
||||
PropertyName
|
||||
( AddOp | MultOp | ExpOp | LogicOp | BangOp | CompOp | Equals | Arrow | PipeOperator | PipeSubstitution )
|
||||
expression
|
||||
}
|
||||
|
||||
LabeledArgument { ArgumentLabel Equals expression }
|
||||
|
||||
ArgumentList { "(" commaSep<LabeledArgument | expression> ")" }
|
||||
@ -115,7 +105,6 @@ commaSep1NoTrailingComma<term> { term ("," term)* }
|
||||
PipeSubstitution { "%" }
|
||||
|
||||
identifier { (@asciiLetter | "_") (@asciiLetter | @digit | "_")* }
|
||||
AnnotationName { "@" identifier? }
|
||||
PropertyName { identifier }
|
||||
TagDeclarator { "$" identifier }
|
||||
|
||||
|
@ -1,153 +0,0 @@
|
||||
# alone
|
||||
|
||||
@a
|
||||
|
||||
==>
|
||||
Program(Annotation(AnnotationName))
|
||||
|
||||
# alone and anonymous
|
||||
|
||||
@
|
||||
|
||||
==>
|
||||
Program(Annotation(AnnotationName))
|
||||
|
||||
# empty
|
||||
|
||||
@ann()
|
||||
|
||||
==>
|
||||
Program(Annotation(AnnotationName,
|
||||
AnnotationList))
|
||||
|
||||
# empty and anonymous
|
||||
|
||||
@()
|
||||
|
||||
==>
|
||||
Program(Annotation(AnnotationName,
|
||||
AnnotationList))
|
||||
|
||||
# equals
|
||||
|
||||
@setting(a=1)
|
||||
|
||||
==>
|
||||
Program(Annotation(AnnotationName,
|
||||
AnnotationList(AnnotationProperty(PropertyName,
|
||||
Equals,
|
||||
Number))))
|
||||
|
||||
# operator
|
||||
|
||||
@ann(a*1)
|
||||
|
||||
==>
|
||||
Program(Annotation(AnnotationName,
|
||||
AnnotationList(AnnotationProperty(PropertyName,
|
||||
MultOp,
|
||||
Number))))
|
||||
|
||||
# anonymous
|
||||
|
||||
@(a=1)
|
||||
|
||||
==>
|
||||
Program(Annotation(AnnotationName,
|
||||
AnnotationList(AnnotationProperty(PropertyName,
|
||||
Equals,
|
||||
Number))))
|
||||
|
||||
# complex expr
|
||||
|
||||
@ann(a=(1+2+f('yes')))
|
||||
|
||||
==>
|
||||
Program(Annotation(AnnotationName,
|
||||
AnnotationList(AnnotationProperty(PropertyName,
|
||||
Equals,
|
||||
ParenthesizedExpression(BinaryExpression(BinaryExpression(Number,
|
||||
AddOp,
|
||||
Number),
|
||||
AddOp,
|
||||
CallExpression(VariableName,
|
||||
ArgumentList(String))))))))
|
||||
|
||||
# many args
|
||||
|
||||
@ann(a=1, b=2)
|
||||
|
||||
==>
|
||||
Program(Annotation(AnnotationName,
|
||||
AnnotationList(AnnotationProperty(PropertyName,
|
||||
Equals,
|
||||
Number),
|
||||
AnnotationProperty(PropertyName,
|
||||
Equals,
|
||||
Number))))
|
||||
|
||||
# space around op
|
||||
|
||||
@ann(a / 1)
|
||||
|
||||
==>
|
||||
Program(Annotation(AnnotationName,
|
||||
AnnotationList(AnnotationProperty(PropertyName,
|
||||
MultOp,
|
||||
Number))))
|
||||
|
||||
# space around sep
|
||||
|
||||
@ann(a/1 , b/2)
|
||||
|
||||
==>
|
||||
Program(Annotation(AnnotationName,
|
||||
AnnotationList(AnnotationProperty(PropertyName,
|
||||
MultOp,
|
||||
Number),
|
||||
AnnotationProperty(PropertyName,
|
||||
MultOp,
|
||||
Number))))
|
||||
|
||||
# trailing sep
|
||||
|
||||
@ann(a=1,)
|
||||
|
||||
==>
|
||||
Program(Annotation(AnnotationName,
|
||||
AnnotationList(AnnotationProperty(PropertyName,
|
||||
Equals,
|
||||
Number))))
|
||||
|
||||
# lone sep
|
||||
|
||||
@ann(,)
|
||||
|
||||
==>
|
||||
Program(Annotation(AnnotationName,
|
||||
AnnotationList))
|
||||
|
||||
# inside fn
|
||||
|
||||
fn f() {
|
||||
@anno(b=2)
|
||||
}
|
||||
|
||||
==>
|
||||
Program(FunctionDeclaration(fn,
|
||||
VariableDefinition,
|
||||
ParamList,
|
||||
Body(Annotation(AnnotationName,
|
||||
AnnotationList(AnnotationProperty(PropertyName,
|
||||
Equals,
|
||||
Number))))))
|
||||
|
||||
# laxer with space than the language parser is
|
||||
|
||||
@anno (b=2)
|
||||
|
||||
==>
|
||||
Program(Annotation(AnnotationName,
|
||||
AnnotationList(AnnotationProperty(PropertyName,
|
||||
Equals,
|
||||
Number))))
|
@ -69,7 +69,6 @@ import {
|
||||
startSketchOnDefault,
|
||||
} from 'lang/modifyAst'
|
||||
import {
|
||||
KclValue,
|
||||
PathToNode,
|
||||
Program,
|
||||
VariableDeclaration,
|
||||
@ -805,33 +804,12 @@ export const ModelingMachineProvider = ({
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(plane)) return Promise.reject(plane)
|
||||
let sketch: KclValue | null = null
|
||||
for (const variable of Object.values(
|
||||
kclManager.execState.variables
|
||||
)) {
|
||||
// find programMemory that matches path artifact
|
||||
if (
|
||||
|
||||
const sketch = Object.values(kclManager.execState.variables).find(
|
||||
(variable) =>
|
||||
variable?.type === 'Sketch' &&
|
||||
variable.value.artifactId === plane.pathIds[0]
|
||||
) {
|
||||
sketch = variable
|
||||
break
|
||||
}
|
||||
if (
|
||||
// if the variable is an sweep, check if the underlying sketch matches the artifact
|
||||
variable?.type === 'Solid' &&
|
||||
variable.value.sketch.on.type === 'plane' &&
|
||||
variable.value.sketch.artifactId === plane.pathIds[0]
|
||||
) {
|
||||
sketch = {
|
||||
type: 'Sketch',
|
||||
value: variable.value.sketch,
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!sketch || sketch.type !== 'Sketch')
|
||||
return Promise.reject(new Error('No sketch'))
|
||||
)
|
||||
if (!sketch || sketch.type !== 'Sketch')
|
||||
return Promise.reject(new Error('No sketch'))
|
||||
const info = await getSketchOrientationDetails(sketch.value)
|
||||
@ -1573,11 +1551,8 @@ export const ModelingMachineProvider = ({
|
||||
? pathToProfile
|
||||
: updatedSketchNodePaths[0]
|
||||
}
|
||||
|
||||
if (doesNeedSplitting || indexToDelete >= 0) {
|
||||
await kclManager.executeAstMock(moddedAst)
|
||||
await codeManager.updateEditorWithAstAndWriteToFile(moddedAst)
|
||||
}
|
||||
await kclManager.executeAstMock(moddedAst)
|
||||
await codeManager.updateEditorWithAstAndWriteToFile(moddedAst)
|
||||
return {
|
||||
updatedEntryNodePath: pathToProfile,
|
||||
updatedSketchNodePaths: updatedSketchNodePaths,
|
||||
|
@ -8,7 +8,7 @@ import { editorManager } from 'lib/singletons'
|
||||
import { Annotation, Transaction } from '@codemirror/state'
|
||||
import { EditorView, KeyBinding } from '@codemirror/view'
|
||||
import { recast, Program } from 'lang/wasm'
|
||||
import { err, reportRejection } from 'lib/trap'
|
||||
import { err } from 'lib/trap'
|
||||
import { Compartment } from '@codemirror/state'
|
||||
import { history } from '@codemirror/commands'
|
||||
|
||||
@ -168,7 +168,7 @@ export default class CodeManager {
|
||||
const newCode = recast(ast)
|
||||
if (err(newCode)) return
|
||||
this.updateCodeStateEditor(newCode)
|
||||
this.writeToFile().catch(reportRejection)
|
||||
await this.writeToFile()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -239,7 +239,6 @@ const newVar = myVar + 1`
|
||||
expect(mem['three']).toEqual({
|
||||
type: 'Number',
|
||||
value: 3,
|
||||
ty: expect.any(Object),
|
||||
__meta: [
|
||||
{
|
||||
sourceRange: [14, 15, 0],
|
||||
@ -249,23 +248,12 @@ const newVar = myVar + 1`
|
||||
expect(mem['yo']).toEqual({
|
||||
type: 'Array',
|
||||
value: [
|
||||
{
|
||||
type: 'Number',
|
||||
value: 1,
|
||||
ty: expect.any(Object),
|
||||
__meta: [{ sourceRange: [28, 29, 0] }],
|
||||
},
|
||||
{ type: 'Number', value: 1, __meta: [{ sourceRange: [28, 29, 0] }] },
|
||||
{ type: 'String', value: '2', __meta: [{ sourceRange: [31, 34, 0] }] },
|
||||
{
|
||||
type: 'Number',
|
||||
value: 3,
|
||||
ty: expect.any(Object),
|
||||
__meta: [{ sourceRange: [14, 15, 0] }],
|
||||
},
|
||||
{ type: 'Number', value: 3, __meta: [{ sourceRange: [14, 15, 0] }] },
|
||||
{
|
||||
type: 'Number',
|
||||
value: 9,
|
||||
ty: expect.any(Object),
|
||||
__meta: [{ sourceRange: [43, 44, 0] }, { sourceRange: [47, 48, 0] }],
|
||||
},
|
||||
],
|
||||
@ -293,19 +281,16 @@ const newVar = myVar + 1`
|
||||
anum: {
|
||||
type: 'Number',
|
||||
value: 2,
|
||||
ty: expect.any(Object),
|
||||
__meta: [{ sourceRange: [47, 48, 0] }],
|
||||
},
|
||||
identifier: {
|
||||
type: 'Number',
|
||||
value: 3,
|
||||
ty: expect.any(Object),
|
||||
__meta: [{ sourceRange: [14, 15, 0] }],
|
||||
},
|
||||
binExp: {
|
||||
type: 'Number',
|
||||
value: 9,
|
||||
ty: expect.any(Object),
|
||||
__meta: [{ sourceRange: [77, 78, 0] }, { sourceRange: [81, 82, 0] }],
|
||||
},
|
||||
},
|
||||
@ -419,7 +404,6 @@ describe('testing math operators', () => {
|
||||
],
|
||||
type: 'Number',
|
||||
value: 1,
|
||||
ty: expect.any(Object),
|
||||
},
|
||||
{
|
||||
__meta: [
|
||||
@ -429,7 +413,6 @@ describe('testing math operators', () => {
|
||||
],
|
||||
type: 'Number',
|
||||
value: -3,
|
||||
ty: expect.any(Object),
|
||||
},
|
||||
])
|
||||
})
|
||||
|
@ -32,7 +32,7 @@ child_process.spawnSync('git', [
|
||||
'clone',
|
||||
'--single-branch',
|
||||
'--branch',
|
||||
'next',
|
||||
'achalmers/kw-pattern-transform2',
|
||||
URL_GIT_KCL_SAMPLES,
|
||||
DIR_KCL_SAMPLES,
|
||||
])
|
||||
|
@ -821,146 +821,144 @@ sketch003 = startSketchOn('XZ')
|
||||
type: 'segment',
|
||||
},
|
||||
],
|
||||
// TODO FIXME, similar to fix me in e2e/playwright/testing-selections.spec.ts
|
||||
// also related to deleting, deleting in general probably is due for a refactor
|
||||
// [
|
||||
// 'delete extrude',
|
||||
// {
|
||||
// codeBefore: `sketch001 = startSketchOn('XZ')
|
||||
// |> startProfileAt([3.29, 7.86], %)
|
||||
// |> line(end = [2.48, 2.44])
|
||||
// |> line(end = [2.66, 1.17])
|
||||
// |> line(end = [3.75, 0.46])
|
||||
// |> line(end = [4.99, -0.46], tag = $seg01)
|
||||
// |> line(end = [-3.86, -2.73])
|
||||
// |> line(end = [-17.67, 0.85])
|
||||
// |> close()
|
||||
// const extrude001 = extrude(sketch001, length = 10)`,
|
||||
// codeAfter: `sketch001 = startSketchOn('XZ')
|
||||
// |> startProfileAt([3.29, 7.86], %)
|
||||
// |> line(end = [2.48, 2.44])
|
||||
// |> line(end = [2.66, 1.17])
|
||||
// |> line(end = [3.75, 0.46])
|
||||
// |> line(end = [4.99, -0.46], tag = $seg01)
|
||||
// |> line(end = [-3.86, -2.73])
|
||||
// |> line(end = [-17.67, 0.85])
|
||||
// |> close()\n`,
|
||||
// lineOfInterest: 'line(end = [2.66, 1.17])',
|
||||
// type: 'wall',
|
||||
// },
|
||||
// ],
|
||||
// [
|
||||
// 'delete extrude with sketch on it',
|
||||
// {
|
||||
// codeBefore: `myVar = 5
|
||||
// sketch001 = startSketchOn('XZ')
|
||||
// |> startProfileAt([4.46, 5.12], %, $tag)
|
||||
// |> line(end = [0.08, myVar])
|
||||
// |> line(end = [13.03, 2.02], tag = $seg01)
|
||||
// |> line(end = [3.9, -7.6])
|
||||
// |> line(end = [-11.18, -2.15])
|
||||
// |> line(end = [5.41, -9.61])
|
||||
// |> line(end = [-8.54, -2.51])
|
||||
// |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
// |> close()
|
||||
// const extrude001 = extrude(sketch001, length = 5)
|
||||
// sketch002 = startSketchOn(extrude001, seg01)
|
||||
// |> startProfileAt([-12.55, 2.89], %)
|
||||
// |> line(end = [3.02, 1.9])
|
||||
// |> line(end = [1.82, -1.49], tag = $seg02)
|
||||
// |> angledLine([-86, segLen(seg02)], %)
|
||||
// |> line(end = [-3.97, -0.53])
|
||||
// |> line(end = [0.3, 0.84])
|
||||
// |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
// |> close()`,
|
||||
// codeAfter: `myVar = 5
|
||||
// sketch001 = startSketchOn('XZ')
|
||||
// |> startProfileAt([4.46, 5.12], %, $tag)
|
||||
// |> line(end = [0.08, myVar])
|
||||
// |> line(end = [13.03, 2.02], tag = $seg01)
|
||||
// |> line(end = [3.9, -7.6])
|
||||
// |> line(end = [-11.18, -2.15])
|
||||
// |> line(end = [5.41, -9.61])
|
||||
// |> line(end = [-8.54, -2.51])
|
||||
// |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
// |> close()
|
||||
// sketch002 = startSketchOn({
|
||||
// plane = {
|
||||
// origin = { x = 1, y = 2, z = 3 },
|
||||
// xAxis = { x = 4, y = 5, z = 6 },
|
||||
// yAxis = { x = 7, y = 8, z = 9 },
|
||||
// zAxis = { x = 10, y = 11, z = 12 }
|
||||
// }
|
||||
// })
|
||||
// |> startProfileAt([-12.55, 2.89], %)
|
||||
// |> line(end = [3.02, 1.9])
|
||||
// |> line(end = [1.82, -1.49], tag = $seg02)
|
||||
// |> angledLine([-86, segLen(seg02)], %)
|
||||
// |> line(end = [-3.97, -0.53])
|
||||
// |> line(end = [0.3, 0.84])
|
||||
// |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
// |> close()
|
||||
// `,
|
||||
// lineOfInterest: 'line(end = [-11.18, -2.15])',
|
||||
// type: 'wall',
|
||||
// },
|
||||
// ],
|
||||
// [
|
||||
// 'delete extrude with sketch on it 2',
|
||||
// {
|
||||
// codeBefore: `myVar = 5
|
||||
// sketch001 = startSketchOn('XZ')
|
||||
// |> startProfileAt([4.46, 5.12], %, $tag)
|
||||
// |> line(end = [0.08, myVar])
|
||||
// |> line(end = [13.03, 2.02], tag = $seg01)
|
||||
// |> line(end = [3.9, -7.6])
|
||||
// |> line(end = [-11.18, -2.15])
|
||||
// |> line(end = [5.41, -9.61])
|
||||
// |> line(end = [-8.54, -2.51])
|
||||
// |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
// |> close()
|
||||
// const extrude001 = extrude(sketch001, length = 5)
|
||||
// sketch002 = startSketchOn(extrude001, seg01)
|
||||
// |> startProfileAt([-12.55, 2.89], %)
|
||||
// |> line(end = [3.02, 1.9])
|
||||
// |> line(end = [1.82, -1.49], tag = $seg02)
|
||||
// |> angledLine([-86, segLen(seg02)], %)
|
||||
// |> line(end = [-3.97, -0.53])
|
||||
// |> line(end = [0.3, 0.84])
|
||||
// |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
// |> close()`,
|
||||
// codeAfter: `myVar = 5
|
||||
// sketch001 = startSketchOn('XZ')
|
||||
// |> startProfileAt([4.46, 5.12], %, $tag)
|
||||
// |> line(end = [0.08, myVar])
|
||||
// |> line(end = [13.03, 2.02], tag = $seg01)
|
||||
// |> line(end = [3.9, -7.6])
|
||||
// |> line(end = [-11.18, -2.15])
|
||||
// |> line(end = [5.41, -9.61])
|
||||
// |> line(end = [-8.54, -2.51])
|
||||
// |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
// |> close()
|
||||
// sketch002 = startSketchOn({
|
||||
// plane = {
|
||||
// origin = { x = 1, y = 2, z = 3 },
|
||||
// xAxis = { x = 4, y = 5, z = 6 },
|
||||
// yAxis = { x = 7, y = 8, z = 9 },
|
||||
// zAxis = { x = 10, y = 11, z = 12 }
|
||||
// }
|
||||
// })
|
||||
// |> startProfileAt([-12.55, 2.89], %)
|
||||
// |> line(end = [3.02, 1.9])
|
||||
// |> line(end = [1.82, -1.49], tag = $seg02)
|
||||
// |> angledLine([-86, segLen(seg02)], %)
|
||||
// |> line(end = [-3.97, -0.53])
|
||||
// |> line(end = [0.3, 0.84])
|
||||
// |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
// |> close()
|
||||
// `,
|
||||
// lineOfInterest: 'startProfileAt([4.46, 5.12], %, $tag)',
|
||||
// type: 'cap',
|
||||
// },
|
||||
// ],
|
||||
[
|
||||
'delete extrude',
|
||||
{
|
||||
codeBefore: `sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([3.29, 7.86], %)
|
||||
|> line(end = [2.48, 2.44])
|
||||
|> line(end = [2.66, 1.17])
|
||||
|> line(end = [3.75, 0.46])
|
||||
|> line(end = [4.99, -0.46], tag = $seg01)
|
||||
|> line(end = [-3.86, -2.73])
|
||||
|> line(end = [-17.67, 0.85])
|
||||
|> close()
|
||||
const extrude001 = extrude(sketch001, length = 10)`,
|
||||
codeAfter: `sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([3.29, 7.86], %)
|
||||
|> line(end = [2.48, 2.44])
|
||||
|> line(end = [2.66, 1.17])
|
||||
|> line(end = [3.75, 0.46])
|
||||
|> line(end = [4.99, -0.46], tag = $seg01)
|
||||
|> line(end = [-3.86, -2.73])
|
||||
|> line(end = [-17.67, 0.85])
|
||||
|> close()\n`,
|
||||
lineOfInterest: 'line(end = [2.66, 1.17])',
|
||||
type: 'wall',
|
||||
},
|
||||
],
|
||||
[
|
||||
'delete extrude with sketch on it',
|
||||
{
|
||||
codeBefore: `myVar = 5
|
||||
sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([4.46, 5.12], %, $tag)
|
||||
|> line(end = [0.08, myVar])
|
||||
|> line(end = [13.03, 2.02], tag = $seg01)
|
||||
|> line(end = [3.9, -7.6])
|
||||
|> line(end = [-11.18, -2.15])
|
||||
|> line(end = [5.41, -9.61])
|
||||
|> line(end = [-8.54, -2.51])
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
const extrude001 = extrude(sketch001, length = 5)
|
||||
sketch002 = startSketchOn(extrude001, seg01)
|
||||
|> startProfileAt([-12.55, 2.89], %)
|
||||
|> line(end = [3.02, 1.9])
|
||||
|> line(end = [1.82, -1.49], tag = $seg02)
|
||||
|> angledLine([-86, segLen(seg02)], %)
|
||||
|> line(end = [-3.97, -0.53])
|
||||
|> line(end = [0.3, 0.84])
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()`,
|
||||
codeAfter: `myVar = 5
|
||||
sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([4.46, 5.12], %, $tag)
|
||||
|> line(end = [0.08, myVar])
|
||||
|> line(end = [13.03, 2.02], tag = $seg01)
|
||||
|> line(end = [3.9, -7.6])
|
||||
|> line(end = [-11.18, -2.15])
|
||||
|> line(end = [5.41, -9.61])
|
||||
|> line(end = [-8.54, -2.51])
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
sketch002 = startSketchOn({
|
||||
plane = {
|
||||
origin = { x = 1, y = 2, z = 3 },
|
||||
xAxis = { x = 4, y = 5, z = 6 },
|
||||
yAxis = { x = 7, y = 8, z = 9 },
|
||||
zAxis = { x = 10, y = 11, z = 12 }
|
||||
}
|
||||
})
|
||||
|> startProfileAt([-12.55, 2.89], %)
|
||||
|> line(end = [3.02, 1.9])
|
||||
|> line(end = [1.82, -1.49], tag = $seg02)
|
||||
|> angledLine([-86, segLen(seg02)], %)
|
||||
|> line(end = [-3.97, -0.53])
|
||||
|> line(end = [0.3, 0.84])
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
`,
|
||||
lineOfInterest: 'line(end = [-11.18, -2.15])',
|
||||
type: 'wall',
|
||||
},
|
||||
],
|
||||
[
|
||||
'delete extrude with sketch on it 2',
|
||||
{
|
||||
codeBefore: `myVar = 5
|
||||
sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([4.46, 5.12], %, $tag)
|
||||
|> line(end = [0.08, myVar])
|
||||
|> line(end = [13.03, 2.02], tag = $seg01)
|
||||
|> line(end = [3.9, -7.6])
|
||||
|> line(end = [-11.18, -2.15])
|
||||
|> line(end = [5.41, -9.61])
|
||||
|> line(end = [-8.54, -2.51])
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
const extrude001 = extrude(sketch001, length = 5)
|
||||
sketch002 = startSketchOn(extrude001, seg01)
|
||||
|> startProfileAt([-12.55, 2.89], %)
|
||||
|> line(end = [3.02, 1.9])
|
||||
|> line(end = [1.82, -1.49], tag = $seg02)
|
||||
|> angledLine([-86, segLen(seg02)], %)
|
||||
|> line(end = [-3.97, -0.53])
|
||||
|> line(end = [0.3, 0.84])
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()`,
|
||||
codeAfter: `myVar = 5
|
||||
sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([4.46, 5.12], %, $tag)
|
||||
|> line(end = [0.08, myVar])
|
||||
|> line(end = [13.03, 2.02], tag = $seg01)
|
||||
|> line(end = [3.9, -7.6])
|
||||
|> line(end = [-11.18, -2.15])
|
||||
|> line(end = [5.41, -9.61])
|
||||
|> line(end = [-8.54, -2.51])
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
sketch002 = startSketchOn({
|
||||
plane = {
|
||||
origin = { x = 1, y = 2, z = 3 },
|
||||
xAxis = { x = 4, y = 5, z = 6 },
|
||||
yAxis = { x = 7, y = 8, z = 9 },
|
||||
zAxis = { x = 10, y = 11, z = 12 }
|
||||
}
|
||||
})
|
||||
|> startProfileAt([-12.55, 2.89], %)
|
||||
|> line(end = [3.02, 1.9])
|
||||
|> line(end = [1.82, -1.49], tag = $seg02)
|
||||
|> angledLine([-86, segLen(seg02)], %)
|
||||
|> line(end = [-3.97, -0.53])
|
||||
|> line(end = [0.3, 0.84])
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
`,
|
||||
lineOfInterest: 'startProfileAt([4.46, 5.12], %, $tag)',
|
||||
type: 'cap',
|
||||
},
|
||||
],
|
||||
] as const
|
||||
test.each(cases)(
|
||||
'%s',
|
||||
@ -982,7 +980,6 @@ sketch003 = startSketchOn('XZ')
|
||||
artifact,
|
||||
},
|
||||
execState.variables,
|
||||
execState.artifactGraph,
|
||||
async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
return {
|
||||
|
@ -38,7 +38,6 @@ import {
|
||||
isCallExprWithName,
|
||||
ARG_INDEX_FIELD,
|
||||
LABELED_ARG_FIELD,
|
||||
UNLABELED_ARG,
|
||||
} from './queryAst'
|
||||
import {
|
||||
addTagForSketchOnFace,
|
||||
@ -69,12 +68,12 @@ import {
|
||||
expandWall,
|
||||
getArtifactOfTypes,
|
||||
getArtifactsOfTypes,
|
||||
getFaceCodeRef,
|
||||
getPathsFromArtifact,
|
||||
} from './std/artifactGraph'
|
||||
import { BodyItem } from 'wasm-lib/kcl/bindings/BodyItem'
|
||||
import { findKwArg } from './util'
|
||||
import { deleteEdgeTreatment } from './modifyAst/addEdgeTreatment'
|
||||
import { engineCommandManager } from 'lib/singletons'
|
||||
|
||||
export function startSketchOnDefault(
|
||||
node: Node<Program>,
|
||||
@ -677,11 +676,10 @@ export function addOffsetPlane({
|
||||
|
||||
const newPlane = createVariableDeclaration(
|
||||
newPlaneName,
|
||||
createCallExpressionStdLibKw(
|
||||
'offsetPlane',
|
||||
createCallExpressionStdLib('offsetPlane', [
|
||||
createLiteral(defaultPlane.toUpperCase()),
|
||||
[createLabeledArg('offset', offset)]
|
||||
)
|
||||
offset,
|
||||
])
|
||||
)
|
||||
|
||||
const insertAt =
|
||||
@ -699,7 +697,8 @@ export function addOffsetPlane({
|
||||
[insertAt, 'index'],
|
||||
['declaration', 'VariableDeclaration'],
|
||||
['init', 'VariableDeclarator'],
|
||||
['unlabeled', UNLABELED_ARG],
|
||||
['arguments', 'CallExpression'],
|
||||
[0, 'index'],
|
||||
]
|
||||
return {
|
||||
modifiedAst,
|
||||
@ -984,7 +983,6 @@ export function createCallExpressionStdLibKw(
|
||||
end: 0,
|
||||
moduleId: 0,
|
||||
outerAttrs: [],
|
||||
nonCodeMeta: nonCodeMetaEmpty(),
|
||||
callee: {
|
||||
type: 'Identifier',
|
||||
start: 0,
|
||||
@ -1392,7 +1390,6 @@ export async function deleteFromSelection(
|
||||
ast: Node<Program>,
|
||||
selection: Selection,
|
||||
variables: VariableMap,
|
||||
artifactGraph: ArtifactGraph,
|
||||
getFaceDetails: (id: string) => Promise<Models['FaceIsPlanar_type']> = () =>
|
||||
({} as any)
|
||||
): Promise<Node<Program> | Error> {
|
||||
@ -1405,12 +1402,12 @@ export async function deleteFromSelection(
|
||||
) {
|
||||
const plane =
|
||||
selection.artifact.type === 'plane'
|
||||
? expandPlane(selection.artifact, artifactGraph)
|
||||
? expandPlane(selection.artifact, engineCommandManager.artifactGraph)
|
||||
: selection.artifact.type === 'wall'
|
||||
? expandWall(selection.artifact, artifactGraph)
|
||||
: expandCap(selection.artifact, artifactGraph)
|
||||
? expandWall(selection.artifact, engineCommandManager.artifactGraph)
|
||||
: expandCap(selection.artifact, engineCommandManager.artifactGraph)
|
||||
for (const path of plane.paths.sort(
|
||||
(a, b) => b.codeRef.range?.[0] - a.codeRef.range?.[0]
|
||||
(a, b) => b.codeRef.range[0] - a.codeRef.range[0]
|
||||
)) {
|
||||
const varDec = getNodeFromPath<VariableDeclarator>(
|
||||
ast,
|
||||
@ -1427,15 +1424,6 @@ export async function deleteFromSelection(
|
||||
selection.artifact.type === 'cap' ||
|
||||
selection.artifact.type === 'wall'
|
||||
) {
|
||||
// Delete the sketch node, which would not work if
|
||||
// we continued down the traditional code path below.
|
||||
// faceCodeRef's pathToNode is empty for some reason
|
||||
// so using source range instead
|
||||
const codeRef = getFaceCodeRef(selection.artifact)
|
||||
if (!codeRef) return new Error('Could not find face code ref')
|
||||
const sketchVarDec = getNodePathFromSourceRange(astClone, codeRef.range)
|
||||
const sketchBodyIndex = Number(sketchVarDec[1][0])
|
||||
astClone.body.splice(sketchBodyIndex, 1)
|
||||
return astClone
|
||||
}
|
||||
}
|
||||
@ -1535,20 +1523,20 @@ export async function deleteFromSelection(
|
||||
selection.artifact.surfaceId
|
||||
? getArtifactOfTypes(
|
||||
{ key: selection.artifact.surfaceId, types: ['wall'] },
|
||||
artifactGraph
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
: null
|
||||
if (err(wallArtifact)) return
|
||||
if (wallArtifact) {
|
||||
const sweep = getArtifactOfTypes(
|
||||
{ key: wallArtifact.sweepId, types: ['sweep'] },
|
||||
artifactGraph
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(sweep)) return
|
||||
const wallsWithDependencies = Array.from(
|
||||
getArtifactsOfTypes(
|
||||
{ keys: sweep.surfaceIds, types: ['wall', 'cap'] },
|
||||
artifactGraph
|
||||
engineCommandManager.artifactGraph
|
||||
).values()
|
||||
).filter((wall) => wall?.pathIds?.length)
|
||||
const wallIds = wallsWithDependencies.map((wall) => wall.id)
|
||||
@ -1726,7 +1714,7 @@ export async function deleteFromSelection(
|
||||
return new Error('Selection not recognised, could not delete')
|
||||
}
|
||||
|
||||
export const nonCodeMetaEmpty = () => {
|
||||
const nonCodeMetaEmpty = () => {
|
||||
return { nonCodeNodes: {}, startNodes: [], start: 0, end: 0 }
|
||||
}
|
||||
|
||||
|
@ -582,7 +582,7 @@ sketch002 = startSketchOn(extrude001, $seg01)
|
||||
it('finds sketch001 and sketch002 pipes to be lofted', async () => {
|
||||
const exampleCode = `sketch001 = startSketchOn('XZ')
|
||||
|> circle({ center = [0, 0], radius = 1 }, %)
|
||||
plane001 = offsetPlane('XZ', offset = 2)
|
||||
plane001 = offsetPlane('XZ', 2)
|
||||
sketch002 = startSketchOn(plane001)
|
||||
|> circle({ center = [0, 0], radius = 3 }, %)
|
||||
`
|
||||
|
@ -45,7 +45,6 @@ import { ImportStatement } from 'wasm-lib/kcl/bindings/ImportStatement'
|
||||
import { KclSettingsAnnotation } from 'lib/settings/settingsTypes'
|
||||
|
||||
export const LABELED_ARG_FIELD = 'LabeledArg -> Arg'
|
||||
export const UNLABELED_ARG = 'unlabeled first arg'
|
||||
export const ARG_INDEX_FIELD = 'arg index'
|
||||
|
||||
/**
|
||||
|
@ -532,32 +532,6 @@ function getPlaneFromSolid2D(
|
||||
if (err(path)) return path
|
||||
return getPlaneFromPath(path, graph)
|
||||
}
|
||||
function getPlaneFromCap(
|
||||
cap: CapArtifact,
|
||||
graph: ArtifactGraph
|
||||
): PlaneArtifact | WallArtifact | CapArtifact | Error {
|
||||
const sweep = getArtifactOfTypes(
|
||||
{ key: cap.sweepId, types: ['sweep'] },
|
||||
graph
|
||||
)
|
||||
if (err(sweep)) return sweep
|
||||
const path = getArtifactOfTypes({ key: sweep.pathId, types: ['path'] }, graph)
|
||||
if (err(path)) return path
|
||||
return getPlaneFromPath(path, graph)
|
||||
}
|
||||
function getPlaneFromWall(
|
||||
wall: WallArtifact,
|
||||
graph: ArtifactGraph
|
||||
): PlaneArtifact | WallArtifact | CapArtifact | Error {
|
||||
const sweep = getArtifactOfTypes(
|
||||
{ key: wall.sweepId, types: ['sweep'] },
|
||||
graph
|
||||
)
|
||||
if (err(sweep)) return sweep
|
||||
const path = getArtifactOfTypes({ key: sweep.pathId, types: ['path'] }, graph)
|
||||
if (err(path)) return path
|
||||
return getPlaneFromPath(path, graph)
|
||||
}
|
||||
function getPlaneFromSweepEdge(edge: SweepEdge, graph: ArtifactGraph) {
|
||||
const sweep = getArtifactOfTypes(
|
||||
{ key: edge.sweepId, types: ['sweep'] },
|
||||
@ -578,15 +552,7 @@ export function getPlaneFromArtifact(
|
||||
if (artifact.type === 'path') return getPlaneFromPath(artifact, graph)
|
||||
if (artifact.type === 'segment') return getPlaneFromSegment(artifact, graph)
|
||||
if (artifact.type === 'solid2d') return getPlaneFromSolid2D(artifact, graph)
|
||||
if (
|
||||
// if the user selects a face with sketch on it (pathIds.length), they probably wanted to edit that sketch,
|
||||
// not the sketch for the underlying sweep sketch
|
||||
(artifact.type === 'wall' || artifact.type === 'cap') &&
|
||||
artifact?.pathIds?.length
|
||||
)
|
||||
return artifact
|
||||
if (artifact.type === 'cap') return getPlaneFromCap(artifact, graph)
|
||||
if (artifact.type === 'wall') return getPlaneFromWall(artifact, graph)
|
||||
if (artifact.type === 'wall' || artifact.type === 'cap') return artifact
|
||||
if (artifact.type === 'sweepEdge')
|
||||
return getPlaneFromSweepEdge(artifact, graph)
|
||||
return new Error(`Artifact type ${artifact.type} does not have a plane`)
|
||||
|
@ -30,7 +30,6 @@ import { toolTips, ToolTip } from 'lang/langHelpers'
|
||||
import {
|
||||
createPipeExpression,
|
||||
mutateKwArg,
|
||||
nonCodeMetaEmpty,
|
||||
splitPathAtPipeExpression,
|
||||
} from '../modifyAst'
|
||||
|
||||
@ -2830,7 +2829,6 @@ function addTagKw(): addTagFn {
|
||||
unlabeled: callExpr.node.arguments.length
|
||||
? callExpr.node.arguments[0]
|
||||
: null,
|
||||
nonCodeMeta: nonCodeMetaEmpty(),
|
||||
arguments: [],
|
||||
}
|
||||
const tagArg = findKwArg(ARG_TAG, primaryCallExp)
|
||||
|
@ -14,7 +14,6 @@ describe('KCL expression calculations', () => {
|
||||
variables['x'] = {
|
||||
type: 'Number',
|
||||
value: 2,
|
||||
ty: { type: 'Any' },
|
||||
__meta: [],
|
||||
}
|
||||
const actual = await getCalculatedKclExpressionValue('1 + x', variables)
|
||||
@ -33,7 +32,6 @@ describe('KCL expression calculations', () => {
|
||||
variables['y'] = {
|
||||
type: 'Number',
|
||||
value: 2,
|
||||
ty: { type: 'Any' },
|
||||
__meta: [],
|
||||
}
|
||||
const actual = await getCalculatedKclExpressionValue('1 + x', variables)
|
||||
@ -46,7 +44,6 @@ describe('KCL expression calculations', () => {
|
||||
variables['x'] = {
|
||||
type: 'Number',
|
||||
value: 2,
|
||||
ty: { type: 'Any' },
|
||||
__meta: [],
|
||||
}
|
||||
const actual = await getCalculatedKclExpressionValue(
|
||||
|
@ -126,7 +126,8 @@ const prepareToEditOffsetPlane: PrepareToEditCallback = async ({
|
||||
if (
|
||||
operation.type !== 'StdLibCall' ||
|
||||
!operation.labeledArgs ||
|
||||
!operation.unlabeledArg ||
|
||||
!('std_plane' in operation.labeledArgs) ||
|
||||
!operation.labeledArgs.std_plane ||
|
||||
!('offset' in operation.labeledArgs) ||
|
||||
!operation.labeledArgs.offset
|
||||
) {
|
||||
@ -134,9 +135,11 @@ const prepareToEditOffsetPlane: PrepareToEditCallback = async ({
|
||||
}
|
||||
// TODO: Implement conversion to arbitrary plane selection
|
||||
// once the Offset Plane command supports it.
|
||||
const stdPlane = operation.unlabeledArg
|
||||
const planeName = codeManager.code
|
||||
.slice(stdPlane.sourceRange[0], stdPlane.sourceRange[1])
|
||||
.slice(
|
||||
operation.labeledArgs.std_plane.sourceRange[0],
|
||||
operation.labeledArgs.std_plane.sourceRange[1]
|
||||
)
|
||||
.replaceAll(`'`, ``)
|
||||
|
||||
if (!isDefaultPlaneStr(planeName)) {
|
||||
|
@ -375,7 +375,14 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
||||
},
|
||||
icon: 'line',
|
||||
status: 'available',
|
||||
disabled: (state) => state.matches('Sketch no face'),
|
||||
disabled: (state) =>
|
||||
state.matches('Sketch no face') ||
|
||||
state.matches({
|
||||
Sketch: { 'Rectangle tool': 'Awaiting second corner' },
|
||||
}) ||
|
||||
state.matches({
|
||||
Sketch: { 'Circle tool': 'Awaiting Radius' },
|
||||
}),
|
||||
title: 'Line',
|
||||
hotkey: (state) =>
|
||||
state.matches({ Sketch: 'Line tool' }) ? ['Esc', 'L'] : 'L',
|
||||
|
File diff suppressed because one or more lines are too long
11
src/main.ts
11
src/main.ts
@ -53,7 +53,6 @@ process.env.VITE_KC_CONNECTION_TIMEOUT_MS ??=
|
||||
console.log('process.env', process.env)
|
||||
|
||||
/// Register our application to handle all "zoo-studio:" protocols.
|
||||
const singleInstanceLock = app.requestSingleInstanceLock()
|
||||
if (process.defaultApp) {
|
||||
if (process.argv.length >= 2) {
|
||||
app.setAsDefaultProtocolClient(ZOO_STUDIO_PROTOCOL, process.execPath, [
|
||||
@ -66,13 +65,7 @@ if (process.defaultApp) {
|
||||
|
||||
// Global app listeners
|
||||
// Must be done before ready event.
|
||||
// Checking against this lock is needed for Windows and Linux, see
|
||||
// https://www.electronjs.org/docs/latest/tutorial/launch-app-from-url-in-another-app#windows-and-linux-code
|
||||
if (!singleInstanceLock && !process.env.IS_PLAYWRIGHT) {
|
||||
app.quit()
|
||||
} else {
|
||||
registerStartupListeners()
|
||||
}
|
||||
registerStartupListeners()
|
||||
|
||||
const createWindow = (pathToOpen?: string, reuse?: boolean): BrowserWindow => {
|
||||
let newWindow
|
||||
@ -82,7 +75,7 @@ const createWindow = (pathToOpen?: string, reuse?: boolean): BrowserWindow => {
|
||||
}
|
||||
if (!newWindow) {
|
||||
newWindow = new BrowserWindow({
|
||||
autoHideMenuBar: false,
|
||||
autoHideMenuBar: true,
|
||||
show: false,
|
||||
width: 1800,
|
||||
height: 1200,
|
||||
|
6
src/wasm-lib/Cargo.lock
generated
6
src/wasm-lib/Cargo.lock
generated
@ -730,7 +730,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "derive-docs"
|
||||
version = "0.1.38"
|
||||
version = "0.1.36"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"anyhow",
|
||||
@ -1712,7 +1712,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-lib"
|
||||
version = "0.2.38"
|
||||
version = "0.2.35"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"approx 0.5.1",
|
||||
@ -1779,7 +1779,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-test-server"
|
||||
version = "0.1.38"
|
||||
version = "0.1.21"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"hyper 0.14.32",
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "derive-docs"
|
||||
description = "A tool for generating documentation from Rust derive macros"
|
||||
version = "0.1.38"
|
||||
version = "0.1.36"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/KittyCAD/modeling-app"
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "kcl-test-server"
|
||||
description = "A test server for KCL"
|
||||
version = "0.1.38"
|
||||
version = "0.1.21"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
|
@ -1,4 +1,7 @@
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use indexmap::IndexMap;
|
||||
@ -21,20 +24,22 @@ const NEED_PLANES: bool = true;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EngineConnection {
|
||||
batch: Arc<RwLock<Vec<(WebSocketRequest, kcl_lib::SourceRange)>>>,
|
||||
batch_end: Arc<RwLock<IndexMap<uuid::Uuid, (WebSocketRequest, kcl_lib::SourceRange)>>>,
|
||||
core_test: Arc<RwLock<String>>,
|
||||
batch: Arc<Mutex<Vec<(WebSocketRequest, kcl_lib::SourceRange)>>>,
|
||||
batch_end: Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, kcl_lib::SourceRange)>>>,
|
||||
core_test: Arc<Mutex<String>>,
|
||||
default_planes: Arc<RwLock<Option<DefaultPlanes>>>,
|
||||
execution_kind: Arc<RwLock<ExecutionKind>>,
|
||||
execution_kind: Arc<Mutex<ExecutionKind>>,
|
||||
}
|
||||
|
||||
impl EngineConnection {
|
||||
pub async fn new(result: Arc<RwLock<String>>) -> Result<EngineConnection> {
|
||||
result.write().await.push_str(CPP_PREFIX);
|
||||
pub async fn new(result: Arc<Mutex<String>>) -> Result<EngineConnection> {
|
||||
if let Ok(mut code) = result.lock() {
|
||||
code.push_str(CPP_PREFIX);
|
||||
}
|
||||
|
||||
Ok(EngineConnection {
|
||||
batch: Arc::new(RwLock::new(Vec::new())),
|
||||
batch_end: Arc::new(RwLock::new(IndexMap::new())),
|
||||
batch: Arc::new(Mutex::new(Vec::new())),
|
||||
batch_end: Arc::new(Mutex::new(IndexMap::new())),
|
||||
core_test: result,
|
||||
default_planes: Default::default(),
|
||||
execution_kind: Default::default(),
|
||||
@ -357,29 +362,29 @@ fn codegen_cpp_repl_uuid_setters(reps_id: &str, entity_ids: &[uuid::Uuid]) -> St
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl kcl_lib::EngineManager for EngineConnection {
|
||||
fn batch(&self) -> Arc<RwLock<Vec<(WebSocketRequest, kcl_lib::SourceRange)>>> {
|
||||
fn batch(&self) -> Arc<Mutex<Vec<(WebSocketRequest, kcl_lib::SourceRange)>>> {
|
||||
self.batch.clone()
|
||||
}
|
||||
|
||||
fn batch_end(&self) -> Arc<RwLock<IndexMap<uuid::Uuid, (WebSocketRequest, kcl_lib::SourceRange)>>> {
|
||||
fn batch_end(&self) -> Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, kcl_lib::SourceRange)>>> {
|
||||
self.batch_end.clone()
|
||||
}
|
||||
|
||||
fn responses(&self) -> Arc<RwLock<IndexMap<Uuid, WebSocketResponse>>> {
|
||||
Arc::new(RwLock::new(IndexMap::new()))
|
||||
fn responses(&self) -> IndexMap<Uuid, WebSocketResponse> {
|
||||
IndexMap::new()
|
||||
}
|
||||
|
||||
fn artifact_commands(&self) -> Arc<RwLock<Vec<ArtifactCommand>>> {
|
||||
Arc::new(RwLock::new(Vec::new()))
|
||||
fn take_artifact_commands(&self) -> Vec<ArtifactCommand> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
async fn execution_kind(&self) -> ExecutionKind {
|
||||
let guard = self.execution_kind.read().await;
|
||||
fn execution_kind(&self) -> ExecutionKind {
|
||||
let guard = self.execution_kind.lock().unwrap();
|
||||
*guard
|
||||
}
|
||||
|
||||
async fn replace_execution_kind(&self, execution_kind: ExecutionKind) -> ExecutionKind {
|
||||
let mut guard = self.execution_kind.write().await;
|
||||
fn replace_execution_kind(&self, execution_kind: ExecutionKind) -> ExecutionKind {
|
||||
let mut guard = self.execution_kind.lock().unwrap();
|
||||
let original = *guard;
|
||||
*guard = execution_kind;
|
||||
original
|
||||
@ -430,18 +435,24 @@ impl kcl_lib::EngineManager for EngineConnection {
|
||||
}) => {
|
||||
let mut responses = HashMap::new();
|
||||
for request in requests {
|
||||
let (new_code, this_response) = self.handle_command(&request.cmd_id, &request.cmd);
|
||||
let (new_code, this_response);
|
||||
|
||||
if !new_code.is_empty() {
|
||||
let new_code = new_code
|
||||
.trim()
|
||||
.split(' ')
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
+ "\n";
|
||||
//println!("{new_code}");
|
||||
self.core_test.write().await.push_str(&new_code);
|
||||
if let Ok(mut test_code) = self.core_test.lock() {
|
||||
(new_code, this_response) = self.handle_command(&request.cmd_id, &request.cmd);
|
||||
|
||||
if !new_code.is_empty() {
|
||||
let new_code = new_code
|
||||
.trim()
|
||||
.split(' ')
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
+ "\n";
|
||||
//println!("{new_code}");
|
||||
test_code.push_str(&new_code);
|
||||
}
|
||||
} else {
|
||||
this_response = OkModelingCmdResponse::Empty {};
|
||||
}
|
||||
|
||||
responses.insert(
|
||||
@ -459,18 +470,24 @@ impl kcl_lib::EngineManager for EngineConnection {
|
||||
}
|
||||
WebSocketRequest::ModelingCmdReq(ModelingCmdReq { cmd, cmd_id }) => {
|
||||
//also handle unbatched requests inline
|
||||
let (new_code, this_response) = self.handle_command(&cmd_id, &cmd);
|
||||
let (new_code, this_response);
|
||||
|
||||
if !new_code.is_empty() {
|
||||
let new_code = new_code
|
||||
.trim()
|
||||
.split(' ')
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
+ "\n";
|
||||
//println!("{new_code}");
|
||||
self.core_test.write().await.push_str(&new_code);
|
||||
if let Ok(mut test_code) = self.core_test.lock() {
|
||||
(new_code, this_response) = self.handle_command(&cmd_id, &cmd);
|
||||
|
||||
if !new_code.is_empty() {
|
||||
let new_code = new_code
|
||||
.trim()
|
||||
.split(' ')
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
+ "\n";
|
||||
//println!("{new_code}");
|
||||
test_code.push_str(&new_code);
|
||||
}
|
||||
} else {
|
||||
this_response = OkModelingCmdResponse::Empty {};
|
||||
}
|
||||
|
||||
Ok(WebSocketResponse::Success(kcmc::websocket::SuccessWebSocketResponse {
|
||||
|
@ -1,8 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use anyhow::Result;
|
||||
use kcl_lib::{ExecState, ExecutorContext};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod conn_mock_core;
|
||||
@ -11,7 +10,7 @@ mod conn_mock_core;
|
||||
pub async fn kcl_to_engine_core(code: &str) -> Result<String> {
|
||||
let program = kcl_lib::Program::parse_no_errs(code)?;
|
||||
|
||||
let result = Arc::new(RwLock::new("".into()));
|
||||
let result = Arc::new(Mutex::new("".into()));
|
||||
let ref_result = Arc::clone(&result);
|
||||
|
||||
let ctx = ExecutorContext::new_forwarded_mock(Arc::new(Box::new(
|
||||
@ -19,6 +18,6 @@ pub async fn kcl_to_engine_core(code: &str) -> Result<String> {
|
||||
)));
|
||||
ctx.run(&program, &mut ExecState::new(&ctx.settings)).await?;
|
||||
|
||||
let result = result.read().await.clone();
|
||||
let result = result.lock().expect("mutex lock").clone();
|
||||
Ok(result)
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "kcl-lib"
|
||||
description = "KittyCAD Language implementation and tools"
|
||||
version = "0.2.38"
|
||||
version = "0.2.35"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/KittyCAD/modeling-app"
|
||||
|
@ -1,9 +1,13 @@
|
||||
//! Functions for setting up our WebSocket and WebRTC connections for communications with the
|
||||
//! engine.
|
||||
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use dashmap::DashMap;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use indexmap::IndexMap;
|
||||
use kcmc::{
|
||||
@ -13,7 +17,9 @@ use kcmc::{
|
||||
},
|
||||
ModelingCmd,
|
||||
};
|
||||
use kittycad_modeling_cmds::{self as kcmc};
|
||||
use kittycad_modeling_cmds::{
|
||||
self as kcmc, id::ModelingCmdId, ok_response::OkModelingCmdResponse, websocket::ModelingBatch,
|
||||
};
|
||||
use tokio::sync::{mpsc, oneshot, RwLock};
|
||||
use tokio_tungstenite::tungstenite::Message as WsMsg;
|
||||
use uuid::Uuid;
|
||||
@ -37,21 +43,21 @@ type WebSocketTcpWrite = futures::stream::SplitSink<tokio_tungstenite::WebSocket
|
||||
pub struct EngineConnection {
|
||||
engine_req_tx: mpsc::Sender<ToEngineReq>,
|
||||
shutdown_tx: mpsc::Sender<()>,
|
||||
responses: Arc<RwLock<IndexMap<uuid::Uuid, WebSocketResponse>>>,
|
||||
pending_errors: Arc<RwLock<Vec<String>>>,
|
||||
responses: Arc<DashMap<uuid::Uuid, WebSocketResponse>>,
|
||||
pending_errors: Arc<Mutex<Vec<String>>>,
|
||||
#[allow(dead_code)]
|
||||
tcp_read_handle: Arc<TcpReadHandle>,
|
||||
socket_health: Arc<RwLock<SocketHealth>>,
|
||||
batch: Arc<RwLock<Vec<(WebSocketRequest, SourceRange)>>>,
|
||||
batch_end: Arc<RwLock<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>>,
|
||||
artifact_commands: Arc<RwLock<Vec<ArtifactCommand>>>,
|
||||
socket_health: Arc<Mutex<SocketHealth>>,
|
||||
batch: Arc<Mutex<Vec<(WebSocketRequest, SourceRange)>>>,
|
||||
batch_end: Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>>,
|
||||
artifact_commands: Arc<Mutex<Vec<ArtifactCommand>>>,
|
||||
|
||||
/// The default planes for the scene.
|
||||
default_planes: Arc<RwLock<Option<DefaultPlanes>>>,
|
||||
/// If the server sends session data, it'll be copied to here.
|
||||
session_data: Arc<RwLock<Option<ModelingSessionData>>>,
|
||||
session_data: Arc<Mutex<Option<ModelingSessionData>>>,
|
||||
|
||||
execution_kind: Arc<RwLock<ExecutionKind>>,
|
||||
execution_kind: Arc<Mutex<ExecutionKind>>,
|
||||
}
|
||||
|
||||
pub struct TcpRead {
|
||||
@ -224,12 +230,12 @@ impl EngineConnection {
|
||||
|
||||
let mut tcp_read = TcpRead { stream: tcp_read };
|
||||
|
||||
let session_data: Arc<RwLock<Option<ModelingSessionData>>> = Arc::new(RwLock::new(None));
|
||||
let session_data: Arc<Mutex<Option<ModelingSessionData>>> = Arc::new(Mutex::new(None));
|
||||
let session_data2 = session_data.clone();
|
||||
let responses: Arc<RwLock<IndexMap<uuid::Uuid, WebSocketResponse>>> = Arc::new(RwLock::new(IndexMap::new()));
|
||||
let responses: Arc<DashMap<uuid::Uuid, WebSocketResponse>> = Arc::new(DashMap::new());
|
||||
let responses_clone = responses.clone();
|
||||
let socket_health = Arc::new(RwLock::new(SocketHealth::Active));
|
||||
let pending_errors = Arc::new(RwLock::new(Vec::new()));
|
||||
let socket_health = Arc::new(Mutex::new(SocketHealth::Active));
|
||||
let pending_errors = Arc::new(Mutex::new(Vec::new()));
|
||||
let pending_errors_clone = pending_errors.clone();
|
||||
|
||||
let socket_health_tcp_read = socket_health.clone();
|
||||
@ -254,7 +260,7 @@ impl EngineConnection {
|
||||
let id: uuid::Uuid = (*resp_id).into();
|
||||
match batch_response {
|
||||
BatchResponse::Success { response } => {
|
||||
responses_clone.write().await.insert(
|
||||
responses_clone.insert(
|
||||
id,
|
||||
WebSocketResponse::Success(SuccessWebSocketResponse {
|
||||
success: true,
|
||||
@ -266,7 +272,7 @@ impl EngineConnection {
|
||||
);
|
||||
}
|
||||
BatchResponse::Failure { errors } => {
|
||||
responses_clone.write().await.insert(
|
||||
responses_clone.insert(
|
||||
id,
|
||||
WebSocketResponse::Failure(FailureWebSocketResponse {
|
||||
success: false,
|
||||
@ -282,7 +288,7 @@ impl EngineConnection {
|
||||
resp: OkWebSocketResponseData::ModelingSessionData { session },
|
||||
..
|
||||
}) => {
|
||||
let mut sd = session_data2.write().await;
|
||||
let mut sd = session_data2.lock().unwrap();
|
||||
sd.replace(session.clone());
|
||||
}
|
||||
WebSocketResponse::Failure(FailureWebSocketResponse {
|
||||
@ -291,7 +297,7 @@ impl EngineConnection {
|
||||
errors,
|
||||
}) => {
|
||||
if let Some(id) = request_id {
|
||||
responses_clone.write().await.insert(
|
||||
responses_clone.insert(
|
||||
*id,
|
||||
WebSocketResponse::Failure(FailureWebSocketResponse {
|
||||
success: false,
|
||||
@ -301,20 +307,19 @@ impl EngineConnection {
|
||||
);
|
||||
} else {
|
||||
// Add it to our pending errors.
|
||||
let mut pe = pending_errors_clone.write().await;
|
||||
let mut pe = pending_errors_clone.lock().unwrap();
|
||||
for error in errors {
|
||||
if !pe.contains(&error.message) {
|
||||
pe.push(error.message.clone());
|
||||
}
|
||||
}
|
||||
drop(pe);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if let Some(id) = id {
|
||||
responses_clone.write().await.insert(id, ws_resp.clone());
|
||||
responses_clone.insert(id, ws_resp.clone());
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
@ -322,7 +327,7 @@ impl EngineConnection {
|
||||
WebSocketReadError::Read(e) => crate::logln!("could not read from WS: {:?}", e),
|
||||
WebSocketReadError::Deser(e) => crate::logln!("could not deserialize msg from WS: {:?}", e),
|
||||
}
|
||||
*socket_health_tcp_read.write().await = SocketHealth::Inactive;
|
||||
*socket_health_tcp_read.lock().unwrap() = SocketHealth::Inactive;
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
@ -338,41 +343,70 @@ impl EngineConnection {
|
||||
responses,
|
||||
pending_errors,
|
||||
socket_health,
|
||||
batch: Arc::new(RwLock::new(Vec::new())),
|
||||
batch_end: Arc::new(RwLock::new(IndexMap::new())),
|
||||
artifact_commands: Arc::new(RwLock::new(Vec::new())),
|
||||
batch: Arc::new(Mutex::new(Vec::new())),
|
||||
batch_end: Arc::new(Mutex::new(IndexMap::new())),
|
||||
artifact_commands: Arc::new(Mutex::new(Vec::new())),
|
||||
default_planes: Default::default(),
|
||||
session_data,
|
||||
execution_kind: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_command(
|
||||
&self,
|
||||
cmd: &ModelingCmd,
|
||||
cmd_id: ModelingCmdId,
|
||||
id_to_source_range: &HashMap<Uuid, SourceRange>,
|
||||
) -> Result<(), KclError> {
|
||||
let cmd_id = *cmd_id.as_ref();
|
||||
let range = id_to_source_range
|
||||
.get(&cmd_id)
|
||||
.copied()
|
||||
.ok_or_else(|| KclError::internal(format!("Failed to get source range for command ID: {:?}", cmd_id)))?;
|
||||
|
||||
// Add artifact command.
|
||||
let mut artifact_commands = self.artifact_commands.lock().unwrap();
|
||||
artifact_commands.push(ArtifactCommand {
|
||||
cmd_id,
|
||||
range,
|
||||
command: cmd.clone(),
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl EngineManager for EngineConnection {
|
||||
fn batch(&self) -> Arc<RwLock<Vec<(WebSocketRequest, SourceRange)>>> {
|
||||
fn batch(&self) -> Arc<Mutex<Vec<(WebSocketRequest, SourceRange)>>> {
|
||||
self.batch.clone()
|
||||
}
|
||||
|
||||
fn batch_end(&self) -> Arc<RwLock<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>> {
|
||||
fn batch_end(&self) -> Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>> {
|
||||
self.batch_end.clone()
|
||||
}
|
||||
|
||||
fn responses(&self) -> Arc<RwLock<IndexMap<Uuid, WebSocketResponse>>> {
|
||||
self.responses.clone()
|
||||
fn responses(&self) -> IndexMap<Uuid, WebSocketResponse> {
|
||||
self.responses
|
||||
.iter()
|
||||
.map(|entry| {
|
||||
let (k, v) = entry.pair();
|
||||
(*k, v.clone())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn artifact_commands(&self) -> Arc<RwLock<Vec<ArtifactCommand>>> {
|
||||
self.artifact_commands.clone()
|
||||
fn take_artifact_commands(&self) -> Vec<ArtifactCommand> {
|
||||
let mut artifact_commands = self.artifact_commands.lock().unwrap();
|
||||
std::mem::take(&mut *artifact_commands)
|
||||
}
|
||||
|
||||
async fn execution_kind(&self) -> ExecutionKind {
|
||||
let guard = self.execution_kind.read().await;
|
||||
fn execution_kind(&self) -> ExecutionKind {
|
||||
let guard = self.execution_kind.lock().unwrap();
|
||||
*guard
|
||||
}
|
||||
|
||||
async fn replace_execution_kind(&self, execution_kind: ExecutionKind) -> ExecutionKind {
|
||||
let mut guard = self.execution_kind.write().await;
|
||||
fn replace_execution_kind(&self, execution_kind: ExecutionKind) -> ExecutionKind {
|
||||
let mut guard = self.execution_kind.lock().unwrap();
|
||||
let original = *guard;
|
||||
*guard = execution_kind;
|
||||
original
|
||||
@ -413,8 +447,49 @@ impl EngineManager for EngineConnection {
|
||||
id: uuid::Uuid,
|
||||
source_range: SourceRange,
|
||||
cmd: WebSocketRequest,
|
||||
_id_to_source_range: HashMap<Uuid, SourceRange>,
|
||||
id_to_source_range: HashMap<Uuid, SourceRange>,
|
||||
) -> Result<WebSocketResponse, KclError> {
|
||||
match &cmd {
|
||||
WebSocketRequest::ModelingCmdBatchReq(ModelingBatch { requests, .. }) => {
|
||||
for request in requests {
|
||||
self.handle_command(&request.cmd, request.cmd_id, &id_to_source_range)?;
|
||||
}
|
||||
}
|
||||
WebSocketRequest::ModelingCmdReq(request) => {
|
||||
self.handle_command(&request.cmd, request.cmd_id, &id_to_source_range)?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// In isolated mode, we don't send the command to the engine.
|
||||
if self.execution_kind().is_isolated() {
|
||||
return match &cmd {
|
||||
WebSocketRequest::ModelingCmdBatchReq(ModelingBatch { requests, .. }) => {
|
||||
let mut responses = HashMap::with_capacity(requests.len());
|
||||
for request in requests {
|
||||
responses.insert(
|
||||
request.cmd_id,
|
||||
BatchResponse::Success {
|
||||
response: OkModelingCmdResponse::Empty {},
|
||||
},
|
||||
);
|
||||
}
|
||||
Ok(WebSocketResponse::Success(SuccessWebSocketResponse {
|
||||
request_id: Some(id),
|
||||
resp: OkWebSocketResponseData::ModelingBatch { responses },
|
||||
success: true,
|
||||
}))
|
||||
}
|
||||
_ => Ok(WebSocketResponse::Success(SuccessWebSocketResponse {
|
||||
request_id: Some(id),
|
||||
resp: OkWebSocketResponseData::Modeling {
|
||||
modeling_response: OkModelingCmdResponse::Empty {},
|
||||
},
|
||||
success: true,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
// Send the request to the engine, via the actor.
|
||||
@ -449,24 +524,25 @@ impl EngineManager for EngineConnection {
|
||||
// Wait for the response.
|
||||
let current_time = std::time::Instant::now();
|
||||
while current_time.elapsed().as_secs() < 60 {
|
||||
let guard = self.socket_health.read().await;
|
||||
if *guard == SocketHealth::Inactive {
|
||||
// Check if we have any pending errors.
|
||||
let pe = self.pending_errors.read().await;
|
||||
if !pe.is_empty() {
|
||||
return Err(KclError::Engine(KclErrorDetails {
|
||||
message: pe.join(", ").to_string(),
|
||||
source_ranges: vec![source_range],
|
||||
}));
|
||||
} else {
|
||||
return Err(KclError::Engine(KclErrorDetails {
|
||||
message: "Modeling command failed: websocket closed early".to_string(),
|
||||
source_ranges: vec![source_range],
|
||||
}));
|
||||
if let Ok(guard) = self.socket_health.lock() {
|
||||
if *guard == SocketHealth::Inactive {
|
||||
// Check if we have any pending errors.
|
||||
let pe = self.pending_errors.lock().unwrap();
|
||||
if !pe.is_empty() {
|
||||
return Err(KclError::Engine(KclErrorDetails {
|
||||
message: pe.join(", ").to_string(),
|
||||
source_ranges: vec![source_range],
|
||||
}));
|
||||
} else {
|
||||
return Err(KclError::Engine(KclErrorDetails {
|
||||
message: "Modeling command failed: websocket closed early".to_string(),
|
||||
source_ranges: vec![source_range],
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
// We pop off the responses to cleanup our mappings.
|
||||
if let Some(resp) = self.responses.write().await.shift_remove(&id) {
|
||||
if let Some((_, resp)) = self.responses.remove(&id) {
|
||||
return Ok(resp);
|
||||
}
|
||||
}
|
||||
@ -477,16 +553,17 @@ impl EngineManager for EngineConnection {
|
||||
}))
|
||||
}
|
||||
|
||||
async fn get_session_data(&self) -> Option<ModelingSessionData> {
|
||||
self.session_data.read().await.clone()
|
||||
fn get_session_data(&self) -> Option<ModelingSessionData> {
|
||||
self.session_data.lock().unwrap().clone()
|
||||
}
|
||||
|
||||
async fn close(&self) {
|
||||
let _ = self.shutdown_tx.send(()).await;
|
||||
loop {
|
||||
let guard = self.socket_health.read().await;
|
||||
if *guard == SocketHealth::Inactive {
|
||||
return;
|
||||
if let Ok(guard) = self.socket_health.lock() {
|
||||
if *guard == SocketHealth::Inactive {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,10 @@
|
||||
//! Functions for setting up our WebSocket and WebRTC connections for communications with the
|
||||
//! engine.
|
||||
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use indexmap::IndexMap;
|
||||
@ -12,8 +15,7 @@ use kcmc::{
|
||||
WebSocketResponse,
|
||||
},
|
||||
};
|
||||
use kittycad_modeling_cmds::{self as kcmc};
|
||||
use tokio::sync::RwLock;
|
||||
use kittycad_modeling_cmds::{self as kcmc, id::ModelingCmdId, ModelingCmd};
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::ExecutionKind;
|
||||
@ -26,48 +28,71 @@ use crate::{
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EngineConnection {
|
||||
batch: Arc<RwLock<Vec<(WebSocketRequest, SourceRange)>>>,
|
||||
batch_end: Arc<RwLock<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>>,
|
||||
artifact_commands: Arc<RwLock<Vec<ArtifactCommand>>>,
|
||||
execution_kind: Arc<RwLock<ExecutionKind>>,
|
||||
batch: Arc<Mutex<Vec<(WebSocketRequest, SourceRange)>>>,
|
||||
batch_end: Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>>,
|
||||
artifact_commands: Arc<Mutex<Vec<ArtifactCommand>>>,
|
||||
execution_kind: Arc<Mutex<ExecutionKind>>,
|
||||
}
|
||||
|
||||
impl EngineConnection {
|
||||
pub async fn new() -> Result<EngineConnection> {
|
||||
Ok(EngineConnection {
|
||||
batch: Arc::new(RwLock::new(Vec::new())),
|
||||
batch_end: Arc::new(RwLock::new(IndexMap::new())),
|
||||
artifact_commands: Arc::new(RwLock::new(Vec::new())),
|
||||
batch: Arc::new(Mutex::new(Vec::new())),
|
||||
batch_end: Arc::new(Mutex::new(IndexMap::new())),
|
||||
artifact_commands: Arc::new(Mutex::new(Vec::new())),
|
||||
execution_kind: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_command(
|
||||
&self,
|
||||
cmd: &ModelingCmd,
|
||||
cmd_id: ModelingCmdId,
|
||||
id_to_source_range: &HashMap<Uuid, SourceRange>,
|
||||
) -> Result<(), KclError> {
|
||||
let cmd_id = *cmd_id.as_ref();
|
||||
let range = id_to_source_range
|
||||
.get(&cmd_id)
|
||||
.copied()
|
||||
.ok_or_else(|| KclError::internal(format!("Failed to get source range for command ID: {:?}", cmd_id)))?;
|
||||
|
||||
// Add artifact command.
|
||||
let mut artifact_commands = self.artifact_commands.lock().unwrap();
|
||||
artifact_commands.push(ArtifactCommand {
|
||||
cmd_id,
|
||||
range,
|
||||
command: cmd.clone(),
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl crate::engine::EngineManager for EngineConnection {
|
||||
fn batch(&self) -> Arc<RwLock<Vec<(WebSocketRequest, SourceRange)>>> {
|
||||
fn batch(&self) -> Arc<Mutex<Vec<(WebSocketRequest, SourceRange)>>> {
|
||||
self.batch.clone()
|
||||
}
|
||||
|
||||
fn batch_end(&self) -> Arc<RwLock<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>> {
|
||||
fn batch_end(&self) -> Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>> {
|
||||
self.batch_end.clone()
|
||||
}
|
||||
|
||||
fn responses(&self) -> Arc<RwLock<IndexMap<Uuid, WebSocketResponse>>> {
|
||||
Arc::new(RwLock::new(IndexMap::new()))
|
||||
fn responses(&self) -> IndexMap<Uuid, WebSocketResponse> {
|
||||
IndexMap::new()
|
||||
}
|
||||
|
||||
fn artifact_commands(&self) -> Arc<RwLock<Vec<ArtifactCommand>>> {
|
||||
self.artifact_commands.clone()
|
||||
fn take_artifact_commands(&self) -> Vec<ArtifactCommand> {
|
||||
let mut artifact_commands = self.artifact_commands.lock().unwrap();
|
||||
std::mem::take(&mut *artifact_commands)
|
||||
}
|
||||
|
||||
async fn execution_kind(&self) -> ExecutionKind {
|
||||
let guard = self.execution_kind.read().await;
|
||||
fn execution_kind(&self) -> ExecutionKind {
|
||||
let guard = self.execution_kind.lock().unwrap();
|
||||
*guard
|
||||
}
|
||||
|
||||
async fn replace_execution_kind(&self, execution_kind: ExecutionKind) -> ExecutionKind {
|
||||
let mut guard = self.execution_kind.write().await;
|
||||
fn replace_execution_kind(&self, execution_kind: ExecutionKind) -> ExecutionKind {
|
||||
let mut guard = self.execution_kind.lock().unwrap();
|
||||
let original = *guard;
|
||||
*guard = execution_kind;
|
||||
original
|
||||
@ -94,7 +119,7 @@ impl crate::engine::EngineManager for EngineConnection {
|
||||
id: uuid::Uuid,
|
||||
_source_range: SourceRange,
|
||||
cmd: WebSocketRequest,
|
||||
_id_to_source_range: HashMap<Uuid, SourceRange>,
|
||||
id_to_source_range: HashMap<Uuid, SourceRange>,
|
||||
) -> Result<WebSocketResponse, KclError> {
|
||||
match cmd {
|
||||
WebSocketRequest::ModelingCmdBatchReq(ModelingBatch {
|
||||
@ -105,6 +130,7 @@ impl crate::engine::EngineManager for EngineConnection {
|
||||
// Create the empty responses.
|
||||
let mut responses = HashMap::with_capacity(requests.len());
|
||||
for request in requests {
|
||||
self.handle_command(&request.cmd, request.cmd_id, &id_to_source_range)?;
|
||||
responses.insert(
|
||||
request.cmd_id,
|
||||
BatchResponse::Success {
|
||||
@ -118,13 +144,17 @@ impl crate::engine::EngineManager for EngineConnection {
|
||||
success: true,
|
||||
}))
|
||||
}
|
||||
WebSocketRequest::ModelingCmdReq(_) => Ok(WebSocketResponse::Success(SuccessWebSocketResponse {
|
||||
request_id: Some(id),
|
||||
resp: OkWebSocketResponseData::Modeling {
|
||||
modeling_response: OkModelingCmdResponse::Empty {},
|
||||
},
|
||||
success: true,
|
||||
})),
|
||||
WebSocketRequest::ModelingCmdReq(request) => {
|
||||
self.handle_command(&request.cmd, request.cmd_id, &id_to_source_range)?;
|
||||
|
||||
Ok(WebSocketResponse::Success(SuccessWebSocketResponse {
|
||||
request_id: Some(id),
|
||||
resp: OkWebSocketResponseData::Modeling {
|
||||
modeling_response: OkModelingCmdResponse::Empty {},
|
||||
},
|
||||
success: true,
|
||||
}))
|
||||
}
|
||||
_ => Ok(WebSocketResponse::Success(SuccessWebSocketResponse {
|
||||
request_id: Some(id),
|
||||
resp: OkWebSocketResponseData::Modeling {
|
||||
|
@ -1,12 +1,22 @@
|
||||
//! Functions for setting up our WebSocket and WebRTC connections for communications with the
|
||||
//! engine.
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use indexmap::IndexMap;
|
||||
use kcmc::websocket::{WebSocketRequest, WebSocketResponse};
|
||||
use kcmc::{
|
||||
id::ModelingCmdId,
|
||||
ok_response::OkModelingCmdResponse,
|
||||
websocket::{
|
||||
BatchResponse, ModelingBatch, OkWebSocketResponseData, SuccessWebSocketResponse, WebSocketRequest,
|
||||
WebSocketResponse,
|
||||
},
|
||||
ModelingCmd,
|
||||
};
|
||||
use kittycad_modeling_cmds as kcmc;
|
||||
use tokio::sync::RwLock;
|
||||
use uuid::Uuid;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
@ -44,11 +54,11 @@ extern "C" {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EngineConnection {
|
||||
manager: Arc<EngineCommandManager>,
|
||||
batch: Arc<RwLock<Vec<(WebSocketRequest, SourceRange)>>>,
|
||||
batch_end: Arc<RwLock<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>>,
|
||||
responses: Arc<RwLock<IndexMap<Uuid, WebSocketResponse>>>,
|
||||
artifact_commands: Arc<RwLock<Vec<ArtifactCommand>>>,
|
||||
execution_kind: Arc<RwLock<ExecutionKind>>,
|
||||
batch: Arc<Mutex<Vec<(WebSocketRequest, SourceRange)>>>,
|
||||
batch_end: Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>>,
|
||||
responses: Arc<Mutex<IndexMap<Uuid, WebSocketResponse>>>,
|
||||
artifact_commands: Arc<Mutex<Vec<ArtifactCommand>>>,
|
||||
execution_kind: Arc<Mutex<ExecutionKind>>,
|
||||
}
|
||||
|
||||
// Safety: WebAssembly will only ever run in a single-threaded context.
|
||||
@ -60,101 +70,66 @@ impl EngineConnection {
|
||||
#[allow(clippy::arc_with_non_send_sync)]
|
||||
Ok(EngineConnection {
|
||||
manager: Arc::new(manager),
|
||||
batch: Arc::new(RwLock::new(Vec::new())),
|
||||
batch_end: Arc::new(RwLock::new(IndexMap::new())),
|
||||
responses: Arc::new(RwLock::new(IndexMap::new())),
|
||||
artifact_commands: Arc::new(RwLock::new(Vec::new())),
|
||||
batch: Arc::new(Mutex::new(Vec::new())),
|
||||
batch_end: Arc::new(Mutex::new(IndexMap::new())),
|
||||
responses: Arc::new(Mutex::new(IndexMap::new())),
|
||||
artifact_commands: Arc::new(Mutex::new(Vec::new())),
|
||||
execution_kind: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn do_send_modeling_cmd(
|
||||
impl EngineConnection {
|
||||
fn handle_command(
|
||||
&self,
|
||||
id: uuid::Uuid,
|
||||
source_range: SourceRange,
|
||||
cmd: WebSocketRequest,
|
||||
id_to_source_range: HashMap<uuid::Uuid, SourceRange>,
|
||||
) -> Result<WebSocketResponse, KclError> {
|
||||
let source_range_str = serde_json::to_string(&source_range).map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to serialize source range: {:?}", e),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
let cmd_str = serde_json::to_string(&cmd).map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to serialize modeling command: {:?}", e),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
let id_to_source_range_str = serde_json::to_string(&id_to_source_range).map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to serialize id to source range: {:?}", e),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
cmd: &ModelingCmd,
|
||||
cmd_id: ModelingCmdId,
|
||||
id_to_source_range: &HashMap<Uuid, SourceRange>,
|
||||
) -> Result<(), KclError> {
|
||||
let cmd_id = *cmd_id.as_ref();
|
||||
let range = id_to_source_range
|
||||
.get(&cmd_id)
|
||||
.copied()
|
||||
.ok_or_else(|| KclError::internal(format!("Failed to get source range for command ID: {:?}", cmd_id)))?;
|
||||
|
||||
let promise = self
|
||||
.manager
|
||||
.send_modeling_cmd_from_wasm(id.to_string(), source_range_str, cmd_str, id_to_source_range_str)
|
||||
.map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: e.to_string().into(),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
let value = crate::wasm::JsFuture::from(promise).await.map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to wait for promise from engine: {:?}", e),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
// Parse the value as a string.
|
||||
let s = value.as_string().ok_or_else(|| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to get string from response from engine: `{:?}`", value),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
let ws_result: WebSocketResponse = serde_json::from_str(&s).map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to deserialize response from engine: {:?}", e),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
Ok(ws_result)
|
||||
// Add artifact command.
|
||||
let mut artifact_commands = self.artifact_commands.lock().unwrap();
|
||||
artifact_commands.push(ArtifactCommand {
|
||||
cmd_id,
|
||||
range,
|
||||
command: cmd.clone(),
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl crate::engine::EngineManager for EngineConnection {
|
||||
fn batch(&self) -> Arc<RwLock<Vec<(WebSocketRequest, SourceRange)>>> {
|
||||
fn batch(&self) -> Arc<Mutex<Vec<(WebSocketRequest, SourceRange)>>> {
|
||||
self.batch.clone()
|
||||
}
|
||||
|
||||
fn batch_end(&self) -> Arc<RwLock<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>> {
|
||||
fn batch_end(&self) -> Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>> {
|
||||
self.batch_end.clone()
|
||||
}
|
||||
|
||||
fn responses(&self) -> Arc<RwLock<IndexMap<Uuid, WebSocketResponse>>> {
|
||||
self.responses.clone()
|
||||
fn responses(&self) -> IndexMap<Uuid, WebSocketResponse> {
|
||||
let responses = self.responses.lock().unwrap();
|
||||
responses.clone()
|
||||
}
|
||||
|
||||
fn artifact_commands(&self) -> Arc<RwLock<Vec<ArtifactCommand>>> {
|
||||
self.artifact_commands.clone()
|
||||
fn take_artifact_commands(&self) -> Vec<ArtifactCommand> {
|
||||
let mut artifact_commands = self.artifact_commands.lock().unwrap();
|
||||
std::mem::take(&mut *artifact_commands)
|
||||
}
|
||||
|
||||
async fn execution_kind(&self) -> ExecutionKind {
|
||||
let guard = self.execution_kind.read().await;
|
||||
fn execution_kind(&self) -> ExecutionKind {
|
||||
let guard = self.execution_kind.lock().unwrap();
|
||||
*guard
|
||||
}
|
||||
|
||||
async fn replace_execution_kind(&self, execution_kind: ExecutionKind) -> ExecutionKind {
|
||||
let mut guard = self.execution_kind.write().await;
|
||||
fn replace_execution_kind(&self, execution_kind: ExecutionKind) -> ExecutionKind {
|
||||
let mut guard = self.execution_kind.lock().unwrap();
|
||||
let original = *guard;
|
||||
*guard = execution_kind;
|
||||
original
|
||||
@ -239,18 +214,100 @@ impl crate::engine::EngineManager for EngineConnection {
|
||||
cmd: WebSocketRequest,
|
||||
id_to_source_range: HashMap<uuid::Uuid, SourceRange>,
|
||||
) -> Result<WebSocketResponse, KclError> {
|
||||
let ws_result = self
|
||||
.do_send_modeling_cmd(id, source_range, cmd, id_to_source_range)
|
||||
.await?;
|
||||
|
||||
// In isolated mode, we don't save the response.
|
||||
if self.execution_kind().await.is_isolated() {
|
||||
return Ok(ws_result);
|
||||
match &cmd {
|
||||
WebSocketRequest::ModelingCmdBatchReq(ModelingBatch { requests, .. }) => {
|
||||
for request in requests {
|
||||
self.handle_command(&request.cmd, request.cmd_id, &id_to_source_range)?;
|
||||
}
|
||||
}
|
||||
WebSocketRequest::ModelingCmdReq(request) => {
|
||||
self.handle_command(&request.cmd, request.cmd_id, &id_to_source_range)?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let mut responses = self.responses.write().await;
|
||||
// In isolated mode, we don't send the command to the engine.
|
||||
if self.execution_kind().is_isolated() {
|
||||
return match &cmd {
|
||||
WebSocketRequest::ModelingCmdBatchReq(ModelingBatch { requests, .. }) => {
|
||||
let mut responses = HashMap::with_capacity(requests.len());
|
||||
for request in requests {
|
||||
responses.insert(
|
||||
request.cmd_id,
|
||||
BatchResponse::Success {
|
||||
response: OkModelingCmdResponse::Empty {},
|
||||
},
|
||||
);
|
||||
}
|
||||
Ok(WebSocketResponse::Success(SuccessWebSocketResponse {
|
||||
request_id: Some(id),
|
||||
resp: OkWebSocketResponseData::ModelingBatch { responses },
|
||||
success: true,
|
||||
}))
|
||||
}
|
||||
_ => Ok(WebSocketResponse::Success(SuccessWebSocketResponse {
|
||||
request_id: Some(id),
|
||||
resp: OkWebSocketResponseData::Modeling {
|
||||
modeling_response: OkModelingCmdResponse::Empty {},
|
||||
},
|
||||
success: true,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
let source_range_str = serde_json::to_string(&source_range).map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to serialize source range: {:?}", e),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
let cmd_str = serde_json::to_string(&cmd).map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to serialize modeling command: {:?}", e),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
let id_to_source_range_str = serde_json::to_string(&id_to_source_range).map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to serialize id to source range: {:?}", e),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
let promise = self
|
||||
.manager
|
||||
.send_modeling_cmd_from_wasm(id.to_string(), source_range_str, cmd_str, id_to_source_range_str)
|
||||
.map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: e.to_string().into(),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
let value = crate::wasm::JsFuture::from(promise).await.map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to wait for promise from engine: {:?}", e),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
// Parse the value as a string.
|
||||
let s = value.as_string().ok_or_else(|| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to get string from response from engine: `{:?}`", value),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
let ws_result: WebSocketResponse = serde_json::from_str(&s).map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to deserialize response from engine: {:?}", e),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
let mut responses = self.responses.lock().unwrap();
|
||||
responses.insert(id, ws_result.clone());
|
||||
drop(responses);
|
||||
|
||||
Ok(ws_result)
|
||||
}
|
||||
|
@ -8,12 +8,14 @@ pub mod conn_mock;
|
||||
#[cfg(feature = "engine")]
|
||||
pub mod conn_wasm;
|
||||
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use kcmc::{
|
||||
each_cmd as mcmd,
|
||||
id::ModelingCmdId,
|
||||
length_unit::LengthUnit,
|
||||
ok_response::OkModelingCmdResponse,
|
||||
shared::Color,
|
||||
@ -26,7 +28,6 @@ use kcmc::{
|
||||
use kittycad_modeling_cmds as kcmc;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::RwLock;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
@ -61,38 +62,28 @@ impl ExecutionKind {
|
||||
#[async_trait::async_trait]
|
||||
pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
/// Get the batch of commands to be sent to the engine.
|
||||
fn batch(&self) -> Arc<RwLock<Vec<(WebSocketRequest, SourceRange)>>>;
|
||||
fn batch(&self) -> Arc<Mutex<Vec<(WebSocketRequest, SourceRange)>>>;
|
||||
|
||||
/// Get the batch of end commands to be sent to the engine.
|
||||
fn batch_end(&self) -> Arc<RwLock<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>>;
|
||||
fn batch_end(&self) -> Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>>;
|
||||
|
||||
/// Get the command responses from the engine.
|
||||
fn responses(&self) -> Arc<RwLock<IndexMap<Uuid, WebSocketResponse>>>;
|
||||
fn responses(&self) -> IndexMap<Uuid, WebSocketResponse>;
|
||||
|
||||
/// Get the artifact commands that have accumulated so far.
|
||||
fn artifact_commands(&self) -> Arc<RwLock<Vec<ArtifactCommand>>>;
|
||||
/// Take the artifact commands generated up to this point and clear them.
|
||||
fn take_artifact_commands(&self) -> Vec<ArtifactCommand>;
|
||||
|
||||
/// Clear all artifact commands that have accumulated so far.
|
||||
async fn clear_artifact_commands(&self) {
|
||||
self.artifact_commands().write().await.clear();
|
||||
}
|
||||
|
||||
/// Take the artifact commands that have accumulated so far and clear them.
|
||||
async fn take_artifact_commands(&self) -> Vec<ArtifactCommand> {
|
||||
std::mem::take(&mut *self.artifact_commands().write().await)
|
||||
}
|
||||
|
||||
/// Take the responses that have accumulated so far and clear them.
|
||||
async fn take_responses(&self) -> IndexMap<Uuid, WebSocketResponse> {
|
||||
std::mem::take(&mut *self.responses().write().await)
|
||||
fn clear_artifact_commands(&self) {
|
||||
self.take_artifact_commands();
|
||||
}
|
||||
|
||||
/// Get the current execution kind.
|
||||
async fn execution_kind(&self) -> ExecutionKind;
|
||||
fn execution_kind(&self) -> ExecutionKind;
|
||||
|
||||
/// Replace the current execution kind with a new value and return the
|
||||
/// existing value.
|
||||
async fn replace_execution_kind(&self, execution_kind: ExecutionKind) -> ExecutionKind;
|
||||
fn replace_execution_kind(&self, execution_kind: ExecutionKind) -> ExecutionKind;
|
||||
|
||||
/// Get the default planes.
|
||||
async fn default_planes(
|
||||
@ -136,7 +127,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
|
||||
// Ensure artifact commands are cleared so that we don't accumulate them
|
||||
// across runs.
|
||||
self.clear_artifact_commands().await;
|
||||
self.clear_artifact_commands();
|
||||
|
||||
// Do the after clear scene hook.
|
||||
self.clear_scene_post_hook(id_generator, source_range).await?;
|
||||
@ -160,27 +151,6 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_artifact_command(
|
||||
&self,
|
||||
cmd: &ModelingCmd,
|
||||
cmd_id: ModelingCmdId,
|
||||
id_to_source_range: &HashMap<Uuid, SourceRange>,
|
||||
) -> Result<(), KclError> {
|
||||
let cmd_id = *cmd_id.as_ref();
|
||||
let range = id_to_source_range
|
||||
.get(&cmd_id)
|
||||
.copied()
|
||||
.ok_or_else(|| KclError::internal(format!("Failed to get source range for command ID: {:?}", cmd_id)))?;
|
||||
|
||||
// Add artifact command.
|
||||
self.artifact_commands().write().await.push(ArtifactCommand {
|
||||
cmd_id,
|
||||
range,
|
||||
command: cmd.clone(),
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn set_units(
|
||||
&self,
|
||||
units: crate::UnitLength,
|
||||
@ -227,18 +197,13 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
source_range: SourceRange,
|
||||
cmd: &ModelingCmd,
|
||||
) -> Result<(), crate::errors::KclError> {
|
||||
// In isolated mode, we don't send the command to the engine.
|
||||
if self.execution_kind().await.is_isolated() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let req = WebSocketRequest::ModelingCmdReq(ModelingCmdReq {
|
||||
cmd: cmd.clone(),
|
||||
cmd_id: id.into(),
|
||||
});
|
||||
|
||||
// Add cmd to the batch.
|
||||
self.batch().write().await.push((req, source_range));
|
||||
self.batch().lock().unwrap().push((req, source_range));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -252,18 +217,13 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
source_range: SourceRange,
|
||||
cmd: &ModelingCmd,
|
||||
) -> Result<(), crate::errors::KclError> {
|
||||
// In isolated mode, we don't send the command to the engine.
|
||||
if self.execution_kind().await.is_isolated() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let req = WebSocketRequest::ModelingCmdReq(ModelingCmdReq {
|
||||
cmd: cmd.clone(),
|
||||
cmd_id: id.into(),
|
||||
});
|
||||
|
||||
// Add cmd to the batch end.
|
||||
self.batch_end().write().await.insert(id, (req, source_range));
|
||||
self.batch_end().lock().unwrap().insert(id, (req, source_range));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -289,11 +249,11 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
source_range: SourceRange,
|
||||
) -> Result<OkWebSocketResponseData, crate::errors::KclError> {
|
||||
let all_requests = if batch_end {
|
||||
let mut requests = self.batch().read().await.clone();
|
||||
requests.extend(self.batch_end().read().await.values().cloned());
|
||||
let mut requests = self.batch().lock().unwrap().clone();
|
||||
requests.extend(self.batch_end().lock().unwrap().values().cloned());
|
||||
requests
|
||||
} else {
|
||||
self.batch().read().await.clone()
|
||||
self.batch().lock().unwrap().clone()
|
||||
};
|
||||
|
||||
// Return early if we have no commands to send.
|
||||
@ -344,27 +304,10 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
}
|
||||
}
|
||||
|
||||
// Do the artifact commands.
|
||||
for (req, _) in all_requests.iter() {
|
||||
match &req {
|
||||
WebSocketRequest::ModelingCmdBatchReq(ModelingBatch { requests, .. }) => {
|
||||
for request in requests {
|
||||
self.handle_artifact_command(&request.cmd, request.cmd_id, &id_to_source_range)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
WebSocketRequest::ModelingCmdReq(request) => {
|
||||
self.handle_artifact_command(&request.cmd, request.cmd_id, &id_to_source_range)
|
||||
.await?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Throw away the old batch queue.
|
||||
self.batch().write().await.clear();
|
||||
self.batch().lock().unwrap().clear();
|
||||
if batch_end {
|
||||
self.batch_end().write().await.clear();
|
||||
self.batch_end().lock().unwrap().clear();
|
||||
}
|
||||
|
||||
// We pop off the responses to cleanup our mappings.
|
||||
@ -653,7 +596,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
|
||||
/// Get session data, if it has been received.
|
||||
/// Returns None if the server never sent it.
|
||||
async fn get_session_data(&self) -> Option<ModelingSessionData> {
|
||||
fn get_session_data(&self) -> Option<ModelingSessionData> {
|
||||
None
|
||||
}
|
||||
|
||||
|
@ -9,8 +9,8 @@ use crate::{
|
||||
execution::{
|
||||
annotations,
|
||||
cad_op::{OpArg, Operation},
|
||||
kcl_value::NumericType,
|
||||
memory,
|
||||
memory::ProgramMemory,
|
||||
state::ModuleState,
|
||||
BodyType, EnvironmentRef, ExecState, ExecutorContext, KclValue, MemoryFunction, Metadata, TagEngineInfo,
|
||||
TagIdentifier,
|
||||
@ -48,7 +48,7 @@ impl ExecutorContext {
|
||||
let old_units = exec_state.length_unit();
|
||||
exec_state.mod_local.settings.update_from_annotation(annotation)?;
|
||||
let new_units = exec_state.length_unit();
|
||||
if !self.engine.execution_kind().await.is_isolated() && old_units != new_units {
|
||||
if !self.engine.execution_kind().is_isolated() && old_units != new_units {
|
||||
self.engine
|
||||
.set_units(new_units.into(), annotation.as_source_range())
|
||||
.await?;
|
||||
@ -393,7 +393,7 @@ impl ExecutorContext {
|
||||
exec_state.global.mod_loader.enter_module(path);
|
||||
std::mem::swap(&mut exec_state.mod_local, &mut local_state);
|
||||
exec_state.mut_memory().push_new_root_env();
|
||||
let original_execution = self.engine.replace_execution_kind(exec_kind).await;
|
||||
let original_execution = self.engine.replace_execution_kind(exec_kind);
|
||||
|
||||
let result = self
|
||||
.exec_program(program, exec_state, crate::execution::BodyType::Root)
|
||||
@ -406,7 +406,7 @@ impl ExecutorContext {
|
||||
if !exec_kind.is_isolated() && new_units != old_units {
|
||||
self.engine.set_units(old_units.into(), Default::default()).await?;
|
||||
}
|
||||
self.engine.replace_execution_kind(original_execution).await;
|
||||
self.engine.replace_execution_kind(original_execution);
|
||||
|
||||
result
|
||||
.map_err(|err| {
|
||||
@ -437,7 +437,7 @@ impl ExecutorContext {
|
||||
) -> Result<KclValue, KclError> {
|
||||
let item = match init {
|
||||
Expr::None(none) => KclValue::from(none),
|
||||
Expr::Literal(literal) => KclValue::from_literal((**literal).clone(), &exec_state.mod_local.settings),
|
||||
Expr::Literal(literal) => KclValue::from(literal),
|
||||
Expr::TagDeclarator(tag) => tag.execute(exec_state).await?,
|
||||
Expr::Identifier(identifier) => {
|
||||
let value = exec_state.memory().get(&identifier.name, identifier.into())?.clone();
|
||||
@ -518,10 +518,7 @@ impl BinaryPart {
|
||||
#[async_recursion]
|
||||
pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
||||
match self {
|
||||
BinaryPart::Literal(literal) => Ok(KclValue::from_literal(
|
||||
(**literal).clone(),
|
||||
&exec_state.mod_local.settings,
|
||||
)),
|
||||
BinaryPart::Literal(literal) => Ok(literal.into()),
|
||||
BinaryPart::Identifier(identifier) => {
|
||||
let value = exec_state.memory().get(&identifier.name, identifier.into())?;
|
||||
Ok(value.clone())
|
||||
@ -707,32 +704,26 @@ impl Node<BinaryExpression> {
|
||||
BinaryOperator::Add => KclValue::Number {
|
||||
value: left + right,
|
||||
meta,
|
||||
ty: NumericType::Unknown,
|
||||
},
|
||||
BinaryOperator::Sub => KclValue::Number {
|
||||
value: left - right,
|
||||
meta,
|
||||
ty: NumericType::Unknown,
|
||||
},
|
||||
BinaryOperator::Mul => KclValue::Number {
|
||||
value: left * right,
|
||||
meta,
|
||||
ty: NumericType::Unknown,
|
||||
},
|
||||
BinaryOperator::Div => KclValue::Number {
|
||||
value: left / right,
|
||||
meta,
|
||||
ty: NumericType::Unknown,
|
||||
},
|
||||
BinaryOperator::Mod => KclValue::Number {
|
||||
value: left % right,
|
||||
meta,
|
||||
ty: NumericType::Unknown,
|
||||
},
|
||||
BinaryOperator::Pow => KclValue::Number {
|
||||
value: left.powf(right),
|
||||
meta,
|
||||
ty: NumericType::Unknown,
|
||||
},
|
||||
BinaryOperator::Neq => KclValue::Bool {
|
||||
value: left != right,
|
||||
@ -795,14 +786,19 @@ impl Node<UnaryExpression> {
|
||||
|
||||
let value = &self.argument.get_result(exec_state, ctx).await?;
|
||||
match value {
|
||||
KclValue::Number { value, ty, .. } => {
|
||||
KclValue::Number { value, meta: _ } => {
|
||||
let meta = vec![Metadata {
|
||||
source_range: self.into(),
|
||||
}];
|
||||
Ok(KclValue::Number { value: -value, meta })
|
||||
}
|
||||
KclValue::Int { value, meta: _ } => {
|
||||
let meta = vec![Metadata {
|
||||
source_range: self.into(),
|
||||
}];
|
||||
Ok(KclValue::Number {
|
||||
value: -value,
|
||||
value: (-value) as f64,
|
||||
meta,
|
||||
ty: ty.clone(),
|
||||
})
|
||||
}
|
||||
_ => Err(KclError::Semantic(KclErrorDetails {
|
||||
@ -1303,9 +1299,8 @@ impl Node<ArrayRangeExpression> {
|
||||
Ok(KclValue::Array {
|
||||
value: range
|
||||
.into_iter()
|
||||
.map(|num| KclValue::Number {
|
||||
value: num as f64,
|
||||
ty: NumericType::Unknown,
|
||||
.map(|num| KclValue::Int {
|
||||
value: num,
|
||||
meta: meta.clone(),
|
||||
})
|
||||
.collect(),
|
||||
@ -1347,6 +1342,8 @@ fn article_for(s: &str) -> &'static str {
|
||||
pub fn parse_number_as_f64(v: &KclValue, source_range: SourceRange) -> Result<f64, KclError> {
|
||||
if let KclValue::Number { value: n, .. } = &v {
|
||||
Ok(*n)
|
||||
} else if let KclValue::Int { value: n, .. } = &v {
|
||||
Ok(*n as f64)
|
||||
} else {
|
||||
let actual_type = v.human_friendly_type();
|
||||
let article = if actual_type.starts_with(['a', 'e', 'i', 'o', 'u']) {
|
||||
@ -1463,7 +1460,16 @@ fn jvalue_to_prop(value: &KclValue, property_sr: Vec<SourceRange>, name: &str) -
|
||||
}))
|
||||
};
|
||||
match value {
|
||||
KclValue::Number{value: num, .. } => {
|
||||
KclValue::Int { value:num, meta: _ } => {
|
||||
let maybe_int: Result<usize, _> = (*num).try_into();
|
||||
if let Ok(uint) = maybe_int {
|
||||
Ok(Property::UInt(uint))
|
||||
}
|
||||
else {
|
||||
make_err(format!("'{num}' is negative, so you can't index an array with it"))
|
||||
}
|
||||
}
|
||||
KclValue::Number{value: num, meta:_} => {
|
||||
let num = *num;
|
||||
if num < 0.0 {
|
||||
return make_err(format!("'{num}' is negative, so you can't index an array with it"))
|
||||
@ -1504,7 +1510,7 @@ impl Node<PipeExpression> {
|
||||
fn assign_args_to_params(
|
||||
function_expression: NodeRef<'_, FunctionExpression>,
|
||||
args: Vec<Arg>,
|
||||
exec_state: &mut ExecState,
|
||||
fn_memory: &mut ProgramMemory,
|
||||
) -> Result<(), KclError> {
|
||||
let num_args = function_expression.number_of_args();
|
||||
let (min_params, max_params) = num_args.into_inner();
|
||||
@ -1524,15 +1530,12 @@ fn assign_args_to_params(
|
||||
return Err(err_wrong_number_args);
|
||||
}
|
||||
|
||||
let mem = &mut exec_state.global.memory;
|
||||
let settings = &exec_state.mod_local.settings;
|
||||
|
||||
// Add the arguments to the memory. A new call frame should have already
|
||||
// been created.
|
||||
for (index, param) in function_expression.params.iter().enumerate() {
|
||||
if let Some(arg) = args.get(index) {
|
||||
// Argument was provided.
|
||||
mem.add(
|
||||
fn_memory.add(
|
||||
param.identifier.name.clone(),
|
||||
arg.value.clone(),
|
||||
(¶m.identifier).into(),
|
||||
@ -1542,9 +1545,9 @@ fn assign_args_to_params(
|
||||
if let Some(ref default_val) = param.default_value {
|
||||
// If the corresponding parameter is optional,
|
||||
// then it's fine, the user doesn't need to supply it.
|
||||
mem.add(
|
||||
fn_memory.add(
|
||||
param.identifier.name.clone(),
|
||||
KclValue::from_default_param(default_val.clone(), settings),
|
||||
default_val.clone().into(),
|
||||
(¶m.identifier).into(),
|
||||
)?;
|
||||
} else {
|
||||
@ -1560,21 +1563,18 @@ fn assign_args_to_params(
|
||||
fn assign_args_to_params_kw(
|
||||
function_expression: NodeRef<'_, FunctionExpression>,
|
||||
mut args: crate::std::args::KwArgs,
|
||||
exec_state: &mut ExecState,
|
||||
fn_memory: &mut ProgramMemory,
|
||||
) -> Result<(), KclError> {
|
||||
// Add the arguments to the memory. A new call frame should have already
|
||||
// been created.
|
||||
let source_ranges = vec![function_expression.into()];
|
||||
let mem = &mut exec_state.global.memory;
|
||||
let settings = &exec_state.mod_local.settings;
|
||||
|
||||
for param in function_expression.params.iter() {
|
||||
if param.labeled {
|
||||
let arg = args.labeled.get(¶m.identifier.name);
|
||||
let arg_val = match arg {
|
||||
Some(arg) => arg.value.clone(),
|
||||
None => match param.default_value {
|
||||
Some(ref default_val) => KclValue::from_default_param(default_val.clone(), settings),
|
||||
Some(ref default_val) => KclValue::from(default_val.clone()),
|
||||
None => {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
source_ranges,
|
||||
@ -1586,7 +1586,7 @@ fn assign_args_to_params_kw(
|
||||
}
|
||||
},
|
||||
};
|
||||
mem.add(param.identifier.name.clone(), arg_val, (¶m.identifier).into())?;
|
||||
fn_memory.add(param.identifier.name.clone(), arg_val, (¶m.identifier).into())?;
|
||||
} else {
|
||||
let Some(unlabeled) = args.unlabeled.take() else {
|
||||
let param_name = ¶m.identifier.name;
|
||||
@ -1603,7 +1603,7 @@ fn assign_args_to_params_kw(
|
||||
})
|
||||
});
|
||||
};
|
||||
mem.add(
|
||||
fn_memory.add(
|
||||
param.identifier.name.clone(),
|
||||
unlabeled.value.clone(),
|
||||
(¶m.identifier).into(),
|
||||
@ -1624,7 +1624,7 @@ pub(crate) async fn call_user_defined_function(
|
||||
// variables shadow variables in the parent scope. The new environment's
|
||||
// parent should be the environment of the closure.
|
||||
exec_state.mut_memory().push_new_env_for_call(memory);
|
||||
if let Err(e) = assign_args_to_params(function_expression, args, exec_state) {
|
||||
if let Err(e) = assign_args_to_params(function_expression, args, exec_state.mut_memory()) {
|
||||
exec_state.mut_memory().pop_env();
|
||||
return Err(e);
|
||||
}
|
||||
@ -1657,7 +1657,7 @@ pub(crate) async fn call_user_defined_function_kw(
|
||||
// variables shadow variables in the parent scope. The new environment's
|
||||
// parent should be the environment of the closure.
|
||||
exec_state.mut_memory().push_new_env_for_call(memory);
|
||||
if let Err(e) = assign_args_to_params_kw(function_expression, args, exec_state) {
|
||||
if let Err(e) = assign_args_to_params_kw(function_expression, args, exec_state.mut_memory()) {
|
||||
exec_state.mut_memory().pop_env();
|
||||
return Err(e);
|
||||
}
|
||||
@ -1720,19 +1720,19 @@ impl JsonSchema for FunctionParam<'_> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::{
|
||||
execution::{memory::ProgramMemory, parse_execute},
|
||||
execution::parse_execute,
|
||||
parsing::ast::types::{DefaultParamVal, Identifier, Parameter},
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_assign_args_to_params() {
|
||||
// Set up a little framework for this test.
|
||||
fn mem(number: usize) -> KclValue {
|
||||
KclValue::Number {
|
||||
value: number as f64,
|
||||
ty: NumericType::count(),
|
||||
KclValue::Int {
|
||||
value: number as i64,
|
||||
meta: Default::default(),
|
||||
}
|
||||
}
|
||||
@ -1838,8 +1838,8 @@ mod test {
|
||||
digest: None,
|
||||
});
|
||||
let args = args.into_iter().map(Arg::synthetic).collect();
|
||||
let mut exec_state = ExecState::new(&Default::default());
|
||||
let actual = assign_args_to_params(func_expr, args, &mut exec_state).map(|_| exec_state.global.memory);
|
||||
let mut actual = ProgramMemory::new();
|
||||
let actual = assign_args_to_params(func_expr, args, &mut actual).map(|_| actual);
|
||||
assert_eq!(
|
||||
actual, expected,
|
||||
"failed test '{test_name}':\ngot {actual:?}\nbut expected\n{expected:?}"
|
||||
|
@ -289,7 +289,7 @@ pub struct PreImportedGeometry {
|
||||
}
|
||||
|
||||
pub async fn send_to_engine(pre: PreImportedGeometry, ctxt: &ExecutorContext) -> Result<ImportedGeometry, KclError> {
|
||||
if ctxt.no_engine_commands().await {
|
||||
if ctxt.no_engine_commands() {
|
||||
return Ok(ImportedGeometry {
|
||||
id: pre.id,
|
||||
value: pre.command.files.iter().map(|f| f.path.to_string()).collect(),
|
||||
|
@ -12,16 +12,14 @@ use crate::{
|
||||
TagIdentifier,
|
||||
},
|
||||
parsing::{
|
||||
ast::types::{
|
||||
DefaultParamVal, FunctionExpression, KclNone, Literal, LiteralValue, Node, TagDeclarator, TagNode,
|
||||
},
|
||||
ast::types::{FunctionExpression, KclNone, LiteralValue, TagDeclarator, TagNode},
|
||||
token::NumericSuffix,
|
||||
},
|
||||
std::{args::Arg, FnAsArg},
|
||||
ExecutorContext, KclError, ModuleId, SourceRange,
|
||||
};
|
||||
|
||||
use super::{memory::EnvironmentRef, MetaSettings};
|
||||
use super::memory::EnvironmentRef;
|
||||
|
||||
pub type KclObjectFields = HashMap<String, KclValue>;
|
||||
|
||||
@ -42,7 +40,11 @@ pub enum KclValue {
|
||||
},
|
||||
Number {
|
||||
value: f64,
|
||||
ty: NumericType,
|
||||
#[serde(rename = "__meta")]
|
||||
meta: Vec<Metadata>,
|
||||
},
|
||||
Int {
|
||||
value: i64,
|
||||
#[serde(rename = "__meta")]
|
||||
meta: Vec<Metadata>,
|
||||
},
|
||||
@ -166,6 +168,7 @@ impl From<KclValue> for Vec<SourceRange> {
|
||||
KclValue::Face { value } => to_vec_sr(&value.meta),
|
||||
KclValue::Bool { meta, .. } => to_vec_sr(&meta),
|
||||
KclValue::Number { meta, .. } => to_vec_sr(&meta),
|
||||
KclValue::Int { meta, .. } => to_vec_sr(&meta),
|
||||
KclValue::String { meta, .. } => to_vec_sr(&meta),
|
||||
KclValue::Array { meta, .. } => to_vec_sr(&meta),
|
||||
KclValue::Object { meta, .. } => to_vec_sr(&meta),
|
||||
@ -197,6 +200,7 @@ impl From<&KclValue> for Vec<SourceRange> {
|
||||
KclValue::Face { value } => to_vec_sr(&value.meta),
|
||||
KclValue::Bool { meta, .. } => to_vec_sr(meta),
|
||||
KclValue::Number { meta, .. } => to_vec_sr(meta),
|
||||
KclValue::Int { meta, .. } => to_vec_sr(meta),
|
||||
KclValue::String { meta, .. } => to_vec_sr(meta),
|
||||
KclValue::Uuid { meta, .. } => to_vec_sr(meta),
|
||||
KclValue::Array { meta, .. } => to_vec_sr(meta),
|
||||
@ -213,7 +217,8 @@ impl KclValue {
|
||||
match self {
|
||||
KclValue::Uuid { value: _, meta } => meta.clone(),
|
||||
KclValue::Bool { value: _, meta } => meta.clone(),
|
||||
KclValue::Number { meta, .. } => meta.clone(),
|
||||
KclValue::Number { value: _, meta } => meta.clone(),
|
||||
KclValue::Int { value: _, meta } => meta.clone(),
|
||||
KclValue::String { value: _, meta } => meta.clone(),
|
||||
KclValue::Array { value: _, meta } => meta.clone(),
|
||||
KclValue::Object { value: _, meta } => meta.clone(),
|
||||
@ -292,6 +297,7 @@ impl KclValue {
|
||||
KclValue::Face { .. } => "Face",
|
||||
KclValue::Bool { .. } => "boolean (true/false value)",
|
||||
KclValue::Number { .. } => "number",
|
||||
KclValue::Int { .. } => "integer",
|
||||
KclValue::String { .. } => "string (text)",
|
||||
KclValue::Array { .. } => "array (list)",
|
||||
KclValue::Object { .. } => "object",
|
||||
@ -301,29 +307,14 @@ impl KclValue {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_literal(literal: Node<Literal>, settings: &MetaSettings) -> Self {
|
||||
let meta = vec![literal.metadata()];
|
||||
match literal.inner.value {
|
||||
LiteralValue::Number { value, suffix } => KclValue::Number {
|
||||
value,
|
||||
meta,
|
||||
ty: NumericType::from_parsed(suffix, settings),
|
||||
},
|
||||
pub(crate) fn from_literal(literal: LiteralValue, meta: Vec<Metadata>) -> Self {
|
||||
match literal {
|
||||
LiteralValue::Number { value, .. } => KclValue::Number { value, meta },
|
||||
LiteralValue::String(value) => KclValue::String { value, meta },
|
||||
LiteralValue::Bool(value) => KclValue::Bool { value, meta },
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_default_param(param: DefaultParamVal, settings: &MetaSettings) -> Self {
|
||||
match param {
|
||||
DefaultParamVal::Literal(lit) => Self::from_literal(lit, settings),
|
||||
DefaultParamVal::KclNone(none) => KclValue::KclNone {
|
||||
value: none,
|
||||
meta: Default::default(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn map_env_ref(&self, env_map: &HashMap<EnvironmentRef, EnvironmentRef>) -> Self {
|
||||
let mut result = self.clone();
|
||||
if let KclValue::Function { ref mut memory, .. } = result {
|
||||
@ -336,30 +327,20 @@ impl KclValue {
|
||||
|
||||
/// Put the number into a KCL value.
|
||||
pub const fn from_number(f: f64, meta: Vec<Metadata>) -> Self {
|
||||
Self::Number {
|
||||
value: f,
|
||||
meta,
|
||||
ty: NumericType::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn from_number_with_type(f: f64, ty: NumericType, meta: Vec<Metadata>) -> Self {
|
||||
Self::Number { value: f, meta, ty }
|
||||
Self::Number { value: f, meta }
|
||||
}
|
||||
|
||||
/// Put the point into a KCL value.
|
||||
pub fn from_point2d(p: [f64; 2], ty: NumericType, meta: Vec<Metadata>) -> Self {
|
||||
pub fn from_point2d(p: [f64; 2], meta: Vec<Metadata>) -> Self {
|
||||
Self::Array {
|
||||
value: vec![
|
||||
Self::Number {
|
||||
value: p[0],
|
||||
meta: meta.clone(),
|
||||
ty: ty.clone(),
|
||||
},
|
||||
Self::Number {
|
||||
value: p[1],
|
||||
meta: meta.clone(),
|
||||
ty,
|
||||
},
|
||||
],
|
||||
meta,
|
||||
@ -368,6 +349,7 @@ impl KclValue {
|
||||
|
||||
pub(crate) fn as_usize(&self) -> Option<usize> {
|
||||
match self {
|
||||
KclValue::Int { value, .. } if *value > 0 => Some(*value as usize),
|
||||
KclValue::Number { value, .. } => crate::try_f64_to_usize(*value),
|
||||
_ => None,
|
||||
}
|
||||
@ -375,6 +357,7 @@ impl KclValue {
|
||||
|
||||
pub fn as_int(&self) -> Option<i64> {
|
||||
match self {
|
||||
KclValue::Int { value, .. } => Some(*value),
|
||||
KclValue::Number { value, .. } => crate::try_f64_to_i64(*value),
|
||||
_ => None,
|
||||
}
|
||||
@ -455,8 +438,10 @@ impl KclValue {
|
||||
}
|
||||
|
||||
pub fn as_f64(&self) -> Option<f64> {
|
||||
if let KclValue::Number { value, .. } = &self {
|
||||
if let KclValue::Number { value, meta: _ } = &self {
|
||||
Some(*value)
|
||||
} else if let KclValue::Int { value, meta: _ } = &self {
|
||||
Some(*value as f64)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@ -621,73 +606,6 @@ impl KclValue {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum NumericType {
|
||||
// Specified by the user (directly or indirectly)
|
||||
Known(UnitType),
|
||||
// Unspecified, using defaults
|
||||
Default { len: UnitLen, angle: UnitAngle },
|
||||
// Exceeded the ability of the type system to track.
|
||||
Unknown,
|
||||
// Type info has been explicitly cast away.
|
||||
Any,
|
||||
}
|
||||
|
||||
impl NumericType {
|
||||
pub fn count() -> Self {
|
||||
NumericType::Known(UnitType::Count)
|
||||
}
|
||||
|
||||
pub fn combine(self, other: &NumericType) -> NumericType {
|
||||
if &self == other {
|
||||
self
|
||||
} else {
|
||||
NumericType::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_parsed(suffix: NumericSuffix, settings: &super::MetaSettings) -> Self {
|
||||
match suffix {
|
||||
NumericSuffix::None => NumericType::Default {
|
||||
len: settings.default_length_units,
|
||||
angle: settings.default_angle_units,
|
||||
},
|
||||
NumericSuffix::Count => NumericType::Known(UnitType::Count),
|
||||
NumericSuffix::Mm => NumericType::Known(UnitType::Length(UnitLen::Mm)),
|
||||
NumericSuffix::Cm => NumericType::Known(UnitType::Length(UnitLen::Cm)),
|
||||
NumericSuffix::M => NumericType::Known(UnitType::Length(UnitLen::M)),
|
||||
NumericSuffix::Inch => NumericType::Known(UnitType::Length(UnitLen::Inches)),
|
||||
NumericSuffix::Ft => NumericType::Known(UnitType::Length(UnitLen::Feet)),
|
||||
NumericSuffix::Yd => NumericType::Known(UnitType::Length(UnitLen::Yards)),
|
||||
NumericSuffix::Deg => NumericType::Known(UnitType::Angle(UnitAngle::Degrees)),
|
||||
NumericSuffix::Rad => NumericType::Known(UnitType::Angle(UnitAngle::Radians)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UnitLen> for NumericType {
|
||||
fn from(value: UnitLen) -> Self {
|
||||
NumericType::Known(UnitType::Length(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UnitAngle> for NumericType {
|
||||
fn from(value: UnitAngle) -> Self {
|
||||
NumericType::Known(UnitType::Angle(value))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum UnitType {
|
||||
Count,
|
||||
Length(UnitLen),
|
||||
Angle(UnitAngle),
|
||||
}
|
||||
|
||||
// TODO called UnitLen so as not to clash with UnitLength in settings)
|
||||
#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)]
|
||||
#[ts(export)]
|
||||
|
@ -845,8 +845,6 @@ mod env {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::execution::kcl_value::NumericType;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn sr() -> SourceRange {
|
||||
@ -854,9 +852,8 @@ mod test {
|
||||
}
|
||||
|
||||
fn val(value: i64) -> KclValue {
|
||||
KclValue::Number {
|
||||
value: value as f64,
|
||||
ty: NumericType::count(),
|
||||
KclValue::Int {
|
||||
value,
|
||||
meta: Vec::new(),
|
||||
}
|
||||
}
|
||||
@ -864,14 +861,14 @@ mod test {
|
||||
#[track_caller]
|
||||
fn assert_get(mem: &ProgramMemory, key: &str, n: i64) {
|
||||
match mem.get(key, sr()).unwrap() {
|
||||
KclValue::Number { value, .. } => assert_eq!(*value as i64, n),
|
||||
KclValue::Int { value, .. } => assert_eq!(*value, n),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_small_number(value: &KclValue) -> Option<i64> {
|
||||
fn expect_int(value: &KclValue) -> Option<i64> {
|
||||
match value {
|
||||
KclValue::Number { value, .. } if value > &0.0 && value < &10.0 => Some(*value as i64),
|
||||
KclValue::Int { value, .. } => Some(*value),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@ -879,7 +876,7 @@ mod test {
|
||||
#[track_caller]
|
||||
fn assert_get_from(mem: &ProgramMemory, key: &str, n: i64, snapshot: EnvironmentRef) {
|
||||
match mem.get_from(key, snapshot, sr()).unwrap() {
|
||||
KclValue::Number { value, .. } => assert_eq!(*value as i64, n),
|
||||
KclValue::Int { value, .. } => assert_eq!(*value, n),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
@ -1130,7 +1127,7 @@ mod test {
|
||||
assert_get_from(mem, "b", 3, sn3);
|
||||
assert_get_from(mem, "b", 4, sn4);
|
||||
|
||||
let vals: Vec<_> = mem.walk_call_stack().filter_map(expect_small_number).collect();
|
||||
let vals: Vec<_> = mem.walk_call_stack().filter_map(expect_int).collect();
|
||||
let expected = [6, 1, 3, 1, 7];
|
||||
assert_eq!(vals, expected);
|
||||
|
||||
@ -1139,7 +1136,7 @@ mod test {
|
||||
mem.get_from("b", sn1, sr()).unwrap_err();
|
||||
assert_get_from(mem, "b", 3, sn2);
|
||||
|
||||
let vals: Vec<_> = mem.walk_call_stack().filter_map(expect_small_number).collect();
|
||||
let vals: Vec<_> = mem.walk_call_stack().filter_map(expect_int).collect();
|
||||
let expected = [1, 7];
|
||||
assert_eq!(vals, expected);
|
||||
|
||||
|
@ -484,8 +484,8 @@ impl ExecutorContext {
|
||||
}
|
||||
|
||||
/// Returns true if we should not send engine commands for any reason.
|
||||
pub async fn no_engine_commands(&self) -> bool {
|
||||
self.is_mock() || self.engine.execution_kind().await.is_isolated()
|
||||
pub fn no_engine_commands(&self) -> bool {
|
||||
self.is_mock() || self.engine.execution_kind().is_isolated()
|
||||
}
|
||||
|
||||
pub async fn send_clear_scene(
|
||||
@ -713,7 +713,7 @@ impl ExecutorContext {
|
||||
"Post interpretation KCL memory stats: {:#?}",
|
||||
exec_state.memory().stats
|
||||
));
|
||||
let session_data = self.engine.get_session_data().await;
|
||||
let session_data = self.engine.get_session_data();
|
||||
Ok(session_data)
|
||||
}
|
||||
|
||||
@ -734,11 +734,8 @@ impl ExecutorContext {
|
||||
exec_state
|
||||
.global
|
||||
.artifact_commands
|
||||
.extend(self.engine.take_artifact_commands().await);
|
||||
exec_state
|
||||
.global
|
||||
.artifact_responses
|
||||
.extend(self.engine.take_responses().await);
|
||||
.extend(self.engine.take_artifact_commands());
|
||||
exec_state.global.artifact_responses.extend(self.engine.responses());
|
||||
// Build the artifact graph.
|
||||
match build_artifact_graph(
|
||||
&exec_state.global.artifact_commands,
|
||||
|
@ -1581,7 +1581,7 @@ pub struct CallExpression {
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase", tag = "type")]
|
||||
#[serde(tag = "type")]
|
||||
pub struct CallExpressionKw {
|
||||
pub callee: Node<Identifier>,
|
||||
pub unlabeled: Option<Expr>,
|
||||
@ -1591,9 +1591,6 @@ pub struct CallExpressionKw {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub digest: Option<Digest>,
|
||||
|
||||
#[serde(default, skip_serializing_if = "NonCodeMeta::is_empty")]
|
||||
pub non_code_meta: NonCodeMeta,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
@ -1717,7 +1714,6 @@ impl CallExpressionKw {
|
||||
unlabeled,
|
||||
arguments,
|
||||
digest: None,
|
||||
non_code_meta: Default::default(),
|
||||
}))
|
||||
}
|
||||
|
||||
@ -2084,6 +2080,30 @@ impl Literal {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Node<Literal>> for KclValue {
|
||||
fn from(literal: Node<Literal>) -> Self {
|
||||
let meta = vec![literal.metadata()];
|
||||
match literal.inner.value {
|
||||
LiteralValue::Number { value, .. } => KclValue::Number { value, meta },
|
||||
LiteralValue::String(value) => KclValue::String { value, meta },
|
||||
LiteralValue::Bool(value) => KclValue::Bool { value, meta },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Node<Literal>> for KclValue {
|
||||
fn from(literal: &Node<Literal>) -> Self {
|
||||
Self::from(literal.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&BoxNode<Literal>> for KclValue {
|
||||
fn from(literal: &BoxNode<Literal>) -> Self {
|
||||
let b: &Node<Literal> = literal;
|
||||
Self::from(b)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
@ -3068,7 +3088,20 @@ pub enum FnArgType {
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum DefaultParamVal {
|
||||
KclNone(KclNone),
|
||||
Literal(Node<Literal>),
|
||||
Literal(Literal),
|
||||
}
|
||||
|
||||
// TODO: This should actually take metadata.
|
||||
impl From<DefaultParamVal> for KclValue {
|
||||
fn from(v: DefaultParamVal) -> Self {
|
||||
match v {
|
||||
DefaultParamVal::KclNone(kcl_none) => Self::KclNone {
|
||||
value: kcl_none,
|
||||
meta: Default::default(),
|
||||
},
|
||||
DefaultParamVal::Literal(literal) => Self::from_literal(literal.value, Vec::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DefaultParamVal {
|
||||
|
@ -882,17 +882,6 @@ fn property_separator(i: &mut TokenSlice) -> PResult<()> {
|
||||
.parse_next(i)
|
||||
}
|
||||
|
||||
/// Match something that separates the labeled arguments of a fn call.
|
||||
fn labeled_arg_separator(i: &mut TokenSlice) -> PResult<()> {
|
||||
alt((
|
||||
// Normally you need a comma.
|
||||
comma_sep,
|
||||
// But, if the argument list is ending, no need for a comma.
|
||||
peek(preceded(opt(whitespace), close_paren)).void(),
|
||||
))
|
||||
.parse_next(i)
|
||||
}
|
||||
|
||||
/// Parse a KCL object value.
|
||||
pub(crate) fn object(i: &mut TokenSlice) -> PResult<Node<ObjectExpression>> {
|
||||
let open = open_brace(i)?;
|
||||
@ -2507,6 +2496,14 @@ fn labeled_argument(i: &mut TokenSlice) -> PResult<LabeledArg> {
|
||||
.parse_next(i)
|
||||
}
|
||||
|
||||
/// Arguments are passed into a function,
|
||||
/// preceded by the name of the parameter (the label).
|
||||
fn labeled_arguments(i: &mut TokenSlice) -> PResult<Vec<LabeledArg>> {
|
||||
separated(0.., labeled_argument, comma_sep)
|
||||
.context(expected("function arguments"))
|
||||
.parse_next(i)
|
||||
}
|
||||
|
||||
/// A type of a function argument.
|
||||
/// This can be:
|
||||
/// - a primitive type, e.g. 'number' or 'string' or 'bool'
|
||||
@ -2582,7 +2579,7 @@ fn parameter(i: &mut TokenSlice) -> PResult<ParamDescription> {
|
||||
arg_name,
|
||||
type_,
|
||||
default_value: match (question_mark.is_some(), default_literal) {
|
||||
(true, Some(lit)) => Some(DefaultParamVal::Literal(*lit)),
|
||||
(true, Some(lit)) => Some(DefaultParamVal::Literal(lit.inner)),
|
||||
(true, None) => Some(DefaultParamVal::none()),
|
||||
(false, None) => None,
|
||||
(false, Some(lit)) => {
|
||||
@ -2786,28 +2783,7 @@ fn fn_call_kw(i: &mut TokenSlice) -> PResult<Node<CallExpressionKw>> {
|
||||
ignore_whitespace(i);
|
||||
|
||||
let initial_unlabeled_arg = opt((expression, comma, opt(whitespace)).map(|(arg, _, _)| arg)).parse_next(i)?;
|
||||
let args: Vec<_> = repeat(
|
||||
0..,
|
||||
alt((
|
||||
terminated(non_code_node.map(NonCodeOr::NonCode), whitespace),
|
||||
terminated(labeled_argument, labeled_arg_separator).map(NonCodeOr::Code),
|
||||
)),
|
||||
)
|
||||
.parse_next(i)?;
|
||||
let (args, non_code_nodes): (Vec<_>, BTreeMap<usize, _>) = args.into_iter().enumerate().fold(
|
||||
(Vec::new(), BTreeMap::new()),
|
||||
|(mut args, mut non_code_nodes), (i, e)| {
|
||||
match e {
|
||||
NonCodeOr::NonCode(x) => {
|
||||
non_code_nodes.insert(i, vec![x]);
|
||||
}
|
||||
NonCodeOr::Code(x) => {
|
||||
args.push(x);
|
||||
}
|
||||
}
|
||||
(args, non_code_nodes)
|
||||
},
|
||||
);
|
||||
let args = labeled_arguments(i)?;
|
||||
if let Some(std_fn) = crate::std::get_stdlib_fn(&fn_name.name) {
|
||||
let just_args: Vec<_> = args.iter().collect();
|
||||
typecheck_all_kw(std_fn, &just_args)?;
|
||||
@ -2816,10 +2792,6 @@ fn fn_call_kw(i: &mut TokenSlice) -> PResult<Node<CallExpressionKw>> {
|
||||
opt(comma_sep).parse_next(i)?;
|
||||
let end = close_paren.parse_next(i)?.end;
|
||||
|
||||
let non_code_meta = NonCodeMeta {
|
||||
non_code_nodes,
|
||||
..Default::default()
|
||||
};
|
||||
Ok(Node {
|
||||
start: fn_name.start,
|
||||
end,
|
||||
@ -2829,7 +2801,6 @@ fn fn_call_kw(i: &mut TokenSlice) -> PResult<Node<CallExpressionKw>> {
|
||||
unlabeled: initial_unlabeled_arg,
|
||||
arguments: args,
|
||||
digest: None,
|
||||
non_code_meta,
|
||||
},
|
||||
outer_attrs: Vec::new(),
|
||||
})
|
||||
@ -4419,6 +4390,14 @@ let myBox = box([0,0], -3, -16, -10)
|
||||
crate::parsing::top_level_parse(some_program_string).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arg_labels() {
|
||||
let input = r#"length: 3"#;
|
||||
let module_id = ModuleId::default();
|
||||
let tokens = crate::parsing::token::lex(input, module_id).unwrap();
|
||||
super::labeled_arguments(&mut tokens.as_slice()).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kw_fn() {
|
||||
for input in ["val = foo(x, y = z)", "val = foo(y = z)"] {
|
||||
@ -4900,22 +4879,6 @@ my14 = 4 ^ 2 - 3 ^ 2 * 2
|
||||
r#"fn foo(x?: number = 2) { return 1 }"#
|
||||
);
|
||||
snapshot_test!(kw_function_call_in_pipe, r#"val = 1 |> f(arg = x)"#);
|
||||
snapshot_test!(
|
||||
kw_function_call_multiline,
|
||||
r#"val = f(
|
||||
arg = x,
|
||||
foo = x,
|
||||
bar = x,
|
||||
)"#
|
||||
);
|
||||
snapshot_test!(
|
||||
kw_function_call_multiline_with_comments,
|
||||
r#"val = f(
|
||||
arg = x,
|
||||
// foo = x,
|
||||
bar = x,
|
||||
)"#
|
||||
);
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
|
@ -1,86 +0,0 @@
|
||||
---
|
||||
source: kcl/src/parsing/parser.rs
|
||||
expression: actual
|
||||
snapshot_kind: text
|
||||
---
|
||||
{
|
||||
"body": [
|
||||
{
|
||||
"declaration": {
|
||||
"end": 87,
|
||||
"id": {
|
||||
"end": 3,
|
||||
"name": "val",
|
||||
"start": 0,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"init": {
|
||||
"arguments": [
|
||||
{
|
||||
"type": "LabeledArg",
|
||||
"label": {
|
||||
"type": "Identifier",
|
||||
"name": "arg"
|
||||
},
|
||||
"arg": {
|
||||
"end": 29,
|
||||
"name": "x",
|
||||
"start": 28,
|
||||
"type": "Identifier",
|
||||
"type": "Identifier"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "LabeledArg",
|
||||
"label": {
|
||||
"type": "Identifier",
|
||||
"name": "foo"
|
||||
},
|
||||
"arg": {
|
||||
"end": 51,
|
||||
"name": "x",
|
||||
"start": 50,
|
||||
"type": "Identifier",
|
||||
"type": "Identifier"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "LabeledArg",
|
||||
"label": {
|
||||
"type": "Identifier",
|
||||
"name": "bar"
|
||||
},
|
||||
"arg": {
|
||||
"end": 73,
|
||||
"name": "x",
|
||||
"start": 72,
|
||||
"type": "Identifier",
|
||||
"type": "Identifier"
|
||||
}
|
||||
}
|
||||
],
|
||||
"callee": {
|
||||
"end": 7,
|
||||
"name": "f",
|
||||
"start": 6,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"end": 87,
|
||||
"start": 6,
|
||||
"type": "CallExpressionKw",
|
||||
"type": "CallExpressionKw",
|
||||
"unlabeled": null
|
||||
},
|
||||
"start": 0,
|
||||
"type": "VariableDeclarator"
|
||||
},
|
||||
"end": 87,
|
||||
"kind": "const",
|
||||
"start": 0,
|
||||
"type": "VariableDeclaration",
|
||||
"type": "VariableDeclaration"
|
||||
}
|
||||
],
|
||||
"end": 87,
|
||||
"start": 0
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user