Compare commits
11 Commits
guptaarnav
...
nightly-v2
Author | SHA1 | Date | |
---|---|---|---|
013cb10961 | |||
6261083cb1 | |||
2b0ba37ed0 | |||
96174f3cf6 | |||
aed62ff912 | |||
9334d64608 | |||
4fa7d2d8c8 | |||
3e615dfdbc | |||
c9860af29f | |||
23a42f0195 | |||
a77fa639f3 |
2
.github/ci-cd-scripts/playwright-electron.sh
vendored
2
.github/ci-cd-scripts/playwright-electron.sh
vendored
@ -21,7 +21,7 @@ if [[ ! -f "test-results/.last-run.json" ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
retry=1
|
retry=1
|
||||||
max_retrys=4
|
max_retrys=5
|
||||||
|
|
||||||
# retry failed tests, doing our own retries because using inbuilt playwright retries causes connection issues
|
# retry failed tests, doing our own retries because using inbuilt playwright retries causes connection issues
|
||||||
while [[ $retry -le $max_retrys ]]; do
|
while [[ $retry -le $max_retrys ]]; do
|
||||||
|
@ -756,6 +756,17 @@ test(`Offset plane point-and-click`, async ({
|
|||||||
})
|
})
|
||||||
await scene.expectPixelColor([74, 74, 74], testPoint, 15)
|
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 = [
|
const loftPointAndClickCases = [
|
||||||
@ -851,6 +862,75 @@ loftPointAndClickCases.forEach(({ shouldPreselect }) => {
|
|||||||
})
|
})
|
||||||
await scene.expectPixelColor([89, 89, 89], testPoint, 15)
|
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)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1030,4 +1110,104 @@ extrude001 = extrude(40, sketch001)
|
|||||||
})
|
})
|
||||||
await scene.expectPixelColor([49, 49, 49], testPoint, 15)
|
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)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
Binary file not shown.
After Width: | Height: | Size: 42 KiB |
Binary file not shown.
After Width: | Height: | Size: 47 KiB |
18
flake.lock
generated
18
flake.lock
generated
@ -2,11 +2,11 @@
|
|||||||
"nodes": {
|
"nodes": {
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1721933792,
|
"lastModified": 1736320768,
|
||||||
"narHash": "sha256-zYVwABlQnxpbaHMfX6Wt9jhyQstFYwN2XjleOJV3VVg=",
|
"narHash": "sha256-nIYdTAiKIGnFNugbomgBJR+Xv5F1ZQU+HfaBqJKroC0=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "2122a9b35b35719ad9a395fe783eabb092df01b1",
|
"rev": "4bc9c909d9ac828a039f288cf872d16d38185db8",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -18,11 +18,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs_2": {
|
"nixpkgs_2": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1718428119,
|
"lastModified": 1728538411,
|
||||||
"narHash": "sha256-WdWDpNaq6u1IPtxtYHHWpl5BmabtpmLnMAx0RdJ/vo8=",
|
"narHash": "sha256-f0SBJz1eZ2yOuKUr5CA9BHULGXVSn6miBuUWdTyhUhU=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "e6cea36f83499eb4e9cd184c8a8e823296b50ad5",
|
"rev": "b69de56fac8c2b6f8fd27f2eca01dcda8e0a4221",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -43,11 +43,11 @@
|
|||||||
"nixpkgs": "nixpkgs_2"
|
"nixpkgs": "nixpkgs_2"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1721960387,
|
"lastModified": 1736476219,
|
||||||
"narHash": "sha256-o21ax+745ETGXrcgc/yUuLw1SI77ymp3xEpJt+w/kks=",
|
"narHash": "sha256-+qyv3QqdZCdZ3cSO/cbpEY6tntyYjfe1bB12mdpNFaY=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "9cbf831c5b20a53354fc12758abd05966f9f1699",
|
"rev": "de30cc5963da22e9742bbbbb9a3344570ed237b9",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
"@headlessui/react": "^1.7.19",
|
"@headlessui/react": "^1.7.19",
|
||||||
"@headlessui/tailwindcss": "^0.2.0",
|
"@headlessui/tailwindcss": "^0.2.0",
|
||||||
"@kittycad/lib": "2.0.12",
|
"@kittycad/lib": "2.0.13",
|
||||||
"@lezer/highlight": "^1.2.1",
|
"@lezer/highlight": "^1.2.1",
|
||||||
"@lezer/lr": "^1.4.1",
|
"@lezer/lr": "^1.4.1",
|
||||||
"@react-hook/resize-observer": "^2.0.1",
|
"@react-hook/resize-observer": "^2.0.1",
|
||||||
|
@ -32,10 +32,9 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
projects: [
|
projects: [
|
||||||
{
|
{
|
||||||
name: 'Google Chrome',
|
name: 'chromium',
|
||||||
use: {
|
use: {
|
||||||
...devices['Desktop Chrome'],
|
...devices['Desktop Chrome'],
|
||||||
channel: 'chrome',
|
|
||||||
contextOptions: {
|
contextOptions: {
|
||||||
/* Chromium is the only one with these permission types */
|
/* Chromium is the only one with these permission types */
|
||||||
permissions: ['clipboard-write', 'clipboard-read'],
|
permissions: ['clipboard-write', 'clipboard-read'],
|
||||||
|
@ -271,6 +271,7 @@ export const ModelingMachineProvider = ({
|
|||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
cmd: {
|
cmd: {
|
||||||
type: 'default_camera_center_to_selection',
|
type: 'default_camera_center_to_selection',
|
||||||
|
camera_movement: 'vantage',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.catch(reportRejection)
|
.catch(reportRejection)
|
||||||
|
@ -1149,11 +1149,17 @@ export async function deleteFromSelection(
|
|||||||
((selection?.artifact?.type === 'wall' ||
|
((selection?.artifact?.type === 'wall' ||
|
||||||
selection?.artifact?.type === 'cap') &&
|
selection?.artifact?.type === 'cap') &&
|
||||||
varDec.node.init.type === 'PipeExpression') ||
|
varDec.node.init.type === 'PipeExpression') ||
|
||||||
selection.artifact?.type === 'sweep'
|
selection.artifact?.type === 'sweep' ||
|
||||||
|
selection.artifact?.type === 'plane' ||
|
||||||
|
!selection.artifact // aka expected to be a shell at this point
|
||||||
) {
|
) {
|
||||||
let extrudeNameToDelete = ''
|
let extrudeNameToDelete = ''
|
||||||
let pathToNode: PathToNode | null = null
|
let pathToNode: PathToNode | null = null
|
||||||
if (selection.artifact?.type !== 'sweep') {
|
if (
|
||||||
|
selection.artifact &&
|
||||||
|
selection.artifact.type !== 'sweep' &&
|
||||||
|
selection.artifact.type !== 'plane'
|
||||||
|
) {
|
||||||
const varDecName = varDec.node.id.name
|
const varDecName = varDec.node.id.name
|
||||||
traverse(astClone, {
|
traverse(astClone, {
|
||||||
enter: (node, path) => {
|
enter: (node, path) => {
|
||||||
@ -1169,6 +1175,17 @@ export async function deleteFromSelection(
|
|||||||
pathToNode = path
|
pathToNode = path
|
||||||
extrudeNameToDelete = dec.id.name
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -29,7 +29,9 @@ export function revolveSketch(
|
|||||||
pathToSketchNode: PathToNode,
|
pathToSketchNode: PathToNode,
|
||||||
shouldPipe = false,
|
shouldPipe = false,
|
||||||
angle: Expr = createLiteral(4),
|
angle: Expr = createLiteral(4),
|
||||||
axis: Selections
|
axisOrEdge: string,
|
||||||
|
axis: string,
|
||||||
|
edge: Selections
|
||||||
):
|
):
|
||||||
| {
|
| {
|
||||||
modifiedAst: Node<Program>
|
modifiedAst: Node<Program>
|
||||||
@ -41,31 +43,34 @@ export function revolveSketch(
|
|||||||
const sketchNode = getNodeFromPath(clonedAst, pathToSketchNode)
|
const sketchNode = getNodeFromPath(clonedAst, pathToSketchNode)
|
||||||
if (err(sketchNode)) return sketchNode
|
if (err(sketchNode)) return sketchNode
|
||||||
|
|
||||||
// testing code
|
let generatedAxis
|
||||||
const pathToAxisSelection = getNodePathFromSourceRange(
|
|
||||||
clonedAst,
|
|
||||||
axis.graphSelections[0]?.codeRef.range
|
|
||||||
)
|
|
||||||
|
|
||||||
const lineNode = getNodeFromPath<CallExpression>(
|
if (axisOrEdge === 'Edge') {
|
||||||
clonedAst,
|
const pathToAxisSelection = getNodePathFromSourceRange(
|
||||||
pathToAxisSelection,
|
clonedAst,
|
||||||
'CallExpression'
|
edge.graphSelections[0]?.codeRef.range
|
||||||
)
|
)
|
||||||
if (err(lineNode)) return lineNode
|
const lineNode = getNodeFromPath<CallExpression>(
|
||||||
|
clonedAst,
|
||||||
|
pathToAxisSelection,
|
||||||
|
'CallExpression'
|
||||||
|
)
|
||||||
|
if (err(lineNode)) return lineNode
|
||||||
|
|
||||||
// TODO Kevin: What if |> close(%)?
|
const tagResult = mutateAstWithTagForSketchSegment(
|
||||||
// TODO Kevin: What if opposite edge
|
clonedAst,
|
||||||
// TODO Kevin: What if the edge isn't planar to the sketch?
|
pathToAxisSelection
|
||||||
// TODO Kevin: add a tag.
|
)
|
||||||
const tagResult = mutateAstWithTagForSketchSegment(
|
|
||||||
clonedAst,
|
|
||||||
pathToAxisSelection
|
|
||||||
)
|
|
||||||
|
|
||||||
// Have the tag whether it is already created or a new one is generated
|
// Have the tag whether it is already created or a new one is generated
|
||||||
if (err(tagResult)) return tagResult
|
if (err(tagResult)) return tagResult
|
||||||
const { tag } = 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 */
|
/* Original Code */
|
||||||
const { node: sketchExpression } = sketchNode
|
const { node: sketchExpression } = sketchNode
|
||||||
@ -91,14 +96,12 @@ export function revolveSketch(
|
|||||||
shallowPath: sketchPathToDecleration,
|
shallowPath: sketchPathToDecleration,
|
||||||
} = sketchVariableDeclaratorNode
|
} = sketchVariableDeclaratorNode
|
||||||
|
|
||||||
const axisSelection = axis?.graphSelections[0]?.artifact
|
if (!generatedAxis) return new Error('Generated axis selection is missing.')
|
||||||
|
|
||||||
if (!axisSelection) return new Error('Axis selection is missing.')
|
|
||||||
|
|
||||||
const revolveCall = createCallExpressionStdLib('revolve', [
|
const revolveCall = createCallExpressionStdLib('revolve', [
|
||||||
createObjectExpression({
|
createObjectExpression({
|
||||||
angle: angle,
|
angle: angle,
|
||||||
axis: getEdgeTagCall(tag, axisSelection),
|
axis: generatedAxis,
|
||||||
}),
|
}),
|
||||||
createIdentifier(sketchVariableDeclarator.id.name),
|
createIdentifier(sketchVariableDeclarator.id.name),
|
||||||
])
|
])
|
||||||
|
@ -49,17 +49,27 @@ export function addShell({
|
|||||||
return new Error("Couldn't find extrude")
|
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.
|
// 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
|
// We must find a technique for these situations that is robust to intermediate declarations
|
||||||
const sketchNode = getNodeFromPath<VariableDeclarator>(
|
const extrudeNode = getNodeFromPath<VariableDeclarator>(
|
||||||
modifiedAst,
|
modifiedAst,
|
||||||
graphSelection.codeRef.pathToNode,
|
extrudeLookupResult.pathToExtrudeNode,
|
||||||
'VariableDeclarator'
|
'VariableDeclarator'
|
||||||
)
|
)
|
||||||
if (err(sketchNode)) {
|
const segmentNode = getNodeFromPath<VariableDeclarator>(
|
||||||
return sketchNode
|
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")
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedArtifact = graphSelection.artifact
|
const selectedArtifact = graphSelection.artifact
|
||||||
|
@ -77,7 +77,7 @@ interface SegmentArtifactRich extends BaseArtifact {
|
|||||||
/** A Sweep is a more generic term for extrude, revolve, loft and sweep*/
|
/** A Sweep is a more generic term for extrude, revolve, loft and sweep*/
|
||||||
interface SweepArtifact extends BaseArtifact {
|
interface SweepArtifact extends BaseArtifact {
|
||||||
type: 'sweep'
|
type: 'sweep'
|
||||||
subType: 'extrusion' | 'revolve'
|
subType: 'extrusion' | 'revolve' | 'loft'
|
||||||
pathId: string
|
pathId: string
|
||||||
surfaceIds: Array<string>
|
surfaceIds: Array<string>
|
||||||
edgeIds: Array<string>
|
edgeIds: Array<string>
|
||||||
@ -85,7 +85,7 @@ interface SweepArtifact extends BaseArtifact {
|
|||||||
}
|
}
|
||||||
interface SweepArtifactRich extends BaseArtifact {
|
interface SweepArtifactRich extends BaseArtifact {
|
||||||
type: 'sweep'
|
type: 'sweep'
|
||||||
subType: 'extrusion' | 'revolve'
|
subType: 'extrusion' | 'revolve' | 'loft'
|
||||||
path: PathArtifact
|
path: PathArtifact
|
||||||
surfaces: Array<WallArtifact | CapArtifact>
|
surfaces: Array<WallArtifact | CapArtifact>
|
||||||
edges: Array<SweepEdge>
|
edges: Array<SweepEdge>
|
||||||
@ -398,6 +398,33 @@ export function getArtifactsToUpdate({
|
|||||||
artifact: { ...path, sweepId: id },
|
artifact: { ...path, sweepId: id },
|
||||||
})
|
})
|
||||||
return returnArr
|
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 (
|
} else if (
|
||||||
cmd.type === 'solid3d_get_extrusion_face_info' &&
|
cmd.type === 'solid3d_get_extrusion_face_info' &&
|
||||||
response?.type === 'modeling' &&
|
response?.type === 'modeling' &&
|
||||||
|
@ -47,7 +47,9 @@ export type ModelingCommandSchema = {
|
|||||||
Revolve: {
|
Revolve: {
|
||||||
selection: Selections
|
selection: Selections
|
||||||
angle: KclCommandValue
|
angle: KclCommandValue
|
||||||
axis: Selections
|
axisOrEdge: string
|
||||||
|
axis: string
|
||||||
|
edge: Selections
|
||||||
}
|
}
|
||||||
Fillet: {
|
Fillet: {
|
||||||
// todo
|
// todo
|
||||||
@ -324,10 +326,10 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// TODO: Update this configuration, copied from extrude for MVP of revolve, specifically the args.selection
|
|
||||||
Revolve: {
|
Revolve: {
|
||||||
description: 'Create a 3D body by rotating a sketch region about an axis.',
|
description: 'Create a 3D body by rotating a sketch region about an axis.',
|
||||||
icon: 'revolve',
|
icon: 'revolve',
|
||||||
|
status: 'development',
|
||||||
needsReview: true,
|
needsReview: true,
|
||||||
args: {
|
args: {
|
||||||
selection: {
|
selection: {
|
||||||
@ -336,9 +338,34 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
|||||||
multiple: false, // TODO: multiple selection
|
multiple: false, // TODO: multiple selection
|
||||||
required: true,
|
required: true,
|
||||||
skip: 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: {
|
axis: {
|
||||||
required: true,
|
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
|
||||||
|
),
|
||||||
inputType: 'selection',
|
inputType: 'selection',
|
||||||
selectionTypes: ['segment', 'sweepEdge', 'edgeCutEdge'],
|
selectionTypes: ['segment', 'sweepEdge', 'edgeCutEdge'],
|
||||||
multiple: false,
|
multiple: false,
|
||||||
|
@ -68,7 +68,7 @@ export const revolveAxisValidator = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sketchSelection = artifact.pathId
|
const sketchSelection = artifact.pathId
|
||||||
let edgeSelection = data.axis.graphSelections[0].artifact?.id
|
let edgeSelection = data.edge.graphSelections[0].artifact?.id
|
||||||
|
|
||||||
if (!sketchSelection) {
|
if (!sketchSelection) {
|
||||||
return 'Unable to revolve, sketch is missing'
|
return 'Unable to revolve, sketch is missing'
|
||||||
@ -101,7 +101,7 @@ export const revolveAxisValidator = async ({
|
|||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
// return error message for the toast
|
// return error message for the toast
|
||||||
return 'Unable to revolve with selected axis'
|
return 'Unable to revolve with selected edge'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ import {
|
|||||||
StateMachineCommandSetSchema,
|
StateMachineCommandSetSchema,
|
||||||
} from './commandTypes'
|
} from './commandTypes'
|
||||||
import { DEV } from 'env'
|
import { DEV } from 'env'
|
||||||
|
import { IS_NIGHTLY_OR_DEBUG } from 'routes/Settings'
|
||||||
|
|
||||||
interface CreateMachineCommandProps<
|
interface CreateMachineCommandProps<
|
||||||
T extends AnyStateMachine,
|
T extends AnyStateMachine,
|
||||||
@ -84,7 +85,7 @@ export function createMachineCommand<
|
|||||||
} else if ('status' in commandConfig) {
|
} else if ('status' in commandConfig) {
|
||||||
const { status } = commandConfig
|
const { status } = commandConfig
|
||||||
if (status === 'inactive') return null
|
if (status === 'inactive') return null
|
||||||
if (status === 'development' && !DEV) return null
|
if (status === 'development' && !(DEV || IS_NIGHTLY_OR_DEBUG)) return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const icon = ('icon' in commandConfig && commandConfig.icon) || undefined
|
const icon = ('icon' in commandConfig && commandConfig.icon) || undefined
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
modelingMachine,
|
modelingMachine,
|
||||||
pipeHasCircle,
|
pipeHasCircle,
|
||||||
} from 'machines/modelingMachine'
|
} from 'machines/modelingMachine'
|
||||||
|
import { IS_NIGHTLY_OR_DEBUG } from 'routes/Settings'
|
||||||
import { EventFrom, StateFrom } from 'xstate'
|
import { EventFrom, StateFrom } from 'xstate'
|
||||||
|
|
||||||
export type ToolbarModeName = 'modeling' | 'sketching'
|
export type ToolbarModeName = 'modeling' | 'sketching'
|
||||||
@ -103,7 +104,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
|||||||
data: { name: 'Revolve', groupId: 'modeling' },
|
data: { name: 'Revolve', groupId: 'modeling' },
|
||||||
}),
|
}),
|
||||||
icon: 'revolve',
|
icon: 'revolve',
|
||||||
status: DEV ? 'available' : 'kcl-only',
|
status: DEV || IS_NIGHTLY_OR_DEBUG ? 'available' : 'kcl-only',
|
||||||
title: 'Revolve',
|
title: 'Revolve',
|
||||||
hotkey: 'R',
|
hotkey: 'R',
|
||||||
description:
|
description:
|
||||||
@ -161,7 +162,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
|||||||
data: { name: 'Fillet', groupId: 'modeling' },
|
data: { name: 'Fillet', groupId: 'modeling' },
|
||||||
}),
|
}),
|
||||||
icon: 'fillet3d',
|
icon: 'fillet3d',
|
||||||
status: DEV ? 'available' : 'kcl-only',
|
status: DEV || IS_NIGHTLY_OR_DEBUG ? 'available' : 'kcl-only',
|
||||||
title: 'Fillet',
|
title: 'Fillet',
|
||||||
hotkey: 'F',
|
hotkey: 'F',
|
||||||
description: 'Round the edges of a 3D solid.',
|
description: 'Round the edges of a 3D solid.',
|
||||||
|
@ -685,7 +685,7 @@ export const modelingMachine = setup({
|
|||||||
if (event.type !== 'Revolve') return
|
if (event.type !== 'Revolve') return
|
||||||
;(async () => {
|
;(async () => {
|
||||||
if (!event.data) return
|
if (!event.data) return
|
||||||
const { selection, angle, axis } = event.data
|
const { selection, angle, axis, edge, axisOrEdge } = event.data
|
||||||
let ast = kclManager.ast
|
let ast = kclManager.ast
|
||||||
if (
|
if (
|
||||||
'variableName' in angle &&
|
'variableName' in angle &&
|
||||||
@ -710,7 +710,9 @@ export const modelingMachine = setup({
|
|||||||
'variableName' in angle
|
'variableName' in angle
|
||||||
? angle.variableIdentifierAst
|
? angle.variableIdentifierAst
|
||||||
: angle.valueAst,
|
: angle.valueAst,
|
||||||
axis
|
axisOrEdge,
|
||||||
|
axis,
|
||||||
|
edge
|
||||||
)
|
)
|
||||||
if (trap(revolveSketchRes)) return
|
if (trap(revolveSketchRes)) return
|
||||||
const { modifiedAst, pathToRevolveArg } = revolveSketchRes
|
const { modifiedAst, pathToRevolveArg } = revolveSketchRes
|
||||||
|
@ -41,13 +41,13 @@ export default function Export() {
|
|||||||
export to almost any CAD software.
|
export to almost any CAD software.
|
||||||
</p>
|
</p>
|
||||||
<p className="my-4">
|
<p className="my-4">
|
||||||
Our teammate David is working on the file format, check out{' '}
|
Our teammate Katie is working on the file format, check out{' '}
|
||||||
<a
|
<a
|
||||||
href="https://www.youtube.com/watch?v=8SuW0qkYCZo"
|
href="https://github.com/KhronosGroup/glTF/pull/2343"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer noopener"
|
rel="noreferrer noopener"
|
||||||
>
|
>
|
||||||
his talk with the Metaverse Standards Forum
|
her standards proposal on GitHub
|
||||||
</a>
|
</a>
|
||||||
!
|
!
|
||||||
</p>
|
</p>
|
||||||
|
@ -32,6 +32,8 @@ export const PACKAGE_NAME = isDesktop()
|
|||||||
|
|
||||||
export const IS_NIGHTLY = PACKAGE_NAME.indexOf('-nightly') > -1
|
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) {
|
export function getReleaseUrl(version: string = APP_VERSION) {
|
||||||
return `https://github.com/KittyCAD/modeling-app/releases/tag/${
|
return `https://github.com/KittyCAD/modeling-app/releases/tag/${
|
||||||
IS_NIGHTLY ? 'nightly-' : ''
|
IS_NIGHTLY ? 'nightly-' : ''
|
||||||
|
@ -502,4 +502,6 @@ impl kcl_lib::EngineManager for EngineConnection {
|
|||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn close(&self) {}
|
||||||
}
|
}
|
||||||
|
@ -37,9 +37,10 @@ enum SocketHealth {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type WebSocketTcpWrite = futures::stream::SplitSink<tokio_tungstenite::WebSocketStream<reqwest::Upgraded>, WsMsg>;
|
type WebSocketTcpWrite = futures::stream::SplitSink<tokio_tungstenite::WebSocketStream<reqwest::Upgraded>, WsMsg>;
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug)]
|
||||||
pub struct EngineConnection {
|
pub struct EngineConnection {
|
||||||
engine_req_tx: mpsc::Sender<ToEngineReq>,
|
engine_req_tx: mpsc::Sender<ToEngineReq>,
|
||||||
|
shutdown_tx: mpsc::Sender<()>,
|
||||||
responses: Arc<DashMap<uuid::Uuid, WebSocketResponse>>,
|
responses: Arc<DashMap<uuid::Uuid, WebSocketResponse>>,
|
||||||
pending_errors: Arc<Mutex<Vec<String>>>,
|
pending_errors: Arc<Mutex<Vec<String>>>,
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@ -130,21 +131,49 @@ struct ToEngineReq {
|
|||||||
|
|
||||||
impl EngineConnection {
|
impl EngineConnection {
|
||||||
/// Start waiting for incoming engine requests, and send each one over the WebSocket to the engine.
|
/// 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>) {
|
async fn start_write_actor(
|
||||||
while let Some(req) = engine_req_rx.recv().await {
|
mut tcp_write: WebSocketTcpWrite,
|
||||||
let ToEngineReq { req, request_sent } = req;
|
mut engine_req_rx: mpsc::Receiver<ToEngineReq>,
|
||||||
let res = if let WebSocketRequest::ModelingCmdReq(ModelingCmdReq {
|
mut shutdown_rx: mpsc::Receiver<()>,
|
||||||
cmd: ModelingCmd::ImportFiles { .. },
|
) {
|
||||||
cmd_id: _,
|
loop {
|
||||||
}) = &req
|
tokio::select! {
|
||||||
{
|
maybe_req = engine_req_rx.recv() => {
|
||||||
// Send it as binary.
|
match maybe_req {
|
||||||
Self::inner_send_to_engine_binary(req, &mut tcp_write).await
|
Some(ToEngineReq { req, request_sent }) => {
|
||||||
} else {
|
// Decide whether to send as binary or text,
|
||||||
Self::inner_send_to_engine(req, &mut tcp_write).await
|
// then send to the engine.
|
||||||
};
|
let res = if let WebSocketRequest::ModelingCmdReq(ModelingCmdReq {
|
||||||
let _ = request_sent.send(res);
|
cmd: ModelingCmd::ImportFiles { .. },
|
||||||
|
cmd_id: _,
|
||||||
|
}) = &req
|
||||||
|
{
|
||||||
|
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 we’ve 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;
|
let _ = Self::inner_close_engine(&mut tcp_write).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,7 +223,8 @@ impl EngineConnection {
|
|||||||
|
|
||||||
let (tcp_write, tcp_read) = ws_stream.split();
|
let (tcp_write, tcp_read) = ws_stream.split();
|
||||||
let (engine_req_tx, engine_req_rx) = mpsc::channel(10);
|
let (engine_req_tx, engine_req_rx) = mpsc::channel(10);
|
||||||
tokio::task::spawn(Self::start_write_actor(tcp_write, engine_req_rx));
|
let (shutdown_tx, shutdown_rx) = mpsc::channel(1);
|
||||||
|
tokio::task::spawn(Self::start_write_actor(tcp_write, engine_req_rx, shutdown_rx));
|
||||||
|
|
||||||
let mut tcp_read = TcpRead { stream: tcp_read };
|
let mut tcp_read = TcpRead { stream: tcp_read };
|
||||||
|
|
||||||
@ -304,6 +334,7 @@ impl EngineConnection {
|
|||||||
|
|
||||||
Ok(EngineConnection {
|
Ok(EngineConnection {
|
||||||
engine_req_tx,
|
engine_req_tx,
|
||||||
|
shutdown_tx,
|
||||||
tcp_read_handle: Arc::new(TcpReadHandle {
|
tcp_read_handle: Arc::new(TcpReadHandle {
|
||||||
handle: Arc::new(tcp_read_handle),
|
handle: Arc::new(tcp_read_handle),
|
||||||
}),
|
}),
|
||||||
@ -484,4 +515,15 @@ impl EngineManager for EngineConnection {
|
|||||||
fn get_session_data(&self) -> Option<ModelingSessionData> {
|
fn get_session_data(&self) -> Option<ModelingSessionData> {
|
||||||
self.session_data.lock().unwrap().clone()
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -160,4 +160,6 @@ impl crate::engine::EngineManager for EngineConnection {
|
|||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn close(&self) {}
|
||||||
}
|
}
|
||||||
|
@ -267,4 +267,7 @@ impl crate::engine::EngineManager for EngineConnection {
|
|||||||
|
|
||||||
Ok(ws_result)
|
Ok(ws_result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// maybe we can actually impl this here? not sure how atm.
|
||||||
|
async fn close(&self) {}
|
||||||
}
|
}
|
||||||
|
@ -600,6 +600,9 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
|||||||
fn get_session_data(&self) -> Option<ModelingSessionData> {
|
fn get_session_data(&self) -> Option<ModelingSessionData> {
|
||||||
None
|
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)]
|
#[derive(Debug, Hash, Eq, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
|
@ -2013,10 +2013,13 @@ impl ExecutorContext {
|
|||||||
// AND if we aren't in wasm it doesn't really matter.
|
// AND if we aren't in wasm it doesn't really matter.
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
// Given an old ast, old program memory and new ast, find the parts of the code that need to be
|
/// Given an old ast, old program memory and new ast, find the parts of the code that need to be
|
||||||
// re-executed.
|
/// re-executed.
|
||||||
// This function should never error, because in the case of any internal error, we should just pop
|
/// This function should never error, because in the case of any internal error, we should just pop
|
||||||
// the cache.
|
/// the cache.
|
||||||
|
///
|
||||||
|
/// Returns `None` when there are no changes to the program, i.e. it is
|
||||||
|
/// fully cached.
|
||||||
pub async fn get_changed_program(&self, info: CacheInformation) -> Option<CacheResult> {
|
pub async fn get_changed_program(&self, info: CacheInformation) -> Option<CacheResult> {
|
||||||
let Some(old) = info.old else {
|
let Some(old) = info.old else {
|
||||||
// We have no old info, we need to re-execute the whole thing.
|
// We have no old info, we need to re-execute the whole thing.
|
||||||
@ -2137,7 +2140,7 @@ impl ExecutorContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::cmp::Ordering::Equal => {
|
std::cmp::Ordering::Equal => {
|
||||||
// currently unreachable, but lets pretend like the code
|
// currently unreachable, but let's pretend like the code
|
||||||
// above can do something meaningful here for when we get
|
// above can do something meaningful here for when we get
|
||||||
// to diffing and yanking chunks of the program apart.
|
// to diffing and yanking chunks of the program apart.
|
||||||
|
|
||||||
@ -2236,7 +2239,10 @@ impl ExecutorContext {
|
|||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
// Move the artifact commands to simplify cache management.
|
// Move the artifact commands to simplify cache management.
|
||||||
exec_state.global.artifact_commands = self.engine.take_artifact_commands();
|
exec_state
|
||||||
|
.global
|
||||||
|
.artifact_commands
|
||||||
|
.extend(self.engine.take_artifact_commands());
|
||||||
let session_data = self.engine.get_session_data();
|
let session_data = self.engine.get_session_data();
|
||||||
Ok(session_data)
|
Ok(session_data)
|
||||||
}
|
}
|
||||||
@ -2626,6 +2632,10 @@ impl ExecutorContext {
|
|||||||
|
|
||||||
self.prepare_snapshot().await
|
self.prepare_snapshot().await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn close(&self) {
|
||||||
|
self.engine.close().await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// For each argument given,
|
/// For each argument given,
|
||||||
|
@ -156,5 +156,8 @@ async fn inner_loft(
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Using the first sketch as the base curve, idk we might want to change this later.
|
// Using the first sketch as the base curve, idk we might want to change this later.
|
||||||
do_post_extrude(sketches[0].clone(), 0.0, exec_state, args).await
|
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
|
||||||
}
|
}
|
||||||
|
@ -25,10 +25,12 @@ pub async fn execute_and_snapshot(
|
|||||||
) -> Result<image::DynamicImage, ExecError> {
|
) -> Result<image::DynamicImage, ExecError> {
|
||||||
let ctx = new_context(units, true, project_directory).await?;
|
let ctx = new_context(units, true, project_directory).await?;
|
||||||
let program = Program::parse_no_errs(code).map_err(KclErrorWithOutputs::no_outputs)?;
|
let program = Program::parse_no_errs(code).map_err(KclErrorWithOutputs::no_outputs)?;
|
||||||
do_execute_and_snapshot(&ctx, program)
|
let res = do_execute_and_snapshot(&ctx, program)
|
||||||
.await
|
.await
|
||||||
.map(|(_state, snap)| snap)
|
.map(|(_state, snap)| snap)
|
||||||
.map_err(|err| err.error)
|
.map_err(|err| err.error);
|
||||||
|
ctx.close().await;
|
||||||
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Executes a kcl program and takes a snapshot of the result.
|
/// Executes a kcl program and takes a snapshot of the result.
|
||||||
@ -39,14 +41,16 @@ pub async fn execute_and_snapshot_ast(
|
|||||||
project_directory: Option<PathBuf>,
|
project_directory: Option<PathBuf>,
|
||||||
) -> Result<(ProgramMemory, Vec<Operation>, Vec<ArtifactCommand>, image::DynamicImage), ExecErrorWithState> {
|
) -> Result<(ProgramMemory, Vec<Operation>, Vec<ArtifactCommand>, image::DynamicImage), ExecErrorWithState> {
|
||||||
let ctx = new_context(units, true, project_directory).await?;
|
let ctx = new_context(units, true, project_directory).await?;
|
||||||
do_execute_and_snapshot(&ctx, ast).await.map(|(state, snap)| {
|
let res = do_execute_and_snapshot(&ctx, ast).await.map(|(state, snap)| {
|
||||||
(
|
(
|
||||||
state.mod_local.memory,
|
state.mod_local.memory,
|
||||||
state.mod_local.operations,
|
state.mod_local.operations,
|
||||||
state.global.artifact_commands,
|
state.global.artifact_commands,
|
||||||
snap,
|
snap,
|
||||||
)
|
)
|
||||||
})
|
});
|
||||||
|
ctx.close().await;
|
||||||
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn execute_and_snapshot_no_auth(
|
pub async fn execute_and_snapshot_no_auth(
|
||||||
@ -56,10 +60,12 @@ pub async fn execute_and_snapshot_no_auth(
|
|||||||
) -> Result<image::DynamicImage, ExecError> {
|
) -> Result<image::DynamicImage, ExecError> {
|
||||||
let ctx = new_context(units, false, project_directory).await?;
|
let ctx = new_context(units, false, project_directory).await?;
|
||||||
let program = Program::parse_no_errs(code).map_err(KclErrorWithOutputs::no_outputs)?;
|
let program = Program::parse_no_errs(code).map_err(KclErrorWithOutputs::no_outputs)?;
|
||||||
do_execute_and_snapshot(&ctx, program)
|
let res = do_execute_and_snapshot(&ctx, program)
|
||||||
.await
|
.await
|
||||||
.map(|(_state, snap)| snap)
|
.map(|(_state, snap)| snap)
|
||||||
.map_err(|err| err.error)
|
.map_err(|err| err.error);
|
||||||
|
ctx.close().await;
|
||||||
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn do_execute_and_snapshot(
|
async fn do_execute_and_snapshot(
|
||||||
@ -80,6 +86,9 @@ async fn do_execute_and_snapshot(
|
|||||||
.map_err(|e| ExecError::BadPng(e.to_string()))
|
.map_err(|e| ExecError::BadPng(e.to_string()))
|
||||||
.and_then(|x| x.decode().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()))?;
|
.map_err(|err| ExecErrorWithState::new(err, exec_state.clone()))?;
|
||||||
|
|
||||||
|
ctx.close().await;
|
||||||
|
|
||||||
Ok((exec_state, img))
|
Ok((exec_state, img))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,14 +1,18 @@
|
|||||||
//! Cache testing framework.
|
//! Cache testing framework.
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use kcl_lib::ExecError;
|
use kcl_lib::{ExecError, ExecState};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
struct Variation<'a> {
|
struct Variation<'a> {
|
||||||
code: &'a str,
|
code: &'a str,
|
||||||
settings: &'a kcl_lib::ExecutorSettings,
|
settings: &'a kcl_lib::ExecutorSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn cache_test(test_name: &str, variations: Vec<Variation<'_>>) -> Result<Vec<(String, image::DynamicImage)>> {
|
async fn cache_test(
|
||||||
|
test_name: &str,
|
||||||
|
variations: Vec<Variation<'_>>,
|
||||||
|
) -> Result<Vec<(String, image::DynamicImage, ExecState)>> {
|
||||||
let first = variations
|
let first = variations
|
||||||
.first()
|
.first()
|
||||||
.ok_or_else(|| anyhow::anyhow!("No variations provided for test '{}'", test_name))?;
|
.ok_or_else(|| anyhow::anyhow!("No variations provided for test '{}'", test_name))?;
|
||||||
@ -42,7 +46,7 @@ async fn cache_test(test_name: &str, variations: Vec<Variation<'_>>) -> Result<V
|
|||||||
// Save the snapshot.
|
// Save the snapshot.
|
||||||
let path = crate::assert_out(&format!("cache_{}_{}", test_name, index), &img);
|
let path = crate::assert_out(&format!("cache_{}_{}", test_name, index), &img);
|
||||||
|
|
||||||
img_results.push((path, img));
|
img_results.push((path, img, exec_state.clone()));
|
||||||
|
|
||||||
// Prepare the last state.
|
// Prepare the last state.
|
||||||
old_ast_state = Some(kcl_lib::OldAstState {
|
old_ast_state = Some(kcl_lib::OldAstState {
|
||||||
@ -52,6 +56,8 @@ async fn cache_test(test_name: &str, variations: Vec<Variation<'_>>) -> Result<V
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx.close().await;
|
||||||
|
|
||||||
Ok(img_results)
|
Ok(img_results)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,3 +220,47 @@ async fn kcl_test_cache_change_highlight_edges_changes_visual() {
|
|||||||
|
|
||||||
assert!(first.1 != second.1);
|
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.
After Width: | Height: | Size: 28 KiB |
Binary file not shown.
After Width: | Height: | Size: 47 KiB |
@ -2010,10 +2010,10 @@
|
|||||||
"@jridgewell/resolve-uri" "^3.1.0"
|
"@jridgewell/resolve-uri" "^3.1.0"
|
||||||
"@jridgewell/sourcemap-codec" "^1.4.14"
|
"@jridgewell/sourcemap-codec" "^1.4.14"
|
||||||
|
|
||||||
"@kittycad/lib@2.0.12":
|
"@kittycad/lib@2.0.13":
|
||||||
version "2.0.12"
|
version "2.0.13"
|
||||||
resolved "https://registry.yarnpkg.com/@kittycad/lib/-/lib-2.0.12.tgz#517be58ee8b5f59e5c89bb5076492c960b4ef7d8"
|
resolved "https://registry.yarnpkg.com/@kittycad/lib/-/lib-2.0.13.tgz#e20aa17847ab1359065d21bed143ea330cf545d1"
|
||||||
integrity sha512-1eXIP+JbFvWSWQe//ijBuhlnCLRUnZzNAiOf7oMI0WcRTTn8SD8A+TY+NgK6OVGG12unyTPCVXxRR4Xtm3ahLQ==
|
integrity sha512-wLn6/iRVdqbRCvf6t2FhNr8No6+I6elpCEVHGUexyHLoE+1XeUS1lHeapQqcfR0pEQiwtGpcKTDfUNSlmnmaFw==
|
||||||
dependencies:
|
dependencies:
|
||||||
openapi-types "^12.0.0"
|
openapi-types "^12.0.0"
|
||||||
ts-node "^10.9.1"
|
ts-node "^10.9.1"
|
||||||
|
Reference in New Issue
Block a user