Compare commits

..

1 Commits

Author SHA1 Message Date
fcd4fc49d4 push, reduce, and map with kw args 2025-01-09 10:39:25 -05:00
53 changed files with 1386 additions and 2018 deletions

View File

@ -5,24 +5,16 @@
},
"plugins": [
"css-modules",
"jest",
"react",
"suggest-no-throw",
"@typescript-eslint"
],
"extends": [
"react-app",
"react-app/jest",
"plugin:css-modules/recommended"
],
"rules": {
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-misused-promises": "error",
"no-restricted-globals": [
"error",
{
"name": "isNaN",
"message": "Use Number.isNaN() instead."
}
],
"semi": [
"error",
"never"

View File

@ -21,7 +21,7 @@ if [[ ! -f "test-results/.last-run.json" ]]; then
fi
retry=1
max_retrys=5
max_retrys=4
# retry failed tests, doing our own retries because using inbuilt playwright retries causes connection issues
while [[ $retry -le $max_retrys ]]; do

File diff suppressed because one or more lines are too long

View File

@ -17,8 +17,8 @@ push(array: [KclValue], elem: KclValue) -> KclValue
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `array` | [`[KclValue]`](/docs/kcl/types/KclValue) | | Yes |
| `elem` | [`KclValue`](/docs/kcl/types/KclValue) | Any KCL value. | Yes |
| `array` | [`[KclValue]`](/docs/kcl/types/KclValue) | The array to push to. | Yes |
| `elem` | [`KclValue`](/docs/kcl/types/KclValue) | The element to push to the array. | Yes |
### Returns
@ -29,7 +29,7 @@ push(array: [KclValue], elem: KclValue) -> KclValue
```js
arr = [1, 2, 3]
new_arr = push(arr, 4)
new_arr = push(arr, elem = 4)
assertEqual(new_arr[3], 4, 0.00001, "4 was added to the end of the array")
```

View File

@ -17,9 +17,9 @@ reduce(array: [KclValue], start: KclValue, reduce_fn: FunctionParam) -> KclValue
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `array` | [`[KclValue]`](/docs/kcl/types/KclValue) | | Yes |
| `start` | [`KclValue`](/docs/kcl/types/KclValue) | Any KCL value. | Yes |
| `reduce_fn` | `FunctionParam` | | Yes |
| `array` | [`[KclValue]`](/docs/kcl/types/KclValue) | The array to reduce. | Yes |
| `start` | [`KclValue`](/docs/kcl/types/KclValue) | The starting value for the reduction. | Yes |
| `reduce_fn` | `FunctionParam` | The function to reduce the array with. | Yes |
### Returns
@ -38,7 +38,7 @@ fn add(a, b) {
// It uses the `reduce` function, to call the `add` function on every
// element of the `arr` parameter. The starting value is 0.
fn sum(arr) {
return reduce(arr, 0, add)
return reduce(arr, start = 0, reduce_fn = add)
}
/* The above is basically like this pseudo-code:
@ -61,7 +61,7 @@ assertEqual(sum([1, 2, 3]), 6, 0.00001, "1 + 2 + 3 summed is 6")
// an anonymous `add` function as its parameter, instead of declaring a
// named function outside.
arr = [1, 2, 3]
sum = reduce(arr, 0, fn(i, result_so_far) {
sum = reduce(arr, start = 0, reduce_fn = fn(i, result_so_far) {
return i + result_so_far
})
@ -85,7 +85,7 @@ fn decagon(radius) {
// Use a `reduce` to draw the remaining decagon sides.
// For each number in the array 1..10, run the given function,
// which takes a partially-sketched decagon and adds one more edge to it.
fullDecagon = reduce([1..10], startOfDecagonSketch, fn(i, partialDecagon) {
fullDecagon = reduce([1..10], start = startOfDecagonSketch, reduce_fn = fn(i, partialDecagon) {
// Draw one edge of the decagon.
x = cos(stepAngle * i) * radius
y = sin(stepAngle * i) * radius

View File

@ -106563,7 +106563,7 @@
"summary": "Apply a function to every element of a list.",
"description": "Given a list like `[a, b, c]`, and a function like `f`, returns `[f(a), f(b), f(c)]`",
"tags": [],
"keywordArguments": false,
"keywordArguments": true,
"args": [
{
"name": "array",
@ -108641,7 +108641,8 @@
}
},
"required": true,
"labelRequired": true
"description": "The array to map.",
"labelRequired": false
},
{
"name": "map_fn",
@ -110716,6 +110717,7 @@
}
},
"required": true,
"description": "The function to map the array with.",
"labelRequired": true
}
],
@ -112800,8 +112802,8 @@
"unpublished": false,
"deprecated": false,
"examples": [
"r = 10 // radius\nfn drawCircle(id) {\n return startSketchOn(\"XY\")\n |> circle({ center = [id * 2 * r, 0], radius = r }, %)\n}\n\n// Call `drawCircle`, passing in each element of the array.\n// The outputs from each `drawCircle` form a new array,\n// which is the return value from `map`.\ncircles = map([1..3], drawCircle)",
"r = 10 // radius\n// Call `map`, using an anonymous function instead of a named one.\ncircles = map([1..3], fn(id) {\n return startSketchOn(\"XY\")\n |> circle({ center = [id * 2 * r, 0], radius = r }, %)\n})"
"r = 10 // radius\nfn drawCircle(id) {\n return startSketchOn(\"XY\")\n |> circle({ center = [id * 2 * r, 0], radius = r }, %)\n}\n\n// Call `drawCircle`, passing in each element of the array.\n// The outputs from each `drawCircle` form a new array,\n// which is the return value from `map`.\ncircles = map([1..3], map_fn = drawCircle)",
"r = 10 // radius\n// Call `map`, using an anonymous function instead of a named one.\ncircles = map([1..3], map_fn = fn(id) {\n return startSketchOn(\"XY\")\n |> circle({ center = [id * 2 * r, 0], radius = r }, %)\n})"
]
},
{
@ -146967,7 +146969,7 @@
"summary": "Append an element to the end of an array.",
"description": "Returns a new array with the element appended.",
"tags": [],
"keywordArguments": false,
"keywordArguments": true,
"args": [
{
"name": "array",
@ -149045,7 +149047,8 @@
}
},
"required": true,
"labelRequired": true
"description": "The array to push to.",
"labelRequired": false
},
{
"name": "elem",
@ -151802,6 +151805,7 @@
}
},
"required": true,
"description": "The element to push to the array.",
"labelRequired": true
}
],
@ -154565,7 +154569,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"arr = [1, 2, 3]\nnew_arr = push(arr, 4)\nassertEqual(new_arr[3], 4, 0.00001, \"4 was added to the end of the array\")"
"arr = [1, 2, 3]\nnew_arr = push(arr, elem = 4)\nassertEqual(new_arr[3], 4, 0.00001, \"4 was added to the end of the array\")"
]
},
{
@ -154573,7 +154577,7 @@
"summary": "Take a starting value. Then, for each element of an array, calculate the next value,",
"description": "using the previous value and the element.",
"tags": [],
"keywordArguments": false,
"keywordArguments": true,
"args": [
{
"name": "array",
@ -156651,7 +156655,8 @@
}
},
"required": true,
"labelRequired": true
"description": "The array to reduce.",
"labelRequired": false
},
{
"name": "start",
@ -159408,6 +159413,7 @@
}
},
"required": true,
"description": "The starting value for the reduction.",
"labelRequired": true
},
{
@ -161483,6 +161489,7 @@
}
},
"required": true,
"description": "The function to reduce the array with.",
"labelRequired": true
}
],
@ -164246,9 +164253,9 @@
"unpublished": false,
"deprecated": false,
"examples": [
"// This function adds two numbers.\nfn add(a, b) {\n return a + b\n}\n\n// This function adds an array of numbers.\n// It uses the `reduce` function, to call the `add` function on every\n// element of the `arr` parameter. The starting value is 0.\nfn sum(arr) {\n return reduce(arr, 0, add)\n}\n\n/* The above is basically like this pseudo-code:\nfn sum(arr):\n sumSoFar = 0\n for i in arr:\n sumSoFar = add(sumSoFar, i)\n return sumSoFar */\n\n\n// We use `assertEqual` to check that our `sum` function gives the\n// expected result. It's good to check your work!\nassertEqual(sum([1, 2, 3]), 6, 0.00001, \"1 + 2 + 3 summed is 6\")",
"// This example works just like the previous example above, but it uses\n// an anonymous `add` function as its parameter, instead of declaring a\n// named function outside.\narr = [1, 2, 3]\nsum = reduce(arr, 0, fn(i, result_so_far) {\n return i + result_so_far\n})\n\n// We use `assertEqual` to check that our `sum` function gives the\n// expected result. It's good to check your work!\nassertEqual(sum, 6, 0.00001, \"1 + 2 + 3 summed is 6\")",
"// Declare a function that sketches a decagon.\nfn decagon(radius) {\n // Each side of the decagon is turned this many degrees from the previous angle.\n stepAngle = 1 / 10 * tau()\n\n // Start the decagon sketch at this point.\n startOfDecagonSketch = startSketchOn('XY')\n |> startProfileAt([cos(0) * radius, sin(0) * radius], %)\n\n // Use a `reduce` to draw the remaining decagon sides.\n // For each number in the array 1..10, run the given function,\n // which takes a partially-sketched decagon and adds one more edge to it.\n fullDecagon = reduce([1..10], startOfDecagonSketch, fn(i, partialDecagon) {\n // Draw one edge of the decagon.\n x = cos(stepAngle * i) * radius\n y = sin(stepAngle * i) * radius\n return lineTo([x, y], partialDecagon)\n })\n\n return fullDecagon\n}\n\n/* The `decagon` above is basically like this pseudo-code:\nfn decagon(radius):\n stepAngle = (1/10) * tau()\n plane = startSketchOn('XY')\n startOfDecagonSketch = startProfileAt([(cos(0)*radius), (sin(0) * radius)], plane)\n\n // Here's the reduce part.\n partialDecagon = startOfDecagonSketch\n for i in [1..10]:\n x = cos(stepAngle * i) * radius\n y = sin(stepAngle * i) * radius\n partialDecagon = lineTo([x, y], partialDecagon)\n fullDecagon = partialDecagon // it's now full\n return fullDecagon */\n\n\n// Use the `decagon` function declared above, to sketch a decagon with radius 5.\ndecagon(5.0)\n |> close(%)"
"// This function adds two numbers.\nfn add(a, b) {\n return a + b\n}\n\n// This function adds an array of numbers.\n// It uses the `reduce` function, to call the `add` function on every\n// element of the `arr` parameter. The starting value is 0.\nfn sum(arr) {\n return reduce(arr, start = 0, reduce_fn = add)\n}\n\n/* The above is basically like this pseudo-code:\nfn sum(arr):\n sumSoFar = 0\n for i in arr:\n sumSoFar = add(sumSoFar, i)\n return sumSoFar */\n\n\n// We use `assertEqual` to check that our `sum` function gives the\n// expected result. It's good to check your work!\nassertEqual(sum([1, 2, 3]), 6, 0.00001, \"1 + 2 + 3 summed is 6\")",
"// This example works just like the previous example above, but it uses\n// an anonymous `add` function as its parameter, instead of declaring a\n// named function outside.\narr = [1, 2, 3]\nsum = reduce(arr, start = 0, reduce_fn = fn(i, result_so_far) {\n return i + result_so_far\n})\n\n// We use `assertEqual` to check that our `sum` function gives the\n// expected result. It's good to check your work!\nassertEqual(sum, 6, 0.00001, \"1 + 2 + 3 summed is 6\")",
"// Declare a function that sketches a decagon.\nfn decagon(radius) {\n // Each side of the decagon is turned this many degrees from the previous angle.\n stepAngle = 1 / 10 * tau()\n\n // Start the decagon sketch at this point.\n startOfDecagonSketch = startSketchOn('XY')\n |> startProfileAt([cos(0) * radius, sin(0) * radius], %)\n\n // Use a `reduce` to draw the remaining decagon sides.\n // For each number in the array 1..10, run the given function,\n // which takes a partially-sketched decagon and adds one more edge to it.\n fullDecagon = reduce([1..10], start = startOfDecagonSketch, reduce_fn = fn(i, partialDecagon) {\n // Draw one edge of the decagon.\n x = cos(stepAngle * i) * radius\n y = sin(stepAngle * i) * radius\n return lineTo([x, y], partialDecagon)\n })\n\n return fullDecagon\n}\n\n/* The `decagon` above is basically like this pseudo-code:\nfn decagon(radius):\n stepAngle = (1/10) * tau()\n plane = startSketchOn('XY')\n startOfDecagonSketch = startProfileAt([(cos(0)*radius), (sin(0) * radius)], plane)\n\n // Here's the reduce part.\n partialDecagon = startOfDecagonSketch\n for i in [1..10]:\n x = cos(stepAngle * i) * radius\n y = sin(stepAngle * i) * radius\n partialDecagon = lineTo([x, y], partialDecagon)\n fullDecagon = partialDecagon // it's now full\n return fullDecagon */\n\n\n// Use the `decagon` function declared above, to sketch a decagon with radius 5.\ndecagon(5.0)\n |> close(%)"
]
},
{

View File

@ -14,7 +14,6 @@ export class ToolbarFixture {
extrudeButton!: Locator
loftButton!: Locator
sweepButton!: Locator
shellButton!: Locator
offsetPlaneButton!: Locator
startSketchBtn!: Locator
@ -41,7 +40,6 @@ export class ToolbarFixture {
this.page = page
this.extrudeButton = page.getByTestId('extrude')
this.loftButton = page.getByTestId('loft')
this.sweepButton = page.getByTestId('sweep')
this.shellButton = page.getByTestId('shell')
this.offsetPlaneButton = page.getByTestId('plane-offset')
this.startSketchBtn = page.getByTestId('sketch')

View File

@ -756,17 +756,6 @@ test(`Offset plane point-and-click`, async ({
})
await scene.expectPixelColor([74, 74, 74], testPoint, 15)
})
await test.step('Delete offset plane via feature tree selection', async () => {
await editor.closePane()
const operationButton = await toolbar.getFeatureTreeOperation(
'Offset Plane',
0
)
await operationButton.click({ button: 'left' })
await page.keyboard.press('Backspace')
await scene.expectPixelColor([50, 51, 96], testPoint, 15)
})
})
const loftPointAndClickCases = [
@ -862,173 +851,6 @@ loftPointAndClickCases.forEach(({ shouldPreselect }) => {
})
await scene.expectPixelColor([89, 89, 89], testPoint, 15)
})
await test.step('Delete loft via feature tree selection', async () => {
await editor.closePane()
const operationButton = await toolbar.getFeatureTreeOperation('Loft', 0)
await operationButton.click({ button: 'left' })
await page.keyboard.press('Backspace')
await scene.expectPixelColor([254, 254, 254], testPoint, 15)
})
})
})
// TODO: merge with above test. Right now we're not able to delete a loft
// right after creation via selection for some reason, so we go with a new instance
test('Loft and offset plane deletion via selection', async ({
context,
page,
homePage,
scene,
}) => {
const initialCode = `sketch001 = startSketchOn('XZ')
|> circle({ center = [0, 0], radius = 30 }, %)
plane001 = offsetPlane('XZ', 50)
sketch002 = startSketchOn(plane001)
|> circle({ center = [0, 0], radius = 20 }, %)
loft001 = loft([sketch001, sketch002])
`
await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode)
}, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene()
// One dumb hardcoded screen pixel value
const testPoint = { x: 575, y: 200 }
const [clickOnSketch1] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
const [clickOnSketch2] = scene.makeMouseHelpers(testPoint.x, testPoint.y + 80)
await test.step(`Delete loft`, async () => {
// Check for loft
await scene.expectPixelColor([89, 89, 89], testPoint, 15)
await clickOnSketch1()
await expect(page.locator('.cm-activeLine')).toHaveText(`
|> circle({ center = [0, 0], radius = 30 }, %)
`)
await page.keyboard.press('Backspace')
// Check for sketch 1
await scene.expectPixelColor([254, 254, 254], testPoint, 15)
})
await test.step('Delete sketch002', async () => {
await page.waitForTimeout(1000)
await clickOnSketch2()
await expect(page.locator('.cm-activeLine')).toHaveText(`
|> circle({ center = [0, 0], radius = 20 }, %)
`)
await page.keyboard.press('Backspace')
// Check for plane001
await scene.expectPixelColor([228, 228, 228], testPoint, 15)
})
await test.step('Delete plane001', async () => {
await page.waitForTimeout(1000)
await clickOnSketch2()
await expect(page.locator('.cm-activeLine')).toHaveText(`
plane001 = offsetPlane('XZ', 50)
`)
await page.keyboard.press('Backspace')
// Check for sketch 1
await scene.expectPixelColor([254, 254, 254], testPoint, 15)
})
})
test(`Sweep point-and-click`, async ({
context,
page,
homePage,
scene,
editor,
toolbar,
cmdBar,
}) => {
const initialCode = `sketch001 = startSketchOn('YZ')
|> circle({
center = [0, 0],
radius = 500
}, %)
sketch002 = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> xLine(-500, %)
|> tangentialArcTo([-2000, 500], %)
`
await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode)
}, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene()
await scene.waitForExecutionDone()
// One dumb hardcoded screen pixel value
const testPoint = { x: 700, y: 250 }
const [clickOnSketch1] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
const [clickOnSketch2] = scene.makeMouseHelpers(testPoint.x - 50, testPoint.y)
const sweepDeclaration = 'sweep001 = sweep({ path = sketch002 }, sketch001)'
await test.step(`Look for sketch001`, async () => {
await toolbar.closePane('code')
await scene.expectPixelColor([53, 53, 53], testPoint, 15)
})
await test.step(`Go through the command bar flow`, async () => {
await toolbar.sweepButton.click()
await cmdBar.expectState({
commandName: 'Sweep',
currentArgKey: 'profile',
currentArgValue: '',
headerArguments: {
Path: '',
Profile: '',
},
highlightedHeaderArg: 'profile',
stage: 'arguments',
})
await clickOnSketch1()
await cmdBar.expectState({
commandName: 'Sweep',
currentArgKey: 'path',
currentArgValue: '',
headerArguments: {
Path: '',
Profile: '1 face',
},
highlightedHeaderArg: 'path',
stage: 'arguments',
})
await clickOnSketch2()
await cmdBar.expectState({
commandName: 'Sweep',
headerArguments: {
Path: '1 face',
Profile: '1 face',
},
stage: 'review',
})
await cmdBar.progressCmdBar()
})
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
await scene.expectPixelColor([135, 64, 73], testPoint, 15)
await toolbar.openPane('code')
await editor.expectEditor.toContain(sweepDeclaration)
await editor.expectState({
diagnostics: [],
activeLines: [sweepDeclaration],
highlightedCode: '',
})
await toolbar.closePane('code')
})
await test.step('Delete sweep via feature tree selection', async () => {
await toolbar.openPane('feature-tree')
await page.waitForTimeout(500)
const operationButton = await toolbar.getFeatureTreeOperation('Sweep', 0)
await operationButton.click({ button: 'left' })
await page.keyboard.press('Backspace')
await page.waitForTimeout(500)
await toolbar.closePane('feature-tree')
await scene.expectPixelColor([53, 53, 53], testPoint, 15)
})
})
@ -1208,104 +1030,4 @@ extrude001 = extrude(40, sketch001)
})
await scene.expectPixelColor([49, 49, 49], testPoint, 15)
})
await test.step('Delete shell via feature tree selection', async () => {
await editor.closePane()
const operationButton = await toolbar.getFeatureTreeOperation('Shell', 0)
await operationButton.click({ button: 'left' })
await page.keyboard.press('Backspace')
await scene.expectPixelColor([99, 99, 99], testPoint, 15)
})
})
const shellSketchOnFacesCases = [
`sketch001 = startSketchOn('XZ')
|> circle({ center = [0, 0], radius = 100 }, %)
|> extrude(100, %)
sketch002 = startSketchOn(sketch001, 'END')
|> circle({ center = [0, 0], radius = 50 }, %)
|> extrude(50, %)
`,
`sketch001 = startSketchOn('XZ')
|> circle({ center = [0, 0], radius = 100 }, %)
extrude001 = extrude(100, sketch001)
sketch002 = startSketchOn(extrude001, 'END')
|> circle({ center = [0, 0], radius = 50 }, %)
extrude002 = extrude(50, sketch002)
`,
]
shellSketchOnFacesCases.forEach((initialCode, index) => {
const hasExtrudesInPipe = index === 0
test(`Shell point-and-click sketch on face (extrudes in pipes: ${hasExtrudesInPipe})`, async ({
context,
page,
homePage,
scene,
editor,
toolbar,
cmdBar,
}) => {
await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode)
}, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene()
await scene.waitForExecutionDone()
// One dumb hardcoded screen pixel value
const testPoint = { x: 550, y: 295 }
const [clickOnCap] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
const shellDeclaration = `shell001 = shell({ faces = ['end'], thickness = 5 }, ${
hasExtrudesInPipe ? 'sketch002' : 'extrude002'
})`
await test.step(`Look for the grey of the shape`, async () => {
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 () => {
await toolbar.shellButton.click()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'selection',
currentArgValue: '',
headerArguments: {
Selection: '',
Thickness: '',
},
highlightedHeaderArg: 'selection',
commandName: 'Shell',
})
await clickOnCap()
await page.waitForTimeout(500)
await cmdBar.progressCmdBar()
await page.waitForTimeout(500)
await cmdBar.progressCmdBar()
await page.waitForTimeout(500)
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Selection: '1 cap',
Thickness: '5',
},
commandName: 'Shell',
})
await cmdBar.progressCmdBar()
})
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
await toolbar.openPane('code')
await editor.expectEditor.toContain(shellDeclaration)
await editor.expectState({
diagnostics: [],
activeLines: [shellDeclaration],
highlightedCode: '',
})
await toolbar.closePane('code')
await scene.expectPixelColor([73, 73, 73], testPoint, 15)
})
})
})

18
flake.lock generated
View File

@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1736320768,
"narHash": "sha256-nIYdTAiKIGnFNugbomgBJR+Xv5F1ZQU+HfaBqJKroC0=",
"lastModified": 1721933792,
"narHash": "sha256-zYVwABlQnxpbaHMfX6Wt9jhyQstFYwN2XjleOJV3VVg=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "4bc9c909d9ac828a039f288cf872d16d38185db8",
"rev": "2122a9b35b35719ad9a395fe783eabb092df01b1",
"type": "github"
},
"original": {
@ -18,11 +18,11 @@
},
"nixpkgs_2": {
"locked": {
"lastModified": 1728538411,
"narHash": "sha256-f0SBJz1eZ2yOuKUr5CA9BHULGXVSn6miBuUWdTyhUhU=",
"lastModified": 1718428119,
"narHash": "sha256-WdWDpNaq6u1IPtxtYHHWpl5BmabtpmLnMAx0RdJ/vo8=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "b69de56fac8c2b6f8fd27f2eca01dcda8e0a4221",
"rev": "e6cea36f83499eb4e9cd184c8a8e823296b50ad5",
"type": "github"
},
"original": {
@ -43,11 +43,11 @@
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1736476219,
"narHash": "sha256-+qyv3QqdZCdZ3cSO/cbpEY6tntyYjfe1bB12mdpNFaY=",
"lastModified": 1721960387,
"narHash": "sha256-o21ax+745ETGXrcgc/yUuLw1SI77ymp3xEpJt+w/kks=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "de30cc5963da22e9742bbbbb9a3344570ed237b9",
"rev": "9cbf831c5b20a53354fc12758abd05966f9f1699",
"type": "github"
},
"original": {

View File

@ -26,7 +26,7 @@
"@fortawesome/react-fontawesome": "^0.2.0",
"@headlessui/react": "^1.7.19",
"@headlessui/tailwindcss": "^0.2.0",
"@kittycad/lib": "2.0.13",
"@kittycad/lib": "2.0.12",
"@lezer/highlight": "^1.2.1",
"@lezer/lr": "^1.4.1",
"@react-hook/resize-observer": "^2.0.1",
@ -91,8 +91,8 @@
"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",
"remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\" || echo \"sed for both mac and linux\"",
"wasm-prep": "rimraf src/wasm-lib/pkg && mkdirp src/wasm-lib/pkg && rimraf src/wasm-lib/kcl/bindings",
"lint-fix": "eslint --fix --ext .ts --ext .tsx src e2e packages/codemirror-lsp-client/src",
"lint": "eslint --max-warnings 0 --ext .ts --ext .tsx src e2e packages/codemirror-lsp-client/src",
"lint-fix": "eslint --fix src e2e packages/codemirror-lsp-client",
"lint": "eslint --max-warnings 0 src e2e packages/codemirror-lsp-client",
"files:set-version": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json",
"files:set-notes": "./scripts/set-files-notes.sh",
"files:flip-to-nightly": "./scripts/flip-files-to-nightly.sh",
@ -171,6 +171,8 @@
"@types/uuid": "^9.0.8",
"@types/wicg-file-system-access": "^2023.10.5",
"@types/ws": "^8.5.13",
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"@vitejs/plugin-react": "^4.3.0",
"@vitest/web-worker": "^1.5.0",
"@xstate/cli": "^0.5.17",
@ -180,10 +182,9 @@
"electron-builder": "24.13.3",
"electron-notarize": "1.2.2",
"eslint": "^8.0.1",
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-css-modules": "^2.12.0",
"eslint-plugin-import": "^2.30.0",
"eslint-plugin-jest": "^28.10.0",
"eslint-plugin-react": "^7.37.3",
"eslint-plugin-suggest-no-throw": "^1.0.0",
"happy-dom": "^16.3.0",
"http-server": "^14.1.1",
@ -199,7 +200,6 @@
"tailwindcss": "^3.4.1",
"ts-node": "^10.0.0",
"typescript": "^5.7.2",
"typescript-eslint": "^8.19.1",
"vite": "^5.4.6",
"vite-plugin-package-version": "^1.1.0",
"vite-tsconfig-paths": "^4.3.2",

View File

@ -42,7 +42,7 @@ export default class StreamDemuxer extends Queue<Uint8Array> {
// try to parse the content-length from the headers
const length = parseInt(match[1])
if (Number.isNaN(length))
if (isNaN(length))
return Promise.reject(new Error('invalid content length'))
// slice the headers since we now have the content length

View File

@ -32,9 +32,10 @@ export default defineConfig({
},
projects: [
{
name: 'chromium',
name: 'Google Chrome',
use: {
...devices['Desktop Chrome'],
channel: 'chrome',
contextOptions: {
/* Chromium is the only one with these permission types */
permissions: ['clipboard-write', 'clipboard-read'],

View File

@ -157,10 +157,12 @@ export const ModelingMachineProvider = ({
'enable copilot': () => {
editorManager.setCopilotEnabled(true)
},
'sketch exit execute': ({ context: { store } }) => {
// TODO: Remove this async callback. For some reason eslint wouldn't
// let me disable @typescript-eslint/no-misused-promises for the line.
;(async () => {
// tsc reports this typing as perfectly fine, but eslint is complaining.
// It's actually nonsensical, so I'm quieting.
// eslint-disable-next-line @typescript-eslint/no-misused-promises
'sketch exit execute': async ({
context: { store },
}): Promise<void> => {
// When cancelling the sketch mode we should disable sketch mode within the engine.
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
@ -188,7 +190,6 @@ export const ModelingMachineProvider = ({
})
})
.catch(reportRejection)
})().catch(reportRejection)
},
'Set mouse state': assign(({ context, event }) => {
if (event.type !== 'Set mouse state') return {}
@ -270,7 +271,6 @@ export const ModelingMachineProvider = ({
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_center_to_selection',
camera_movement: 'vantage',
},
})
.catch(reportRejection)

View File

@ -374,37 +374,6 @@ export function loftSketches(
}
}
export function addSweep(
node: Node<Program>,
profileDeclarator: VariableDeclarator,
pathDeclarator: VariableDeclarator
): {
modifiedAst: Node<Program>
pathToNode: PathToNode
} {
const modifiedAst = structuredClone(node)
const name = findUniqueName(node, KCL_DEFAULT_CONSTANT_PREFIXES.SWEEP)
const sweep = createCallExpressionStdLib('sweep', [
createObjectExpression({ path: createIdentifier(pathDeclarator.id.name) }),
createIdentifier(profileDeclarator.id.name),
])
const declaration = createVariableDeclaration(name, sweep)
modifiedAst.body.push(declaration)
const pathToNode: PathToNode = [
['body', ''],
[modifiedAst.body.length - 1, 'index'],
['declaration', 'VariableDeclaration'],
['init', 'VariableDeclarator'],
['arguments', 'CallExpression'],
[0, 'index'],
]
return {
modifiedAst,
pathToNode,
}
}
export function revolveSketch(
node: Node<Program>,
pathToNode: PathToNode,
@ -1180,17 +1149,11 @@ export async function deleteFromSelection(
((selection?.artifact?.type === 'wall' ||
selection?.artifact?.type === 'cap') &&
varDec.node.init.type === 'PipeExpression') ||
selection.artifact?.type === 'sweep' ||
selection.artifact?.type === 'plane' ||
!selection.artifact // aka expected to be a shell at this point
selection.artifact?.type === 'sweep'
) {
let extrudeNameToDelete = ''
let pathToNode: PathToNode | null = null
if (
selection.artifact &&
selection.artifact.type !== 'sweep' &&
selection.artifact.type !== 'plane'
) {
if (selection.artifact?.type !== 'sweep') {
const varDecName = varDec.node.id.name
traverse(astClone, {
enter: (node, path) => {
@ -1206,17 +1169,6 @@ export async function deleteFromSelection(
pathToNode = path
extrudeNameToDelete = dec.id.name
}
if (
dec.init.type === 'CallExpression' &&
dec.init.callee.name === 'loft' &&
dec.init.arguments?.[0].type === 'ArrayExpression' &&
dec.init.arguments?.[0].elements.some(
(a) => a.type === 'Identifier' && a.name === varDecName
)
) {
pathToNode = path
extrudeNameToDelete = dec.id.name
}
}
},
})

View File

@ -29,9 +29,7 @@ export function revolveSketch(
pathToSketchNode: PathToNode,
shouldPipe = false,
angle: Expr = createLiteral(4),
axisOrEdge: string,
axis: string,
edge: Selections
axis: Selections
):
| {
modifiedAst: Node<Program>
@ -43,13 +41,12 @@ export function revolveSketch(
const sketchNode = getNodeFromPath(clonedAst, pathToSketchNode)
if (err(sketchNode)) return sketchNode
let generatedAxis
if (axisOrEdge === 'Edge') {
// testing code
const pathToAxisSelection = getNodePathFromSourceRange(
clonedAst,
edge.graphSelections[0]?.codeRef.range
axis.graphSelections[0]?.codeRef.range
)
const lineNode = getNodeFromPath<CallExpression>(
clonedAst,
pathToAxisSelection,
@ -57,6 +54,10 @@ export function revolveSketch(
)
if (err(lineNode)) return lineNode
// TODO Kevin: What if |> close(%)?
// TODO Kevin: What if opposite edge
// TODO Kevin: What if the edge isn't planar to the sketch?
// TODO Kevin: add a tag.
const tagResult = mutateAstWithTagForSketchSegment(
clonedAst,
pathToAxisSelection
@ -65,12 +66,6 @@ export function revolveSketch(
// Have the tag whether it is already created or a new one is generated
if (err(tagResult)) return tagResult
const { tag } = tagResult
const axisSelection = edge?.graphSelections[0]?.artifact
if (!axisSelection) return new Error('Generated axis selection is missing.')
generatedAxis = getEdgeTagCall(tag, axisSelection)
} else {
generatedAxis = createLiteral(axis)
}
/* Original Code */
const { node: sketchExpression } = sketchNode
@ -96,12 +91,14 @@ export function revolveSketch(
shallowPath: sketchPathToDecleration,
} = sketchVariableDeclaratorNode
if (!generatedAxis) return new Error('Generated axis selection is missing.')
const axisSelection = axis?.graphSelections[0]?.artifact
if (!axisSelection) return new Error('Axis selection is missing.')
const revolveCall = createCallExpressionStdLib('revolve', [
createObjectExpression({
angle: angle,
axis: generatedAxis,
axis: getEdgeTagCall(tag, axisSelection),
}),
createIdentifier(sketchVariableDeclarator.id.name),
])

View File

@ -49,27 +49,17 @@ export function addShell({
return new Error("Couldn't find extrude")
}
pathToExtrudeNode = extrudeLookupResult.pathToExtrudeNode
// Get the sketch ref from the selection
// TODO: this assumes the segment is piped directly from the sketch, with no intermediate `VariableDeclarator` between.
// We must find a technique for these situations that is robust to intermediate declarations
const extrudeNode = getNodeFromPath<VariableDeclarator>(
const sketchNode = getNodeFromPath<VariableDeclarator>(
modifiedAst,
extrudeLookupResult.pathToExtrudeNode,
graphSelection.codeRef.pathToNode,
'VariableDeclarator'
)
const segmentNode = getNodeFromPath<VariableDeclarator>(
modifiedAst,
extrudeLookupResult.pathToSegmentNode,
'VariableDeclarator'
)
if (err(extrudeNode) || err(segmentNode)) {
return new Error("Couldn't find extrude")
}
if (extrudeNode.node.init.type === 'CallExpression') {
pathToExtrudeNode = extrudeLookupResult.pathToExtrudeNode
} else if (segmentNode.node.init.type === 'PipeExpression') {
pathToExtrudeNode = extrudeLookupResult.pathToSegmentNode
} else {
return new Error("Couldn't find extrude")
if (err(sketchNode)) {
return sketchNode
}
const selectedArtifact = graphSelection.artifact

View File

@ -77,7 +77,7 @@ interface SegmentArtifactRich extends BaseArtifact {
/** A Sweep is a more generic term for extrude, revolve, loft and sweep*/
interface SweepArtifact extends BaseArtifact {
type: 'sweep'
subType: 'extrusion' | 'revolve' | 'loft' | 'sweep'
subType: 'extrusion' | 'revolve'
pathId: string
surfaceIds: Array<string>
edgeIds: Array<string>
@ -85,7 +85,7 @@ interface SweepArtifact extends BaseArtifact {
}
interface SweepArtifactRich extends BaseArtifact {
type: 'sweep'
subType: 'extrusion' | 'revolve' | 'loft' | 'sweep'
subType: 'extrusion' | 'revolve'
path: PathArtifact
surfaces: Array<WallArtifact | CapArtifact>
edges: Array<SweepEdge>
@ -377,11 +377,7 @@ export function getArtifactsToUpdate({
})
}
return returnArr
} else if (
cmd.type === 'extrude' ||
cmd.type === 'revolve' ||
cmd.type === 'sweep'
) {
} else if (cmd.type === 'extrude' || cmd.type === 'revolve') {
const subType = cmd.type === 'extrude' ? 'extrusion' : cmd.type
returnArr.push({
id,
@ -402,33 +398,6 @@ export function getArtifactsToUpdate({
artifact: { ...path, sweepId: id },
})
return returnArr
} else if (
cmd.type === 'loft' &&
response.type === 'modeling' &&
response.data.modeling_response.type === 'loft'
) {
returnArr.push({
id,
artifact: {
type: 'sweep',
subType: 'loft',
id,
// TODO: make sure to revisit this choice, don't think it matters for now
pathId: cmd.section_ids[0],
surfaceIds: [],
edgeIds: [],
codeRef: { range, pathToNode },
},
})
for (const sectionId of cmd.section_ids) {
const path = getArtifact(sectionId)
if (path?.type === 'path')
returnArr.push({
id: sectionId,
artifact: { ...path, sweepId: id },
})
}
return returnArr
} else if (
cmd.type === 'solid3d_get_extrusion_face_info' &&
response?.type === 'modeling' &&

View File

@ -37,10 +37,6 @@ export type ModelingCommandSchema = {
// result: (typeof EXTRUSION_RESULTS)[number]
distance: KclCommandValue
}
Sweep: {
path: Selections
profile: Selections
}
Loft: {
selection: Selections
}
@ -51,9 +47,7 @@ export type ModelingCommandSchema = {
Revolve: {
selection: Selections
angle: KclCommandValue
axisOrEdge: string
axis: string
edge: Selections
axis: Selections
}
Fillet: {
// todo
@ -296,33 +290,6 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
},
},
},
Sweep: {
description:
'Create a 3D body by moving a sketch region along an arbitrary path.',
icon: 'sweep',
status: 'development',
needsReview: true,
args: {
profile: {
inputType: 'selection',
selectionTypes: ['solid2D'],
required: true,
skip: true,
multiple: false,
// TODO: add dry-run validation
warningMessage:
'The sweep workflow is new and under tested. Please break it and report issues.',
},
path: {
inputType: 'selection',
selectionTypes: ['segment', 'path'],
required: true,
skip: true,
multiple: false,
// TODO: add dry-run validation
},
},
},
Loft: {
description: 'Create a 3D body by blending between two or more sketches',
icon: 'loft',
@ -357,10 +324,10 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
},
},
},
// TODO: Update this configuration, copied from extrude for MVP of revolve, specifically the args.selection
Revolve: {
description: 'Create a 3D body by rotating a sketch region about an axis.',
icon: 'revolve',
status: 'development',
needsReview: true,
args: {
selection: {
@ -369,34 +336,9 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
multiple: false, // TODO: multiple selection
required: true,
skip: true,
warningMessage:
'The revolve workflow is new and under tested. Please break it and report issues.',
},
axisOrEdge: {
inputType: 'options',
required: true,
defaultValue: 'Axis',
options: [
{ name: 'Axis', isCurrent: true, value: 'Axis' },
{ name: 'Edge', isCurrent: false, value: 'Edge' },
],
},
axis: {
required: (commandContext) =>
['Axis'].includes(
commandContext.argumentsToSubmit.axisOrEdge as string
),
inputType: 'options',
options: [
{ name: 'X Axis', isCurrent: true, value: 'X' },
{ name: 'Y Axis', isCurrent: false, value: 'Y' },
],
},
edge: {
required: (commandContext) =>
['Edge'].includes(
commandContext.argumentsToSubmit.axisOrEdge as string
),
required: true,
inputType: 'selection',
selectionTypes: ['segment', 'sweepEdge', 'edgeCutEdge'],
multiple: false,

View File

@ -68,7 +68,7 @@ export const revolveAxisValidator = async ({
}
const sketchSelection = artifact.pathId
let edgeSelection = data.edge.graphSelections[0].artifact?.id
let edgeSelection = data.axis.graphSelections[0].artifact?.id
if (!sketchSelection) {
return 'Unable to revolve, sketch is missing'
@ -101,7 +101,7 @@ export const revolveAxisValidator = async ({
return true
} else {
// return error message for the toast
return 'Unable to revolve with selected edge'
return 'Unable to revolve with selected axis'
}
}

View File

@ -53,7 +53,6 @@ export const KCL_DEFAULT_CONSTANT_PREFIXES = {
SKETCH: 'sketch',
EXTRUDE: 'extrude',
LOFT: 'loft',
SWEEP: 'sweep',
SHELL: 'shell',
SEGMENT: 'seg',
REVOLVE: 'revolve',

View File

@ -15,7 +15,6 @@ import {
StateMachineCommandSetSchema,
} from './commandTypes'
import { DEV } from 'env'
import { IS_NIGHTLY_OR_DEBUG } from 'routes/Settings'
interface CreateMachineCommandProps<
T extends AnyStateMachine,
@ -85,7 +84,7 @@ export function createMachineCommand<
} else if ('status' in commandConfig) {
const { status } = commandConfig
if (status === 'inactive') return null
if (status === 'development' && !(DEV || IS_NIGHTLY_OR_DEBUG)) return null
if (status === 'development' && !DEV) return null
}
const icon = ('icon' in commandConfig && commandConfig.icon) || undefined

View File

@ -8,7 +8,6 @@ import {
modelingMachine,
pipeHasCircle,
} from 'machines/modelingMachine'
import { IS_NIGHTLY_OR_DEBUG } from 'routes/Settings'
import { EventFrom, StateFrom } from 'xstate'
export type ToolbarModeName = 'modeling' | 'sketching'
@ -104,7 +103,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
data: { name: 'Revolve', groupId: 'modeling' },
}),
icon: 'revolve',
status: DEV || IS_NIGHTLY_OR_DEBUG ? 'available' : 'kcl-only',
status: DEV ? 'available' : 'kcl-only',
title: 'Revolve',
hotkey: 'R',
description:
@ -119,21 +118,17 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
},
{
id: 'sweep',
onClick: ({ commandBarSend }) =>
commandBarSend({
type: 'Find and select command',
data: { name: 'Sweep', groupId: 'modeling' },
}),
onClick: () => console.error('Sweep not yet implemented'),
icon: 'sweep',
status: DEV || IS_NIGHTLY_OR_DEBUG ? 'available' : 'kcl-only',
status: 'unavailable',
title: 'Sweep',
hotkey: 'W',
description:
'Create a 3D body by moving a sketch region along an arbitrary path.',
links: [
{
label: 'KCL docs',
url: 'https://zoo.dev/docs/kcl/sweep',
label: 'GitHub discussion',
url: 'https://github.com/KittyCAD/modeling-app/discussions/498',
},
],
},
@ -166,7 +161,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
data: { name: 'Fillet', groupId: 'modeling' },
}),
icon: 'fillet3d',
status: DEV || IS_NIGHTLY_OR_DEBUG ? 'available' : 'kcl-only',
status: DEV ? 'available' : 'kcl-only',
title: 'Fillet',
hotkey: 'F',
description: 'Round the edges of a 3D solid.',

View File

@ -345,7 +345,7 @@ export function onDragNumberCalculation(text: string, e: MouseEvent) {
)
const newVal = roundOff(addition, precision)
if (Number.isNaN(newVal)) {
if (isNaN(newVal)) {
return
}

View File

@ -45,7 +45,6 @@ import {
import { revolveSketch } from 'lang/modifyAst/addRevolve'
import {
addOffsetPlane,
addSweep,
deleteFromSelection,
extrudeSketch,
loftSketches,
@ -267,7 +266,6 @@ export type ModelingMachineEvent =
| { type: 'Export'; data: ModelingCommandSchema['Export'] }
| { type: 'Make'; data: ModelingCommandSchema['Make'] }
| { type: 'Extrude'; data?: ModelingCommandSchema['Extrude'] }
| { type: 'Sweep'; data?: ModelingCommandSchema['Sweep'] }
| { type: 'Loft'; data?: ModelingCommandSchema['Loft'] }
| { type: 'Shell'; data?: ModelingCommandSchema['Shell'] }
| { type: 'Revolve'; data?: ModelingCommandSchema['Revolve'] }
@ -687,7 +685,7 @@ export const modelingMachine = setup({
if (event.type !== 'Revolve') return
;(async () => {
if (!event.data) return
const { selection, angle, axis, edge, axisOrEdge } = event.data
const { selection, angle, axis } = event.data
let ast = kclManager.ast
if (
'variableName' in angle &&
@ -712,9 +710,7 @@ export const modelingMachine = setup({
'variableName' in angle
? angle.variableIdentifierAst
: angle.valueAst,
axisOrEdge,
axis,
edge
axis
)
if (trap(revolveSketchRes)) return
const { modifiedAst, pathToRevolveArg } = revolveSketchRes
@ -1546,66 +1542,6 @@ export const modelingMachine = setup({
}
}
),
sweepAstMod: fromPromise(
async ({
input,
}: {
input: ModelingCommandSchema['Sweep'] | undefined
}) => {
if (!input) return new Error('No input provided')
// Extract inputs
const ast = kclManager.ast
const { profile, path } = input
// Find the profile declaration
const profileNodePath = getNodePathFromSourceRange(
ast,
profile.graphSelections[0].codeRef.range
)
const profileNode = getNodeFromPath<VariableDeclarator>(
ast,
profileNodePath,
'VariableDeclarator'
)
if (err(profileNode)) {
return new Error("Couldn't parse profile selection")
}
const profileDeclarator = profileNode.node
// Find the path declaration
const pathNodePath = getNodePathFromSourceRange(
ast,
path.graphSelections[0].codeRef.range
)
const pathNode = getNodeFromPath<VariableDeclarator>(
ast,
pathNodePath,
'VariableDeclarator'
)
if (err(pathNode)) {
return new Error("Couldn't parse path selection")
}
const pathDeclarator = pathNode.node
// Perform the sweep
const sweepRes = addSweep(ast, profileDeclarator, pathDeclarator)
const updateAstResult = await kclManager.updateAst(
sweepRes.modifiedAst,
true,
{
focusPath: [sweepRes.pathToNode],
}
)
await codeManager.updateEditorWithAstAndWriteToFile(
updateAstResult.newAst
)
if (updateAstResult?.selections) {
editorManager.selectRange(updateAstResult?.selections)
}
}
),
loftAstMod: fromPromise(
async ({
input,
@ -1801,11 +1737,6 @@ export const modelingMachine = setup({
reenter: false,
},
Sweep: {
target: 'Applying sweep',
reenter: true,
},
Loft: {
target: 'Applying loft',
reenter: true,
@ -2598,19 +2529,6 @@ export const modelingMachine = setup({
},
},
'Applying sweep': {
invoke: {
src: 'sweepAstMod',
id: 'sweepAstMod',
input: ({ event }) => {
if (event.type !== 'Sweep') return undefined
return event.data
},
onDone: ['idle'],
onError: ['idle'],
},
},
'Applying loft': {
invoke: {
src: 'loftAstMod',

View File

@ -41,13 +41,13 @@ export default function Export() {
export to almost any CAD software.
</p>
<p className="my-4">
Our teammate Katie is working on the file format, check out{' '}
Our teammate David is working on the file format, check out{' '}
<a
href="https://github.com/KhronosGroup/glTF/pull/2343"
href="https://www.youtube.com/watch?v=8SuW0qkYCZo"
target="_blank"
rel="noreferrer noopener"
>
her standards proposal on GitHub
his talk with the Metaverse Standards Forum
</a>
!
</p>

View File

@ -32,8 +32,6 @@ export const PACKAGE_NAME = isDesktop()
export const IS_NIGHTLY = PACKAGE_NAME.indexOf('-nightly') > -1
export const IS_NIGHTLY_OR_DEBUG = IS_NIGHTLY || APP_VERSION === '0.0.0'
export function getReleaseUrl(version: string = APP_VERSION) {
return `https://github.com/KittyCAD/modeling-app/releases/tag/${
IS_NIGHTLY ? 'nightly-' : ''

View File

@ -502,6 +502,4 @@ impl kcl_lib::EngineManager for EngineConnection {
})),
}
}
async fn close(&self) {}
}

View File

@ -37,10 +37,9 @@ enum SocketHealth {
}
type WebSocketTcpWrite = futures::stream::SplitSink<tokio_tungstenite::WebSocketStream<reqwest::Upgraded>, WsMsg>;
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct EngineConnection {
engine_req_tx: mpsc::Sender<ToEngineReq>,
shutdown_tx: mpsc::Sender<()>,
responses: Arc<DashMap<uuid::Uuid, WebSocketResponse>>,
pending_errors: Arc<Mutex<Vec<String>>>,
#[allow(dead_code)]
@ -131,49 +130,21 @@ struct ToEngineReq {
impl EngineConnection {
/// Start waiting for incoming engine requests, and send each one over the WebSocket to the engine.
async fn start_write_actor(
mut tcp_write: WebSocketTcpWrite,
mut engine_req_rx: mpsc::Receiver<ToEngineReq>,
mut shutdown_rx: mpsc::Receiver<()>,
) {
loop {
tokio::select! {
maybe_req = engine_req_rx.recv() => {
match maybe_req {
Some(ToEngineReq { req, request_sent }) => {
// Decide whether to send as binary or text,
// then send to the engine.
async fn start_write_actor(mut tcp_write: WebSocketTcpWrite, mut engine_req_rx: mpsc::Receiver<ToEngineReq>) {
while let Some(req) = engine_req_rx.recv().await {
let ToEngineReq { req, request_sent } = req;
let res = if let WebSocketRequest::ModelingCmdReq(ModelingCmdReq {
cmd: ModelingCmd::ImportFiles { .. },
cmd_id: _,
}) = &req
{
// Send it as binary.
Self::inner_send_to_engine_binary(req, &mut tcp_write).await
} else {
Self::inner_send_to_engine(req, &mut tcp_write).await
};
// Let the caller know weve sent the request (ok or error).
let _ = request_sent.send(res);
}
None => {
// The engine_req_rx channel has closed, so no more requests.
// We'll gracefully exit the loop and close the engine.
break;
}
}
},
// If we get a shutdown signal, close the engine immediately and return.
_ = shutdown_rx.recv() => {
let _ = Self::inner_close_engine(&mut tcp_write).await;
return;
}
}
}
// If we exit the loop (e.g. engine_req_rx was closed),
// still gracefully close the engine before returning.
let _ = Self::inner_close_engine(&mut tcp_write).await;
}
@ -223,8 +194,7 @@ impl EngineConnection {
let (tcp_write, tcp_read) = ws_stream.split();
let (engine_req_tx, engine_req_rx) = mpsc::channel(10);
let (shutdown_tx, shutdown_rx) = mpsc::channel(1);
tokio::task::spawn(Self::start_write_actor(tcp_write, engine_req_rx, shutdown_rx));
tokio::task::spawn(Self::start_write_actor(tcp_write, engine_req_rx));
let mut tcp_read = TcpRead { stream: tcp_read };
@ -334,7 +304,6 @@ impl EngineConnection {
Ok(EngineConnection {
engine_req_tx,
shutdown_tx,
tcp_read_handle: Arc::new(TcpReadHandle {
handle: Arc::new(tcp_read_handle),
}),
@ -515,15 +484,4 @@ impl EngineManager for EngineConnection {
fn get_session_data(&self) -> Option<ModelingSessionData> {
self.session_data.lock().unwrap().clone()
}
async fn close(&self) {
let _ = self.shutdown_tx.send(()).await;
loop {
if let Ok(guard) = self.socket_health.lock() {
if *guard == SocketHealth::Inactive {
return;
}
}
}
}
}

View File

@ -160,6 +160,4 @@ impl crate::engine::EngineManager for EngineConnection {
})),
}
}
async fn close(&self) {}
}

View File

@ -267,7 +267,4 @@ impl crate::engine::EngineManager for EngineConnection {
Ok(ws_result)
}
// maybe we can actually impl this here? not sure how atm.
async fn close(&self) {}
}

View File

@ -600,9 +600,6 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
fn get_session_data(&self) -> Option<ModelingSessionData> {
None
}
/// Close the engine connection and wait for it to finish.
async fn close(&self);
}
#[derive(Debug, Hash, Eq, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]

View File

@ -2013,13 +2013,10 @@ impl ExecutorContext {
// AND if we aren't in wasm it doesn't really matter.
Ok(())
}
/// Given an old ast, old program memory and new ast, find the parts of the code that need to be
/// re-executed.
/// This function should never error, because in the case of any internal error, we should just pop
/// the cache.
///
/// Returns `None` when there are no changes to the program, i.e. it is
/// fully cached.
// Given an old ast, old program memory and new ast, find the parts of the code that need to be
// re-executed.
// This function should never error, because in the case of any internal error, we should just pop
// the cache.
pub async fn get_changed_program(&self, info: CacheInformation) -> Option<CacheResult> {
let Some(old) = info.old else {
// We have no old info, we need to re-execute the whole thing.
@ -2140,7 +2137,7 @@ impl ExecutorContext {
}
}
std::cmp::Ordering::Equal => {
// currently unreachable, but let's pretend like the code
// currently unreachable, but lets pretend like the code
// above can do something meaningful here for when we get
// to diffing and yanking chunks of the program apart.
@ -2239,10 +2236,7 @@ impl ExecutorContext {
)
})?;
// Move the artifact commands to simplify cache management.
exec_state
.global
.artifact_commands
.extend(self.engine.take_artifact_commands());
exec_state.global.artifact_commands = self.engine.take_artifact_commands();
let session_data = self.engine.get_session_data();
Ok(session_data)
}
@ -2632,10 +2626,6 @@ impl ExecutorContext {
self.prepare_snapshot().await
}
pub async fn close(&self) {
self.engine.close().await;
}
}
/// For each argument given,

View File

@ -1,9 +1,6 @@
use derive_docs::stdlib;
use super::{
args::{Arg, FromArgs},
Args, FnAsArg,
};
use super::{args::Arg, Args, FnAsArg};
use crate::{
errors::{KclError, KclErrorDetails},
execution::{ExecState, FunctionParam, KclValue},
@ -12,14 +9,47 @@ use crate::{
/// Apply a function to each element of an array.
pub async fn map(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (array, f): (Vec<KclValue>, FnAsArg<'_>) = FromArgs::from_args(&args, 0)?;
let array = args.get_unlabeled_kw_arg("array")?;
// Check that the array is an array
let array: Vec<KclValue> = match array {
KclValue::Array { value, meta: _ } => value,
_ => {
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![args.source_range],
message: format!(
"Expected an array to map, but got a value of type {}",
array.human_friendly_type()
),
}))
}
};
// Check that the map_fn is a function
let map_fn_kclvalue: KclValue = args.get_kw_arg("map_fn")?;
match map_fn_kclvalue {
KclValue::Function { .. } => (),
_ => {
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![args.source_range],
message: format!(
"Expected map_fn to be a function, but got a value of type {}",
map_fn_kclvalue.human_friendly_type()
),
}))
}
}
// Extract the function from the KclValue
let map_fn: FnAsArg<'_> = args.get_kw_arg("map_fn")?;
let meta = vec![args.source_range.into()];
let map_fn = FunctionParam {
inner: f.func,
fn_expr: f.expr,
inner: map_fn.func,
fn_expr: map_fn.expr,
meta: meta.clone(),
ctx: args.ctx.clone(),
memory: *f.memory,
memory: *map_fn.memory,
};
let new_array = inner_map(array, map_fn, exec_state, &args).await?;
Ok(KclValue::Array { value: new_array, meta })
@ -41,7 +71,7 @@ pub async fn map(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
/// // which is the return value from `map`.
/// circles = map(
/// [1..3],
/// drawCircle
/// map_fn = drawCircle
/// )
/// ```
/// ```no_run
@ -49,7 +79,7 @@ pub async fn map(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
/// // Call `map`, using an anonymous function instead of a named one.
/// circles = map(
/// [1..3],
/// fn(id) {
/// map_fn = fn(id) {
/// return startSketchOn("XY")
/// |> circle({ center: [id * 2 * r, 0], radius: r}, %)
/// }
@ -57,6 +87,12 @@ pub async fn map(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
/// ```
#[stdlib {
name = "map",
keywords = true,
unlabeled_first = true,
arg_docs = {
array = "The array to map.",
map_fn = "The function to map the array with.",
}
}]
async fn inner_map<'a>(
array: Vec<KclValue>,
@ -91,13 +127,43 @@ async fn call_map_closure<'a>(
/// For each item in an array, update a value.
pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (array, start, f): (Vec<KclValue>, KclValue, FnAsArg<'_>) = FromArgs::from_args(&args, 0)?;
let array_val = args.get_unlabeled_kw_arg("array")?;
let start = args.get_kw_arg("start")?;
let reduce_fn_kclvalue: KclValue = args.get_kw_arg("reduce_fn")?;
let array: Vec<KclValue> = match array_val {
KclValue::Array { value, meta: _ } => value,
_ => {
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![args.source_range],
message: format!("You can't reduce a value of type {}", array_val.human_friendly_type()),
}))
}
};
// Check that the reduce_fn is a function
match reduce_fn_kclvalue {
KclValue::Function { .. } => (),
_ => {
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![args.source_range],
message: format!(
"Expected reduce_fn to be a function, but got a value of type {}",
reduce_fn_kclvalue.human_friendly_type()
),
}))
}
}
// Extract the function from the KclValue
let reduce_fn: FnAsArg<'_> = args.get_kw_arg("reduce_fn")?;
let reduce_fn = FunctionParam {
inner: f.func,
fn_expr: f.expr,
inner: reduce_fn.func,
fn_expr: reduce_fn.expr,
meta: vec![args.source_range.into()],
ctx: args.ctx.clone(),
memory: *f.memory,
memory: *reduce_fn.memory,
};
inner_reduce(array, start, reduce_fn, exec_state, &args).await
}
@ -111,7 +177,7 @@ pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
/// // This function adds an array of numbers.
/// // It uses the `reduce` function, to call the `add` function on every
/// // element of the `arr` parameter. The starting value is 0.
/// fn sum(arr) { return reduce(arr, 0, add) }
/// fn sum(arr) { return reduce(arr, start = 0, reduce_fn = add) }
///
/// /*
/// The above is basically like this pseudo-code:
@ -131,7 +197,7 @@ pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
/// // an anonymous `add` function as its parameter, instead of declaring a
/// // named function outside.
/// arr = [1, 2, 3]
/// sum = reduce(arr, 0, (i, result_so_far) => { return i + result_so_far })
/// sum = reduce(arr, start = 0, reduce_fn = (i, result_so_far) => { return i + result_so_far })
///
/// // We use `assertEqual` to check that our `sum` function gives the
/// // expected result. It's good to check your work!
@ -150,7 +216,7 @@ pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
/// // Use a `reduce` to draw the remaining decagon sides.
/// // For each number in the array 1..10, run the given function,
/// // which takes a partially-sketched decagon and adds one more edge to it.
/// fullDecagon = reduce([1..10], startOfDecagonSketch, fn(i, partialDecagon) {
/// fullDecagon = reduce([1..10], start = startOfDecagonSketch, reduce_fn = fn(i, partialDecagon) {
/// // Draw one edge of the decagon.
/// x = cos(stepAngle * i) * radius
/// y = sin(stepAngle * i) * radius
@ -183,6 +249,13 @@ pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
/// ```
#[stdlib {
name = "reduce",
keywords = true,
unlabeled_first = true,
arg_docs = {
array = "The array to reduce.",
start = "The starting value for the reduction.",
reduce_fn = "The function to reduce the array with.",
}
}]
async fn inner_reduce<'a>(
array: Vec<KclValue>,
@ -227,11 +300,17 @@ async fn call_reduce_closure<'a>(
///
/// ```no_run
/// arr = [1, 2, 3]
/// new_arr = push(arr, 4)
/// new_arr = push(arr, elem = 4)
/// assertEqual(new_arr[3], 4, 0.00001, "4 was added to the end of the array")
/// ```
#[stdlib {
name = "push",
keywords = true,
unlabeled_first = true,
arg_docs = {
array = "The array to push to.",
elem = "The element to push to the array.",
}
}]
async fn inner_push(mut array: Vec<KclValue>, elem: KclValue, args: &Args) -> Result<KclValue, KclError> {
// Unwrap the KclValues to JValues for manipulation
@ -244,7 +323,8 @@ async fn inner_push(mut array: Vec<KclValue>, elem: KclValue, args: &Args) -> Re
pub async fn push(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
// Extract the array and the element from the arguments
let (val, elem): (KclValue, KclValue) = FromArgs::from_args(&args, 0)?;
let val = args.get_unlabeled_kw_arg("array")?;
let elem = args.get_kw_arg("elem")?;
let meta = vec![args.source_range];
let KclValue::Array { value: array, meta: _ } = val else {

View File

@ -156,8 +156,5 @@ async fn inner_loft(
.await?;
// Using the first sketch as the base curve, idk we might want to change this later.
let mut sketch = sketches[0].clone();
// Override its id with the loft id so we can get its faces later
sketch.id = id;
do_post_extrude(sketch, 0.0, exec_state, args).await
do_post_extrude(sketches[0].clone(), 0.0, exec_state, args).await
}

View File

@ -25,12 +25,10 @@ pub async fn execute_and_snapshot(
) -> Result<image::DynamicImage, ExecError> {
let ctx = new_context(units, true, project_directory).await?;
let program = Program::parse_no_errs(code).map_err(KclErrorWithOutputs::no_outputs)?;
let res = do_execute_and_snapshot(&ctx, program)
do_execute_and_snapshot(&ctx, program)
.await
.map(|(_state, snap)| snap)
.map_err(|err| err.error);
ctx.close().await;
res
.map_err(|err| err.error)
}
/// Executes a kcl program and takes a snapshot of the result.
@ -41,16 +39,14 @@ pub async fn execute_and_snapshot_ast(
project_directory: Option<PathBuf>,
) -> Result<(ProgramMemory, Vec<Operation>, Vec<ArtifactCommand>, image::DynamicImage), ExecErrorWithState> {
let ctx = new_context(units, true, project_directory).await?;
let res = do_execute_and_snapshot(&ctx, ast).await.map(|(state, snap)| {
do_execute_and_snapshot(&ctx, ast).await.map(|(state, snap)| {
(
state.mod_local.memory,
state.mod_local.operations,
state.global.artifact_commands,
snap,
)
});
ctx.close().await;
res
})
}
pub async fn execute_and_snapshot_no_auth(
@ -60,12 +56,10 @@ pub async fn execute_and_snapshot_no_auth(
) -> Result<image::DynamicImage, ExecError> {
let ctx = new_context(units, false, project_directory).await?;
let program = Program::parse_no_errs(code).map_err(KclErrorWithOutputs::no_outputs)?;
let res = do_execute_and_snapshot(&ctx, program)
do_execute_and_snapshot(&ctx, program)
.await
.map(|(_state, snap)| snap)
.map_err(|err| err.error);
ctx.close().await;
res
.map_err(|err| err.error)
}
async fn do_execute_and_snapshot(
@ -86,9 +80,6 @@ async fn do_execute_and_snapshot(
.map_err(|e| ExecError::BadPng(e.to_string()))
.and_then(|x| x.decode().map_err(|e| ExecError::BadPng(e.to_string())))
.map_err(|err| ExecErrorWithState::new(err, exec_state.clone()))?;
ctx.close().await;
Ok((exec_state, img))
}

View File

@ -1,6 +1,8 @@
---
source: kcl/src/simulation_tests.rs
assertion_line: 55
description: Result of parsing argument_error.kcl
snapshot_kind: text
---
{
"Ok": {
@ -61,40 +63,40 @@ description: Result of parsing argument_error.kcl
"type": "VariableDeclaration"
},
{
"end": 38,
"end": 47,
"expression": {
"arguments": [
{
"end": 29,
"name": "f",
"start": 28,
"type": "LabeledArg",
"label": {
"type": "Identifier",
"type": "Identifier"
"name": "map_fn"
},
{
"arg": {
"elements": [
{
"end": 33,
"end": 42,
"raw": "0",
"start": 32,
"start": 41,
"type": "Literal",
"type": "Literal",
"value": 0.0
},
{
"end": 36,
"end": 45,
"raw": "1",
"start": 35,
"start": 44,
"type": "Literal",
"type": "Literal",
"value": 1.0
}
],
"end": 37,
"start": 31,
"end": 46,
"start": 40,
"type": "ArrayExpression",
"type": "ArrayExpression"
}
}
],
"callee": {
"end": 27,
@ -102,17 +104,24 @@ description: Result of parsing argument_error.kcl
"start": 24,
"type": "Identifier"
},
"end": 38,
"end": 47,
"start": 24,
"type": "CallExpression",
"type": "CallExpression"
"type": "CallExpressionKw",
"type": "CallExpressionKw",
"unlabeled": {
"end": 29,
"name": "f",
"start": 28,
"type": "Identifier",
"type": "Identifier"
}
},
"start": 24,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
}
],
"end": 39,
"end": 48,
"nonCodeMeta": {
"nonCodeNodes": {
"0": [

View File

@ -1,12 +1,14 @@
---
source: kcl/src/simulation_tests.rs
assertion_line: 133
description: Error from executing argument_error.kcl
snapshot_kind: text
---
KCL Type error
KCL Semantic error
× type: Expected an array but found Function
╭─[5:5]
× semantic: Expected an array to map, but got a value of type Function
╭─[5:1]
4 │
5 │ map(f, [0, 1])
·
5 │ map(f, map_fn = [0, 1])
· ──────────────────────
╰────

View File

@ -2,4 +2,4 @@ fn f(i) {
return 5
}
map(f, [0, 1])
map(f, map_fn = [0, 1])

View File

@ -1,6 +1,8 @@
---
source: kcl/src/simulation_tests.rs
assertion_line: 55
description: Result of parsing array_elem_push.kcl
snapshot_kind: text
---
{
"Ok": {
@ -57,46 +59,53 @@ description: Result of parsing array_elem_push.kcl
},
{
"declaration": {
"end": 39,
"end": 45,
"id": {
"end": 24,
"name": "new_arr1",
"end": 23,
"name": "newArr1",
"start": 16,
"type": "Identifier"
},
"init": {
"arguments": [
{
"end": 35,
"name": "arr",
"start": 32,
"type": "LabeledArg",
"label": {
"type": "Identifier",
"type": "Identifier"
"name": "elem"
},
{
"end": 38,
"arg": {
"end": 44,
"raw": "4",
"start": 37,
"start": 43,
"type": "Literal",
"type": "Literal",
"value": 4.0
}
}
],
"callee": {
"end": 31,
"end": 30,
"name": "push",
"start": 27,
"start": 26,
"type": "Identifier"
},
"end": 39,
"start": 27,
"type": "CallExpression",
"type": "CallExpression"
"end": 45,
"start": 26,
"type": "CallExpressionKw",
"type": "CallExpressionKw",
"unlabeled": {
"end": 34,
"name": "arr",
"start": 31,
"type": "Identifier",
"type": "Identifier"
}
},
"start": 16,
"type": "VariableDeclarator"
},
"end": 39,
"end": 45,
"kind": "const",
"start": 16,
"type": "VariableDeclaration",
@ -104,647 +113,654 @@ description: Result of parsing array_elem_push.kcl
},
{
"declaration": {
"end": 68,
"end": 79,
"id": {
"end": 48,
"name": "new_arr2",
"start": 40,
"end": 53,
"name": "newArr2",
"start": 46,
"type": "Identifier"
},
"init": {
"arguments": [
{
"end": 64,
"name": "new_arr1",
"start": 56,
"type": "LabeledArg",
"label": {
"type": "Identifier",
"type": "Identifier"
"name": "elem"
},
{
"end": 67,
"arg": {
"end": 78,
"raw": "5",
"start": 66,
"start": 77,
"type": "Literal",
"type": "Literal",
"value": 5.0
}
}
],
"callee": {
"end": 55,
"end": 60,
"name": "push",
"start": 51,
"start": 56,
"type": "Identifier"
},
"end": 79,
"start": 56,
"type": "CallExpressionKw",
"type": "CallExpressionKw",
"unlabeled": {
"end": 68,
"start": 51,
"type": "CallExpression",
"type": "CallExpression"
"name": "newArr1",
"start": 61,
"type": "Identifier",
"type": "Identifier"
}
},
"start": 40,
"start": 46,
"type": "VariableDeclarator"
},
"end": 68,
"end": 79,
"kind": "const",
"start": 40,
"start": 46,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
},
{
"end": 142,
"end": 152,
"expression": {
"arguments": [
{
"computed": false,
"end": 92,
"end": 102,
"object": {
"end": 89,
"name": "new_arr1",
"start": 81,
"end": 99,
"name": "newArr1",
"start": 92,
"type": "Identifier",
"type": "Identifier"
},
"property": {
"end": 91,
"end": 101,
"raw": "0",
"start": 90,
"start": 100,
"type": "Literal",
"type": "Literal",
"value": 0.0
},
"start": 81,
"start": 92,
"type": "MemberExpression",
"type": "MemberExpression"
},
{
"end": 95,
"end": 105,
"raw": "1",
"start": 94,
"start": 104,
"type": "Literal",
"type": "Literal",
"value": 1.0
},
{
"end": 104,
"end": 114,
"raw": "0.00001",
"start": 97,
"start": 107,
"type": "Literal",
"type": "Literal",
"value": 0.00001
},
{
"end": 141,
"end": 151,
"raw": "\"element 0 should not have changed\"",
"start": 106,
"start": 116,
"type": "Literal",
"type": "Literal",
"value": "element 0 should not have changed"
}
],
"callee": {
"end": 80,
"end": 91,
"name": "assertEqual",
"start": 69,
"start": 80,
"type": "Identifier"
},
"end": 142,
"start": 69,
"end": 152,
"start": 80,
"type": "CallExpression",
"type": "CallExpression"
},
"start": 69,
"start": 80,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
},
{
"end": 216,
"end": 225,
"expression": {
"arguments": [
{
"computed": false,
"end": 166,
"end": 175,
"object": {
"end": 163,
"name": "new_arr1",
"start": 155,
"end": 172,
"name": "newArr1",
"start": 165,
"type": "Identifier",
"type": "Identifier"
},
"property": {
"end": 165,
"end": 174,
"raw": "1",
"start": 164,
"start": 173,
"type": "Literal",
"type": "Literal",
"value": 1.0
},
"start": 155,
"start": 165,
"type": "MemberExpression",
"type": "MemberExpression"
},
{
"end": 169,
"raw": "2",
"start": 168,
"type": "Literal",
"type": "Literal",
"value": 2.0
},
{
"end": 178,
"raw": "2",
"start": 177,
"type": "Literal",
"type": "Literal",
"value": 2.0
},
{
"end": 187,
"raw": "0.00001",
"start": 171,
"start": 180,
"type": "Literal",
"type": "Literal",
"value": 0.00001
},
{
"end": 215,
"end": 224,
"raw": "\"element 1 should not have changed\"",
"start": 180,
"start": 189,
"type": "Literal",
"type": "Literal",
"value": "element 1 should not have changed"
}
],
"callee": {
"end": 154,
"end": 164,
"name": "assertEqual",
"start": 143,
"start": 153,
"type": "Identifier"
},
"end": 216,
"start": 143,
"end": 225,
"start": 153,
"type": "CallExpression",
"type": "CallExpression"
},
"start": 143,
"start": 153,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
},
{
"end": 290,
"end": 298,
"expression": {
"arguments": [
{
"computed": false,
"end": 240,
"end": 248,
"object": {
"end": 237,
"name": "new_arr1",
"start": 229,
"end": 245,
"name": "newArr1",
"start": 238,
"type": "Identifier",
"type": "Identifier"
},
"property": {
"end": 239,
"end": 247,
"raw": "2",
"start": 238,
"start": 246,
"type": "Literal",
"type": "Literal",
"value": 2.0
},
"start": 229,
"start": 238,
"type": "MemberExpression",
"type": "MemberExpression"
},
{
"end": 243,
"end": 251,
"raw": "3",
"start": 242,
"start": 250,
"type": "Literal",
"type": "Literal",
"value": 3.0
},
{
"end": 252,
"end": 260,
"raw": "0.00001",
"start": 245,
"start": 253,
"type": "Literal",
"type": "Literal",
"value": 0.00001
},
{
"end": 289,
"end": 297,
"raw": "\"element 2 should not have changed\"",
"start": 254,
"start": 262,
"type": "Literal",
"type": "Literal",
"value": "element 2 should not have changed"
}
],
"callee": {
"end": 228,
"end": 237,
"name": "assertEqual",
"start": 217,
"start": 226,
"type": "Identifier"
},
"end": 290,
"start": 217,
"end": 298,
"start": 226,
"type": "CallExpression",
"type": "CallExpression"
},
"start": 217,
"start": 226,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
},
{
"end": 366,
"end": 373,
"expression": {
"arguments": [
{
"computed": false,
"end": 314,
"end": 321,
"object": {
"end": 311,
"name": "new_arr1",
"start": 303,
"end": 318,
"name": "newArr1",
"start": 311,
"type": "Identifier",
"type": "Identifier"
},
"property": {
"end": 313,
"end": 320,
"raw": "3",
"start": 312,
"start": 319,
"type": "Literal",
"type": "Literal",
"value": 3.0
},
"start": 303,
"start": 311,
"type": "MemberExpression",
"type": "MemberExpression"
},
{
"end": 317,
"end": 324,
"raw": "4",
"start": 316,
"start": 323,
"type": "Literal",
"type": "Literal",
"value": 4.0
},
{
"end": 326,
"end": 333,
"raw": "0.00001",
"start": 319,
"start": 326,
"type": "Literal",
"type": "Literal",
"value": 0.00001
},
{
"end": 365,
"end": 372,
"raw": "\"4 was added to the end of the array\"",
"start": 328,
"start": 335,
"type": "Literal",
"type": "Literal",
"value": "4 was added to the end of the array"
}
],
"callee": {
"end": 302,
"end": 310,
"name": "assertEqual",
"start": 291,
"start": 299,
"type": "Identifier"
},
"end": 366,
"start": 291,
"end": 373,
"start": 299,
"type": "CallExpression",
"type": "CallExpression"
},
"start": 291,
"start": 299,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
},
{
"end": 440,
"end": 446,
"expression": {
"arguments": [
{
"computed": false,
"end": 390,
"end": 396,
"object": {
"end": 387,
"name": "new_arr2",
"start": 379,
"end": 393,
"name": "newArr2",
"start": 386,
"type": "Identifier",
"type": "Identifier"
},
"property": {
"end": 389,
"end": 395,
"raw": "0",
"start": 388,
"start": 394,
"type": "Literal",
"type": "Literal",
"value": 0.0
},
"start": 379,
"start": 386,
"type": "MemberExpression",
"type": "MemberExpression"
},
{
"end": 393,
"end": 399,
"raw": "1",
"start": 392,
"start": 398,
"type": "Literal",
"type": "Literal",
"value": 1.0
},
{
"end": 402,
"end": 408,
"raw": "0.00001",
"start": 395,
"start": 401,
"type": "Literal",
"type": "Literal",
"value": 0.00001
},
{
"end": 439,
"end": 445,
"raw": "\"element 0 should not have changed\"",
"start": 404,
"start": 410,
"type": "Literal",
"type": "Literal",
"value": "element 0 should not have changed"
}
],
"callee": {
"end": 378,
"end": 385,
"name": "assertEqual",
"start": 367,
"start": 374,
"type": "Identifier"
},
"end": 440,
"start": 367,
"end": 446,
"start": 374,
"type": "CallExpression",
"type": "CallExpression"
},
"start": 367,
"start": 374,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
},
{
"end": 514,
"end": 519,
"expression": {
"arguments": [
{
"computed": false,
"end": 464,
"end": 469,
"object": {
"end": 461,
"name": "new_arr2",
"start": 453,
"end": 466,
"name": "newArr2",
"start": 459,
"type": "Identifier",
"type": "Identifier"
},
"property": {
"end": 463,
"end": 468,
"raw": "1",
"start": 462,
"start": 467,
"type": "Literal",
"type": "Literal",
"value": 1.0
},
"start": 453,
"start": 459,
"type": "MemberExpression",
"type": "MemberExpression"
},
{
"end": 467,
"end": 472,
"raw": "2",
"start": 466,
"start": 471,
"type": "Literal",
"type": "Literal",
"value": 2.0
},
{
"end": 476,
"end": 481,
"raw": "0.00001",
"start": 469,
"start": 474,
"type": "Literal",
"type": "Literal",
"value": 0.00001
},
{
"end": 513,
"end": 518,
"raw": "\"element 1 should not have changed\"",
"start": 478,
"start": 483,
"type": "Literal",
"type": "Literal",
"value": "element 1 should not have changed"
}
],
"callee": {
"end": 452,
"end": 458,
"name": "assertEqual",
"start": 441,
"start": 447,
"type": "Identifier"
},
"end": 514,
"start": 441,
"end": 519,
"start": 447,
"type": "CallExpression",
"type": "CallExpression"
},
"start": 441,
"start": 447,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
},
{
"end": 588,
"end": 592,
"expression": {
"arguments": [
{
"computed": false,
"end": 538,
"end": 542,
"object": {
"end": 535,
"name": "new_arr2",
"start": 527,
"end": 539,
"name": "newArr2",
"start": 532,
"type": "Identifier",
"type": "Identifier"
},
"property": {
"end": 537,
"end": 541,
"raw": "2",
"start": 536,
"start": 540,
"type": "Literal",
"type": "Literal",
"value": 2.0
},
"start": 527,
"start": 532,
"type": "MemberExpression",
"type": "MemberExpression"
},
{
"end": 541,
"end": 545,
"raw": "3",
"start": 540,
"start": 544,
"type": "Literal",
"type": "Literal",
"value": 3.0
},
{
"end": 550,
"end": 554,
"raw": "0.00001",
"start": 543,
"start": 547,
"type": "Literal",
"type": "Literal",
"value": 0.00001
},
{
"end": 587,
"end": 591,
"raw": "\"element 2 should not have changed\"",
"start": 552,
"start": 556,
"type": "Literal",
"type": "Literal",
"value": "element 2 should not have changed"
}
],
"callee": {
"end": 526,
"end": 531,
"name": "assertEqual",
"start": 515,
"start": 520,
"type": "Identifier"
},
"end": 588,
"start": 515,
"end": 592,
"start": 520,
"type": "CallExpression",
"type": "CallExpression"
},
"start": 515,
"start": 520,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
},
{
"end": 664,
"end": 667,
"expression": {
"arguments": [
{
"computed": false,
"end": 612,
"end": 615,
"object": {
"end": 609,
"name": "new_arr2",
"start": 601,
"end": 612,
"name": "newArr2",
"start": 605,
"type": "Identifier",
"type": "Identifier"
},
"property": {
"end": 611,
"end": 614,
"raw": "3",
"start": 610,
"start": 613,
"type": "Literal",
"type": "Literal",
"value": 3.0
},
"start": 601,
"start": 605,
"type": "MemberExpression",
"type": "MemberExpression"
},
{
"end": 615,
"end": 618,
"raw": "4",
"start": 614,
"start": 617,
"type": "Literal",
"type": "Literal",
"value": 4.0
},
{
"end": 624,
"end": 627,
"raw": "0.00001",
"start": 617,
"start": 620,
"type": "Literal",
"type": "Literal",
"value": 0.00001
},
{
"end": 663,
"end": 666,
"raw": "\"4 was added to the end of the array\"",
"start": 626,
"start": 629,
"type": "Literal",
"type": "Literal",
"value": "4 was added to the end of the array"
}
],
"callee": {
"end": 600,
"end": 604,
"name": "assertEqual",
"start": 589,
"start": 593,
"type": "Identifier"
},
"end": 664,
"start": 589,
"end": 667,
"start": 593,
"type": "CallExpression",
"type": "CallExpression"
},
"start": 589,
"start": 593,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
},
{
"end": 740,
"end": 742,
"expression": {
"arguments": [
{
"computed": false,
"end": 688,
"end": 690,
"object": {
"end": 685,
"name": "new_arr2",
"start": 677,
"end": 687,
"name": "newArr2",
"start": 680,
"type": "Identifier",
"type": "Identifier"
},
"property": {
"end": 687,
"end": 689,
"raw": "4",
"start": 686,
"start": 688,
"type": "Literal",
"type": "Literal",
"value": 4.0
},
"start": 677,
"start": 680,
"type": "MemberExpression",
"type": "MemberExpression"
},
{
"end": 691,
"end": 693,
"raw": "5",
"start": 690,
"start": 692,
"type": "Literal",
"type": "Literal",
"value": 5.0
},
{
"end": 700,
"end": 702,
"raw": "0.00001",
"start": 693,
"start": 695,
"type": "Literal",
"type": "Literal",
"value": 0.00001
},
{
"end": 739,
"end": 741,
"raw": "\"5 was added to the end of the array\"",
"start": 702,
"start": 704,
"type": "Literal",
"type": "Literal",
"value": "5 was added to the end of the array"
}
],
"callee": {
"end": 676,
"end": 679,
"name": "assertEqual",
"start": 665,
"start": 668,
"type": "Identifier"
},
"end": 740,
"start": 665,
"end": 742,
"start": 668,
"type": "CallExpression",
"type": "CallExpression"
},
"start": 665,
"start": 668,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
}
],
"end": 741,
"end": 743,
"start": 0
}
}

View File

@ -1,12 +1,12 @@
arr = [1, 2, 3]
new_arr1 = push(arr, 4)
new_arr2 = push(new_arr1, 5)
assertEqual(new_arr1[0], 1, 0.00001, "element 0 should not have changed")
assertEqual(new_arr1[1], 2, 0.00001, "element 1 should not have changed")
assertEqual(new_arr1[2], 3, 0.00001, "element 2 should not have changed")
assertEqual(new_arr1[3], 4, 0.00001, "4 was added to the end of the array")
assertEqual(new_arr2[0], 1, 0.00001, "element 0 should not have changed")
assertEqual(new_arr2[1], 2, 0.00001, "element 1 should not have changed")
assertEqual(new_arr2[2], 3, 0.00001, "element 2 should not have changed")
assertEqual(new_arr2[3], 4, 0.00001, "4 was added to the end of the array")
assertEqual(new_arr2[4], 5, 0.00001, "5 was added to the end of the array")
newArr1 = push(arr, elem = 4)
newArr2 = push(newArr1, elem = 5)
assertEqual(newArr1[0], 1, 0.00001, "element 0 should not have changed")
assertEqual(newArr1[1], 2, 0.00001, "element 1 should not have changed")
assertEqual(newArr1[2], 3, 0.00001, "element 2 should not have changed")
assertEqual(newArr1[3], 4, 0.00001, "4 was added to the end of the array")
assertEqual(newArr2[0], 1, 0.00001, "element 0 should not have changed")
assertEqual(newArr2[1], 2, 0.00001, "element 1 should not have changed")
assertEqual(newArr2[2], 3, 0.00001, "element 2 should not have changed")
assertEqual(newArr2[3], 4, 0.00001, "4 was added to the end of the array")
assertEqual(newArr2[4], 5, 0.00001, "5 was added to the end of the array")

View File

@ -1,6 +1,6 @@
---
source: kcl/src/simulation_tests.rs
assertion_line: 92
assertion_line: 99
description: Program memory after executing array_elem_push.kcl
snapshot_kind: text
---
@ -81,7 +81,7 @@ snapshot_kind: text
}
]
},
"new_arr1": {
"newArr1": {
"type": "Array",
"value": [
{
@ -129,8 +129,8 @@ snapshot_kind: text
"__meta": [
{
"sourceRange": [
37,
38,
43,
44,
0
]
}
@ -140,14 +140,14 @@ snapshot_kind: text
"__meta": [
{
"sourceRange": [
27,
39,
26,
45,
0
]
}
]
},
"new_arr2": {
"newArr2": {
"type": "Array",
"value": [
{
@ -195,8 +195,8 @@ snapshot_kind: text
"__meta": [
{
"sourceRange": [
37,
38,
43,
44,
0
]
}
@ -208,8 +208,8 @@ snapshot_kind: text
"__meta": [
{
"sourceRange": [
66,
67,
77,
78,
0
]
}
@ -219,8 +219,8 @@ snapshot_kind: text
"__meta": [
{
"sourceRange": [
51,
68,
56,
79,
0
]
}

View File

@ -1,6 +1,8 @@
---
source: kcl/src/simulation_tests.rs
assertion_line: 55
description: Result of parsing array_elem_push_fail.kcl
snapshot_kind: text
---
{
"Ok": {
@ -57,7 +59,7 @@ description: Result of parsing array_elem_push_fail.kcl
},
{
"declaration": {
"end": 40,
"end": 47,
"id": {
"end": 25,
"name": "pushedArr",
@ -67,20 +69,20 @@ description: Result of parsing array_elem_push_fail.kcl
"init": {
"arguments": [
{
"end": 36,
"name": "arr",
"start": 33,
"type": "LabeledArg",
"label": {
"type": "Identifier",
"type": "Identifier"
"name": "elem"
},
{
"end": 39,
"arg": {
"end": 46,
"raw": "4",
"start": 38,
"start": 45,
"type": "Literal",
"type": "Literal",
"value": 4.0
}
}
],
"callee": {
"end": 32,
@ -88,15 +90,22 @@ description: Result of parsing array_elem_push_fail.kcl
"start": 28,
"type": "Identifier"
},
"end": 40,
"end": 47,
"start": 28,
"type": "CallExpression",
"type": "CallExpression"
"type": "CallExpressionKw",
"type": "CallExpressionKw",
"unlabeled": {
"end": 36,
"name": "arr",
"start": 33,
"type": "Identifier",
"type": "Identifier"
}
},
"start": 16,
"type": "VariableDeclarator"
},
"end": 40,
"end": 47,
"kind": "const",
"start": 16,
"type": "VariableDeclaration",
@ -104,46 +113,46 @@ description: Result of parsing array_elem_push_fail.kcl
},
{
"declaration": {
"end": 54,
"end": 61,
"id": {
"end": 45,
"end": 52,
"name": "fail",
"start": 41,
"start": 48,
"type": "Identifier"
},
"init": {
"computed": false,
"end": 54,
"end": 61,
"object": {
"end": 51,
"end": 58,
"name": "arr",
"start": 48,
"start": 55,
"type": "Identifier",
"type": "Identifier"
},
"property": {
"end": 53,
"end": 60,
"raw": "3",
"start": 52,
"start": 59,
"type": "Literal",
"type": "Literal",
"value": 3.0
},
"start": 48,
"start": 55,
"type": "MemberExpression",
"type": "MemberExpression"
},
"start": 41,
"start": 48,
"type": "VariableDeclarator"
},
"end": 54,
"end": 61,
"kind": "const",
"start": 41,
"start": 48,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
}
],
"end": 55,
"end": 62,
"start": 0
}
}

View File

@ -1,12 +1,14 @@
---
source: kcl/src/simulation_tests.rs
assertion_line: 133
description: Error from executing array_elem_push_fail.kcl
snapshot_kind: text
---
KCL UndefinedValue error
× undefined value: The array doesn't have any item at index 3
╭─[3:8]
2 │ pushedArr = push(arr, 4)
2 │ pushedArr = push(arr, elem = 4)
3 │ fail = arr[3]
· ──────
╰────

View File

@ -1,3 +1,3 @@
arr = [1, 2, 3]
pushedArr = push(arr, 4)
pushedArr = push(arr, elem = 4)
fail = arr[3]

View File

@ -1,6 +1,8 @@
---
source: kcl/src/simulation_tests.rs
assertion_line: 55
description: Result of parsing double_map_fn.kcl
snapshot_kind: text
---
{
"Ok": {
@ -117,7 +119,7 @@ description: Result of parsing double_map_fn.kcl
},
{
"declaration": {
"end": 101,
"end": 119,
"id": {
"end": 50,
"name": "ys",
@ -136,18 +138,19 @@ description: Result of parsing double_map_fn.kcl
{
"arguments": [
{
"end": 66,
"start": 65,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
"type": "LabeledArg",
"label": {
"type": "Identifier",
"name": "map_fn"
},
{
"end": 77,
"arg": {
"end": 86,
"name": "increment",
"start": 68,
"start": 77,
"type": "Identifier",
"type": "Identifier"
}
}
],
"callee": {
"end": 64,
@ -155,40 +158,53 @@ description: Result of parsing double_map_fn.kcl
"start": 61,
"type": "Identifier"
},
"end": 78,
"end": 87,
"start": 61,
"type": "CallExpression",
"type": "CallExpression"
"type": "CallExpressionKw",
"type": "CallExpressionKw",
"unlabeled": {
"end": 66,
"start": 65,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
}
},
{
"arguments": [
{
"end": 89,
"start": 88,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
"type": "LabeledArg",
"label": {
"type": "Identifier",
"name": "map_fn"
},
{
"end": 100,
"arg": {
"end": 118,
"name": "increment",
"start": 91,
"start": 109,
"type": "Identifier",
"type": "Identifier"
}
],
"callee": {
"end": 87,
"name": "map",
"start": 84,
"type": "Identifier"
},
"end": 101,
"start": 84,
"type": "CallExpression",
"type": "CallExpression"
}
],
"end": 101,
"callee": {
"end": 96,
"name": "map",
"start": 93,
"type": "Identifier"
},
"end": 119,
"start": 93,
"type": "CallExpressionKw",
"type": "CallExpressionKw",
"unlabeled": {
"end": 98,
"start": 97,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
}
}
],
"end": 119,
"start": 53,
"type": "PipeExpression",
"type": "PipeExpression"
@ -196,14 +212,14 @@ description: Result of parsing double_map_fn.kcl
"start": 48,
"type": "VariableDeclarator"
},
"end": 101,
"end": 119,
"kind": "const",
"start": 48,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
}
],
"end": 102,
"end": 120,
"nonCodeMeta": {
"nonCodeNodes": {
"0": [

View File

@ -4,5 +4,5 @@ fn increment(i) {
xs = [0..2]
ys = xs
|> map(%, increment)
|> map(%, increment)
|> map(%, map_fn = increment)
|> map(%, map_fn = increment)

View File

@ -1,5 +1,6 @@
---
source: kcl/src/simulation_tests.rs
assertion_line: 99
description: Program memory after executing double_map_fn.kcl
snapshot_kind: text
---
@ -261,8 +262,8 @@ snapshot_kind: text
"__meta": [
{
"sourceRange": [
84,
101,
93,
119,
0
]
}

View File

@ -1,18 +1,14 @@
//! Cache testing framework.
use anyhow::Result;
use kcl_lib::{ExecError, ExecState};
use kcl_lib::ExecError;
#[derive(Debug)]
struct Variation<'a> {
code: &'a str,
settings: &'a kcl_lib::ExecutorSettings,
}
async fn cache_test(
test_name: &str,
variations: Vec<Variation<'_>>,
) -> Result<Vec<(String, image::DynamicImage, ExecState)>> {
async fn cache_test(test_name: &str, variations: Vec<Variation<'_>>) -> Result<Vec<(String, image::DynamicImage)>> {
let first = variations
.first()
.ok_or_else(|| anyhow::anyhow!("No variations provided for test '{}'", test_name))?;
@ -46,7 +42,7 @@ async fn cache_test(
// Save the snapshot.
let path = crate::assert_out(&format!("cache_{}_{}", test_name, index), &img);
img_results.push((path, img, exec_state.clone()));
img_results.push((path, img));
// Prepare the last state.
old_ast_state = Some(kcl_lib::OldAstState {
@ -56,8 +52,6 @@ async fn cache_test(
});
}
ctx.close().await;
Ok(img_results)
}
@ -220,47 +214,3 @@ async fn kcl_test_cache_change_highlight_edges_changes_visual() {
assert!(first.1 != second.1);
}
#[tokio::test(flavor = "multi_thread")]
async fn kcl_test_cache_add_line_preserves_artifact_commands() {
let code = r#"sketch001 = startSketchOn('XY')
|> startProfileAt([5.5229, 5.25217], %)
|> line([10.50433, -1.19122], %)
|> line([8.01362, -5.48731], %)
|> line([-1.02877, -6.76825], %)
|> line([-11.53311, 2.81559], %)
|> close(%)
"#;
// Use a new statement; don't extend the prior pipeline. This allows us to
// detect a prefix.
let code_with_extrude = code.to_owned()
+ r#"
extrude(4, sketch001)
"#;
let result = cache_test(
"add_line_preserves_artifact_commands",
vec![
Variation {
code,
settings: &Default::default(),
},
Variation {
code: code_with_extrude.as_str(),
settings: &Default::default(),
},
],
)
.await
.unwrap();
let first = result.first().unwrap();
let second = result.last().unwrap();
assert!(
first.2.global.artifact_commands.len() < second.2.global.artifact_commands.len(),
"Second should have all the artifact commands of the first, plus more. first={:?}, second={:?}",
first.2.global.artifact_commands.len(),
second.2.global.artifact_commands.len()
);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

1528
yarn.lock

File diff suppressed because it is too large Load Diff