multi-profile follow up. (#4802)

* multi-profile work

* fix enter sketch on cap

* fix coderef problem for walls and caps

* allow sketch mode entry from circle

* clean up

* update snapshot

* Look at this (photo)Graph *in the voice of Nickelback*

* trigger CI

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

* add test

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

* fix how expression index is corrected, to make compatible with offset planes

* another test

* tweak test

* more test tweaks

* break up test to fix it hopfully

* fix onboarding test

* remove bad comment

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Kurt Hutten
2024-12-16 18:36:48 +11:00
committed by GitHub
parent 96652a0c48
commit 2b2ed470c1
12 changed files with 538 additions and 98 deletions

View File

@ -216,7 +216,7 @@ export class SceneFixture {
} }
expectPixelColor = async ( expectPixelColor = async (
colour: [number, number, number], colour: [number, number, number] | [number, number, number][],
coords: { x: number; y: number }, coords: { x: number; y: number },
diff: number diff: number
) => { ) => {
@ -237,22 +237,36 @@ export class SceneFixture {
} }
} }
function isColourArray(
colour: [number, number, number] | [number, number, number][]
): colour is [number, number, number][] {
return Array.isArray(colour[0])
}
export async function expectPixelColor( export async function expectPixelColor(
page: Page, page: Page,
colour: [number, number, number], colour: [number, number, number] | [number, number, number][],
coords: { x: number; y: number }, coords: { x: number; y: number },
diff: number diff: number
) { ) {
let finalValue = colour let finalValue = colour
await expect await expect
.poll(async () => { .poll(
async () => {
const pixel = (await getPixelRGBs(page)(coords, 1))[0] const pixel = (await getPixelRGBs(page)(coords, 1))[0]
if (!pixel) return null if (!pixel) return null
finalValue = pixel finalValue = pixel
if (!isColourArray(colour)) {
return pixel.every( return pixel.every(
(channel, index) => Math.abs(channel - colour[index]) < diff (channel, index) => Math.abs(channel - colour[index]) < diff
) )
}) }
return colour.some((c) =>
c.every((channel, index) => Math.abs(pixel[index] - channel) < diff)
)
},
{ timeout: 10_000 }
)
.toBeTruthy() .toBeTruthy()
.catch((cause) => { .catch((cause) => {
throw new Error( throw new Error(

View File

@ -7,6 +7,7 @@ import {
PERSIST_MODELING_CONTEXT, PERSIST_MODELING_CONTEXT,
setup, setup,
tearDown, tearDown,
TEST_COLORS,
} from './test-utils' } from './test-utils'
import { uuidv4, roundOff } from 'lib/utils' import { uuidv4, roundOff } from 'lib/utils'
@ -1350,7 +1351,7 @@ test2.describe('Sketch mode should be toleratant to syntax errors', () => {
const [objClick] = scene.makeMouseHelpers(600, 250) const [objClick] = scene.makeMouseHelpers(600, 250)
const arrowHeadLocation = { x: 604, y: 129 } as const const arrowHeadLocation = { x: 604, y: 129 } as const
const arrowHeadWhite: [number, number, number] = [255, 255, 255] const arrowHeadWhite = TEST_COLORS.WHITE
const backgroundGray: [number, number, number] = [28, 28, 28] const backgroundGray: [number, number, number] = [28, 28, 28]
const verifyArrowHeadColor = async (c: [number, number, number]) => const verifyArrowHeadColor = async (c: [number, number, number]) =>
scene.expectPixelColor(c, arrowHeadLocation, 15) scene.expectPixelColor(c, arrowHeadLocation, 15)
@ -1993,4 +1994,334 @@ extrude001 = extrude(75, thePart)
) )
} }
) )
test2(
'Can enter sketch on sketch of wall and cap for segment, solid2d, extrude-wall, extrude-cap selections',
async ({ app, scene, toolbar, editor }) => {
// TODO this test should include a test for selecting revolve walls and caps
await app.initialise(`sketch001 = startSketchOn('XZ')
profile001 = startProfileAt([6.71, -3.66], sketch001)
|> line([2.65, 9.02], %, $seg02)
|> line([3.73, -9.36], %, $seg01)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(20, profile001)
sketch002 = startSketchOn(extrude001, seg01)
profile002 = startProfileAt([0.75, 13.46], sketch002)
|> line([4.52, 3.79], %)
|> line([5.98, -2.81], %)
profile003 = startProfileAt([3.19, 13.3], sketch002)
|> angledLine([0, 6.64], %, $rectangleSegmentA001)
|> angledLine([
segAng(rectangleSegmentA001) - 90,
2.81
], %)
|> angledLine([
segAng(rectangleSegmentA001),
-segLen(rectangleSegmentA001)
], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
profile004 = startProfileAt([3.15, 9.39], sketch002)
|> xLine(6.92, %)
|> line([-7.41, -2.85], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
profile005 = circle({ center = [5.15, 4.34], radius = 1.66 }, sketch002)
profile006 = startProfileAt([9.65, 3.82], sketch002)
|> line([2.38, 5.62], %)
|> line([2.13, -5.57], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
revolve001 = revolve({
angle = 45,
axis = getNextAdjacentEdge(seg01)
}, profile004)
extrude002 = extrude(4, profile006)
sketch003 = startSketchOn('-XZ')
profile007 = startProfileAt([4.8, 7.55], sketch003)
|> line([7.39, 2.58], %)
|> line([7.02, -2.85], %)
profile008 = startProfileAt([5.54, 5.49], sketch003)
|> line([6.34, 2.64], %)
|> line([6.33, -2.96], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
profile009 = startProfileAt([5.23, 1.95], sketch003)
|> line([6.8, 2.17], %)
|> line([7.34, -2.75], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
profile010 = circle({
center = [7.18, -2.11],
radius = 2.67
}, sketch003)
profile011 = startProfileAt([5.07, -6.39], sketch003)
|> angledLine([0, 4.54], %, $rectangleSegmentA002)
|> angledLine([
segAng(rectangleSegmentA002) - 90,
4.17
], %)
|> angledLine([
segAng(rectangleSegmentA002),
-segLen(rectangleSegmentA002)
], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude003 = extrude(2.5, profile011)
revolve002 = revolve({ angle = 45, axis = seg02 }, profile008)
`)
const camPositionForSelectingSketchOnWallProfiles = () =>
scene.moveCameraTo(
{ x: 834, y: -680, z: 534 },
{ x: -54, y: -476, z: 148 }
)
const camPositionForSelectingSketchOnCapProfiles = () =>
scene.moveCameraTo(
{ x: 404, y: 690, z: 38 },
{ x: 16, y: -140, z: -10 }
)
const wallSelectionOptions = [
{
title: 'select wall segment',
selectClick: scene.makeMouseHelpers(598, 211)[0],
},
{
title: 'select wall solid 2d',
selectClick: scene.makeMouseHelpers(677, 236)[0],
},
{
title: 'select wall circle',
selectClick: scene.makeMouseHelpers(811, 247)[0],
},
{
title: 'select wall extrude wall',
selectClick: scene.makeMouseHelpers(793, 136)[0],
},
{
title: 'select wall extrude cap',
selectClick: scene.makeMouseHelpers(836, 103)[0],
},
] as const
const capSelectionOptions = [
{
title: 'select cap segment',
selectClick: scene.makeMouseHelpers(688, 91)[0],
},
{
title: 'select cap solid 2d',
selectClick: scene.makeMouseHelpers(733, 204)[0],
},
// TODO keeps failing
// {
// title: 'select cap circle',
// selectClick: scene.makeMouseHelpers(679, 290)[0],
// },
{
title: 'select cap extrude wall',
selectClick: scene.makeMouseHelpers(649, 402)[0],
},
{
title: 'select cap extrude cap',
selectClick: scene.makeMouseHelpers(693, 408)[0],
},
] as const
const verifyWallProfilesAreDrawn = async () =>
test2.step('verify wall profiles are drawn', async () => {
// open polygon
await scene.expectPixelColor(
TEST_COLORS.WHITE,
{ x: 599, y: 168 },
15
)
// closed polygon
await scene.expectPixelColor(
TEST_COLORS.WHITE,
{ x: 656, y: 171 },
15
)
// revolved profile
await scene.expectPixelColor(
TEST_COLORS.WHITE,
{ x: 655, y: 264 },
15
)
// extruded profile
await scene.expectPixelColor(
TEST_COLORS.WHITE,
{ x: 808, y: 396 },
15
)
// circle
await scene.expectPixelColor(
[
TEST_COLORS.WHITE,
TEST_COLORS.BLUE, // When entering via the circle, it's selected and therefore blue
],
{ x: 742, y: 386 },
15
)
})
const verifyCapProfilesAreDrawn = async () =>
test2.step('verify wall profiles are drawn', async () => {
// open polygon
await scene.expectPixelColor(
TEST_COLORS.WHITE,
// TEST_COLORS.BLUE, // When entering via the circle, it's selected and therefore blue
{ x: 620, y: 58 },
15
)
// revolved profile
await scene.expectPixelColor(
TEST_COLORS.WHITE,
{ x: 641, y: 110 },
15
)
// closed polygon
await scene.expectPixelColor(
TEST_COLORS.WHITE,
{ x: 632, y: 200 },
15
)
// extruded profile
await scene.expectPixelColor(
TEST_COLORS.WHITE,
{ x: 628, y: 410 },
15
)
// circle
await scene.expectPixelColor(
[
TEST_COLORS.WHITE,
TEST_COLORS.BLUE, // When entering via the circle, it's selected and therefore blue
],
{ x: 681, y: 303 },
15
)
})
await test2.step('select wall profiles', async () => {
for (const { title, selectClick } of wallSelectionOptions) {
await test2.step(title, async () => {
await camPositionForSelectingSketchOnWallProfiles()
await selectClick()
await toolbar.editSketch()
await app.page.waitForTimeout(600)
await verifyWallProfilesAreDrawn()
await toolbar.exitSketchBtn.click()
await app.page.waitForTimeout(100)
})
}
})
await test2.step('select cap profiles', async () => {
for (const { title, selectClick } of capSelectionOptions) {
await test2.step(title, async () => {
await camPositionForSelectingSketchOnCapProfiles()
await app.page.waitForTimeout(100)
await selectClick()
await app.page.waitForTimeout(100)
await toolbar.editSketch()
await app.page.waitForTimeout(600)
await verifyCapProfilesAreDrawn()
await toolbar.exitSketchBtn.click()
await app.page.waitForTimeout(100)
})
}
})
}
)
test2(
'Can enter sketch loft edges, base and continue sketch',
async ({ app, scene, toolbar, editor }) => {
await app.initialise(`sketch001 = startSketchOn('XZ')
profile001 = startProfileAt([34, 42.66], sketch001)
|> line([102.65, 151.99], %)
|> line([76, -138.66], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
plane001 = offsetPlane('XZ', 50)
sketch002 = startSketchOn(plane001)
profile002 = startProfileAt([39.43, 172.21], sketch002)
|> xLine(183.99, %)
|> line([-77.95, -145.93], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
loft([profile001, profile002])
`)
const [baseProfileEdgeClick] = scene.makeMouseHelpers(621, 292)
const [rect1Crn1] = scene.makeMouseHelpers(592, 283)
const [rect1Crn2] = scene.makeMouseHelpers(797, 268)
await baseProfileEdgeClick()
await toolbar.editSketch()
await app.page.waitForTimeout(600)
await scene.expectPixelColor(TEST_COLORS.WHITE, { x: 562, y: 172 }, 15)
await toolbar.rectangleBtn.click()
await app.page.waitForTimeout(100)
await rect1Crn1()
await editor.expectEditor.toContain(
`profile003 = startProfileAt([50.72, -18.19], sketch001)`
)
await rect1Crn2()
await editor.expectEditor.toContain(
`angledLine([0, 113.01], %, $rectangleSegmentA001)`
)
}
)
test2(
'Can enter sketch loft edges offsetPlane and continue sketch',
async ({ app, scene, toolbar, editor }) => {
await app.initialise(`sketch001 = startSketchOn('XZ')
profile001 = startProfileAt([34, 42.66], sketch001)
|> line([102.65, 151.99], %)
|> line([76, -138.66], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
plane001 = offsetPlane('XZ', 50)
sketch002 = startSketchOn(plane001)
profile002 = startProfileAt([39.43, 172.21], sketch002)
|> xLine(183.99, %)
|> line([-77.95, -145.93], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
loft([profile001, profile002])
`)
const topProfileEdgeClickCoords = { x: 602, y: 185 } as const
const [topProfileEdgeClick] = scene.makeMouseHelpers(
topProfileEdgeClickCoords.x,
topProfileEdgeClickCoords.y
)
const [rect1Crn1] = scene.makeMouseHelpers(592, 283)
const [rect1Crn2] = scene.makeMouseHelpers(797, 268)
await scene.moveCameraTo(
{ x: 8171, y: -7740, z: 1624 },
{ x: 3302, y: -627, z: 2892 }
)
await topProfileEdgeClick()
await toolbar.editSketch()
await app.page.waitForTimeout(600)
await scene.expectPixelColor(TEST_COLORS.BLUE, { x: 788, y: 188 }, 15)
await toolbar.rectangleBtn.click()
await app.page.waitForTimeout(100)
await rect1Crn1()
await editor.expectEditor.toContain(
`profile003 = startProfileAt([47.76, -17.13], plane001)`
)
await rect1Crn2()
await editor.expectEditor.toContain(
`angledLine([0, 106.42], %, $rectangleSegmentA001)`
)
}
)
}) })

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

After

Width:  |  Height:  |  Size: 139 KiB

View File

@ -1776,8 +1776,7 @@ export class SceneEntities {
structuredClone(pathToNode) structuredClone(pathToNode)
nodePathWithCorrectedIndexForTruncatedAst[1][0] = nodePathWithCorrectedIndexForTruncatedAst[1][0] =
Number(nodePathWithCorrectedIndexForTruncatedAst[1][0]) - Number(nodePathWithCorrectedIndexForTruncatedAst[1][0]) -
Number(planeNodePath[1][0]) - Number(sketchNodePaths[0][1][0])
1
const _node = getNodeFromPath<Node<CallExpression>>( const _node = getNodeFromPath<Node<CallExpression>>(
modifiedAst, modifiedAst,

View File

@ -2,7 +2,12 @@ import { SVGProps } from 'react'
export const Spinner = (props: SVGProps<SVGSVGElement>) => { export const Spinner = (props: SVGProps<SVGSVGElement>) => {
return ( return (
<svg viewBox="0 0 10 10" className={'w-8 h-8'} {...props}> <svg
data-testid="spinner"
viewBox="0 0 10 10"
className={'w-8 h-8'}
{...props}
>
<circle <circle
cx="5" cx="5"
cy="5" cy="5"

View File

@ -5,7 +5,6 @@ import {
PathToNode, PathToNode,
Expr, Expr,
CallExpression, CallExpression,
PipeExpression,
VariableDeclarator, VariableDeclarator,
} from 'lang/wasm' } from 'lang/wasm'
import { Selections } from 'lib/selections' import { Selections } from 'lib/selections'
@ -15,7 +14,6 @@ import {
createCallExpressionStdLib, createCallExpressionStdLib,
createObjectExpression, createObjectExpression,
createIdentifier, createIdentifier,
createPipeExpression,
findUniqueName, findUniqueName,
createVariableDeclaration, createVariableDeclaration,
} from 'lang/modifyAst' } from 'lang/modifyAst'
@ -24,12 +22,13 @@ import {
mutateAstWithTagForSketchSegment, mutateAstWithTagForSketchSegment,
getEdgeTagCall, getEdgeTagCall,
} from 'lang/modifyAst/addEdgeTreatment' } from 'lang/modifyAst/addEdgeTreatment'
import { Artifact, getPathsFromArtifact } from 'lang/std/artifactGraph'
export function revolveSketch( export function revolveSketch(
ast: Node<Program>, ast: Node<Program>,
pathToSketchNode: PathToNode, pathToSketchNode: PathToNode,
shouldPipe = false,
angle: Expr = createLiteral(4), angle: Expr = createLiteral(4),
axis: Selections axis: Selections,
artifact?: Artifact
): ):
| { | {
modifiedAst: Node<Program> modifiedAst: Node<Program>
@ -37,6 +36,11 @@ export function revolveSketch(
pathToRevolveArg: PathToNode pathToRevolveArg: PathToNode
} }
| Error { | Error {
const orderedSketchNodePaths = getPathsFromArtifact({
artifact: artifact,
sketchPathToNode: pathToSketchNode,
})
if (err(orderedSketchNodePaths)) return orderedSketchNodePaths
const clonedAst = structuredClone(ast) const clonedAst = structuredClone(ast)
const sketchNode = getNodeFromPath(clonedAst, pathToSketchNode) const sketchNode = getNodeFromPath(clonedAst, pathToSketchNode)
if (err(sketchNode)) return sketchNode if (err(sketchNode)) return sketchNode
@ -67,29 +71,13 @@ export function revolveSketch(
if (err(tagResult)) return tagResult if (err(tagResult)) return tagResult
const { tag } = tagResult const { tag } = tagResult
/* Original Code */
const { node: sketchExpression } = sketchNode
// determine if sketchExpression is in a pipeExpression or not
const sketchPipeExpressionNode = getNodeFromPath<PipeExpression>(
clonedAst,
pathToSketchNode,
'PipeExpression'
)
if (err(sketchPipeExpressionNode)) return sketchPipeExpressionNode
const { node: sketchPipeExpression } = sketchPipeExpressionNode
const isInPipeExpression = sketchPipeExpression.type === 'PipeExpression'
const sketchVariableDeclaratorNode = getNodeFromPath<VariableDeclarator>( const sketchVariableDeclaratorNode = getNodeFromPath<VariableDeclarator>(
clonedAst, clonedAst,
pathToSketchNode, pathToSketchNode,
'VariableDeclarator' 'VariableDeclarator'
) )
if (err(sketchVariableDeclaratorNode)) return sketchVariableDeclaratorNode if (err(sketchVariableDeclaratorNode)) return sketchVariableDeclaratorNode
const { const { node: sketchVariableDeclarator } = sketchVariableDeclaratorNode
node: sketchVariableDeclarator,
shallowPath: sketchPathToDecleration,
} = sketchVariableDeclaratorNode
const axisSelection = axis?.graphSelections[0]?.artifact const axisSelection = axis?.graphSelections[0]?.artifact
@ -103,37 +91,13 @@ export function revolveSketch(
createIdentifier(sketchVariableDeclarator.id.name), createIdentifier(sketchVariableDeclarator.id.name),
]) ])
if (shouldPipe) {
const pipeChain = createPipeExpression(
isInPipeExpression
? [...sketchPipeExpression.body, revolveCall]
: [sketchExpression as any, revolveCall]
)
sketchVariableDeclarator.init = pipeChain
const pathToRevolveArg: PathToNode = [
...sketchPathToDecleration,
['init', 'VariableDeclarator'],
['body', ''],
[pipeChain.body.length - 1, 'index'],
['arguments', 'CallExpression'],
[0, 'index'],
]
return {
modifiedAst: clonedAst,
pathToSketchNode,
pathToRevolveArg,
}
}
// We're not creating a pipe expression, // We're not creating a pipe expression,
// but rather a separate constant for the extrusion // but rather a separate constant for the extrusion
const name = findUniqueName(clonedAst, KCL_DEFAULT_CONSTANT_PREFIXES.REVOLVE) const name = findUniqueName(clonedAst, KCL_DEFAULT_CONSTANT_PREFIXES.REVOLVE)
const VariableDeclaration = createVariableDeclaration(name, revolveCall) const VariableDeclaration = createVariableDeclaration(name, revolveCall)
const sketchIndexInPathToNode = const lastSketchNodePath =
sketchPathToDecleration.findIndex((a) => a[0] === 'body') + 1 orderedSketchNodePaths[orderedSketchNodePaths.length - 1]
const sketchIndexInBody = sketchPathToDecleration[sketchIndexInPathToNode][0] const sketchIndexInBody = Number(lastSketchNodePath[1][0])
if (typeof sketchIndexInBody !== 'number') if (typeof sketchIndexInBody !== 'number')
return new Error('expected sketchIndexInBody to be a number') return new Error('expected sketchIndexInBody to be a number')
clonedAst.body.splice(sketchIndexInBody + 1, 0, VariableDeclaration) clonedAst.body.splice(sketchIndexInBody + 1, 0, VariableDeclaration)

View File

@ -212,19 +212,7 @@ Map {
"type": "wall", "type": "wall",
}, },
"UUID-10" => { "UUID-10" => {
"codeRef": { "codeRef": undefined,
"pathToNode": [
[
"body",
"",
],
],
"range": [
501,
522,
true,
],
},
"edgeCutEdgeIds": [], "edgeCutEdgeIds": [],
"id": "UUID", "id": "UUID",
"pathIds": [ "pathIds": [

View File

@ -22,6 +22,7 @@ import * as d3 from 'd3-force'
import path from 'path' import path from 'path'
import pixelmatch from 'pixelmatch' import pixelmatch from 'pixelmatch'
import { PNG } from 'pngjs' import { PNG } from 'pngjs'
import { Node } from 'wasm-lib/kcl/bindings/Node'
/* /*
Note this is an integration test, these tests connect to our real dev server and make websocket commands. Note this is an integration test, these tests connect to our real dev server and make websocket commands.
@ -171,7 +172,7 @@ afterAll(() => {
describe('testing createArtifactGraph', () => { describe('testing createArtifactGraph', () => {
describe('code with offset planes and a sketch:', () => { describe('code with offset planes and a sketch:', () => {
let ast: Program let ast: Node<Program>
let theMap: ReturnType<typeof createArtifactGraph> let theMap: ReturnType<typeof createArtifactGraph>
it('setup', () => { it('setup', () => {
@ -217,7 +218,7 @@ describe('testing createArtifactGraph', () => {
}) })
}) })
describe('code with an extrusion, fillet and sketch of face:', () => { describe('code with an extrusion, fillet and sketch of face:', () => {
let ast: Program let ast: Node<Program>
let theMap: ReturnType<typeof createArtifactGraph> let theMap: ReturnType<typeof createArtifactGraph>
it('setup', () => { it('setup', () => {
// putting this logic in here because describe blocks runs before beforeAll has finished // putting this logic in here because describe blocks runs before beforeAll has finished
@ -312,7 +313,7 @@ describe('testing createArtifactGraph', () => {
}) })
describe(`code with sketches but no extrusions or other 3D elements`, () => { describe(`code with sketches but no extrusions or other 3D elements`, () => {
let ast: Program let ast: Node<Program>
let theMap: ReturnType<typeof createArtifactGraph> let theMap: ReturnType<typeof createArtifactGraph>
it(`setup`, () => { it(`setup`, () => {
// putting this logic in here because describe blocks runs before beforeAll has finished // putting this logic in here because describe blocks runs before beforeAll has finished
@ -377,7 +378,7 @@ describe('testing createArtifactGraph', () => {
describe('capture graph of sketchOnFaceOnFace...', () => { describe('capture graph of sketchOnFaceOnFace...', () => {
describe('code with an extrusion, fillet and sketch of face:', () => { describe('code with an extrusion, fillet and sketch of face:', () => {
let ast: Program let ast: Node<Program>
let theMap: ReturnType<typeof createArtifactGraph> let theMap: ReturnType<typeof createArtifactGraph>
it('setup', async () => { it('setup', async () => {
// putting this logic in here because describe blocks runs before beforeAll has finished // putting this logic in here because describe blocks runs before beforeAll has finished
@ -399,7 +400,9 @@ describe('capture graph of sketchOnFaceOnFace...', () => {
}) })
}) })
function getCommands(codeKey: CodeKey): CacheShape[CodeKey] & { ast: Program } { function getCommands(
codeKey: CodeKey
): CacheShape[CodeKey] & { ast: Node<Program> } {
const ast = assertParse(codeKey) const ast = assertParse(codeKey)
const file = fs.readFileSync(fullPath, 'utf-8') const file = fs.readFileSync(fullPath, 'utf-8')
const parsed: CacheShape = JSON.parse(file) const parsed: CacheShape = JSON.parse(file)

View File

@ -1,8 +1,19 @@
import { Expr, PathToNode, Program, SourceRange } from 'lang/wasm' import {
Expr,
PathToNode,
Program,
SourceRange,
VariableDeclaration,
} from 'lang/wasm'
import { Models } from '@kittycad/lib' import { Models } from '@kittycad/lib'
import { getNodePathFromSourceRange } from 'lang/queryAst' import {
getNodeFromPath,
getNodePathFromSourceRange,
traverse,
} from 'lang/queryAst'
import { err } from 'lib/trap' import { err } from 'lib/trap'
import { engineCommandManager, kclManager } from 'lib/singletons' import { engineCommandManager, kclManager } from 'lib/singletons'
import { Node } from 'wasm-lib/kcl/bindings/Node'
export type ArtifactId = string export type ArtifactId = string
@ -42,7 +53,7 @@ interface Solid2DArtifact extends BaseArtifact {
export interface PathArtifactRich extends BaseArtifact { export interface PathArtifactRich extends BaseArtifact {
type: 'path' type: 'path'
/** A path must always lie on a plane */ /** A path must always lie on a plane */
plane: PlaneArtifact | WallArtifact plane: PlaneArtifact | WallArtifact | CapArtifact
/** A path must always contain 0 or more segments */ /** A path must always contain 0 or more segments */
segments: Array<SegmentArtifact> segments: Array<SegmentArtifact>
/** A path may not result in a sweep artifact */ /** A path may not result in a sweep artifact */
@ -101,6 +112,9 @@ interface CapArtifact extends BaseArtifact {
edgeCutEdgeIds: Array<ArtifactId> edgeCutEdgeIds: Array<ArtifactId>
sweepId: ArtifactId sweepId: ArtifactId
pathIds: Array<ArtifactId> pathIds: Array<ArtifactId>
// codeRef is for the sketchOnFace plane, not for the wall itself
// traverse to the extrude and or segment to get the wall's codeRef
codeRef?: CodeRef
} }
interface SweepEdgeArtifact extends BaseArtifact { interface SweepEdgeArtifact extends BaseArtifact {
@ -163,7 +177,7 @@ export function createArtifactGraph({
}: { }: {
orderedCommands: Array<OrderedCommand> orderedCommands: Array<OrderedCommand>
responseMap: ResponseMap responseMap: ResponseMap
ast: Program ast: Node<Program>
}) { }) {
const myMap = new Map<ArtifactId, Artifact>() const myMap = new Map<ArtifactId, Artifact>()
@ -242,7 +256,7 @@ export function getArtifactsToUpdate({
/** Passing in a getter because we don't wan this function to update the map directly */ /** Passing in a getter because we don't wan this function to update the map directly */
getArtifact: (id: ArtifactId) => Artifact | undefined getArtifact: (id: ArtifactId) => Artifact | undefined
currentPlaneId: ArtifactId currentPlaneId: ArtifactId
ast: Program ast: Node<Program>
}): Array<{ }): Array<{
id: ArtifactId id: ArtifactId
artifact: Artifact artifact: Artifact
@ -278,6 +292,13 @@ export function getArtifactsToUpdate({
plane?.type === 'plane' ? plane?.codeRef : { range, pathToNode } plane?.type === 'plane' ? plane?.codeRef : { range, pathToNode }
const existingPlane = getArtifact(currentPlaneId) const existingPlane = getArtifact(currentPlaneId)
if (existingPlane?.type === 'wall') { if (existingPlane?.type === 'wall') {
let existingPlaneCodeRef = existingPlane.codeRef
if (!existingPlaneCodeRef) {
const astWalkCodeRef = getWallOrCapPlaneCodeRef(ast, codeRef.pathToNode)
if (!err(astWalkCodeRef)) {
existingPlaneCodeRef = astWalkCodeRef
}
}
return [ return [
{ {
id: currentPlaneId, id: currentPlaneId,
@ -288,7 +309,29 @@ export function getArtifactsToUpdate({
edgeCutEdgeIds: existingPlane.edgeCutEdgeIds, edgeCutEdgeIds: existingPlane.edgeCutEdgeIds,
sweepId: existingPlane.sweepId, sweepId: existingPlane.sweepId,
pathIds: existingPlane.pathIds, pathIds: existingPlane.pathIds,
codeRef, codeRef: existingPlaneCodeRef,
},
},
]
} else if (existingPlane?.type === 'cap') {
let existingPlaneCodeRef = existingPlane.codeRef
if (!existingPlaneCodeRef) {
const astWalkCodeRef = getWallOrCapPlaneCodeRef(ast, codeRef.pathToNode)
if (!err(astWalkCodeRef)) {
existingPlaneCodeRef = astWalkCodeRef
}
}
return [
{
id: currentPlaneId,
artifact: {
type: 'cap',
subType: existingPlane.subType,
id: currentPlaneId,
edgeCutEdgeIds: existingPlane.edgeCutEdgeIds,
sweepId: existingPlane.sweepId,
pathIds: existingPlane.pathIds,
codeRef: existingPlaneCodeRef,
}, },
}, },
] ]
@ -333,6 +376,18 @@ export function getArtifactsToUpdate({
pathIds: [id], pathIds: [id],
}, },
}) })
} else if (plane?.type === 'cap') {
returnArr.push({
id: currentPlaneId,
artifact: {
type: 'cap',
id: currentPlaneId,
subType: plane.subType,
edgeCutEdgeIds: plane.edgeCutEdgeIds,
sweepId: plane.sweepId,
pathIds: [id],
},
})
} }
return returnArr return returnArr
} else if (cmd.type === 'extend_path' || cmd.type === 'close_path') { } else if (cmd.type === 'extend_path' || cmd.type === 'close_path') {
@ -880,9 +935,9 @@ export function codeRefFromRange(range: SourceRange, ast: Program): CodeRef {
function getPlaneFromPath( function getPlaneFromPath(
path: PathArtifact, path: PathArtifact,
graph: ArtifactGraph graph: ArtifactGraph
): PlaneArtifact | WallArtifact | Error { ): PlaneArtifact | WallArtifact | CapArtifact | Error {
const plane = getArtifactOfTypes( const plane = getArtifactOfTypes(
{ key: path.planeId, types: ['plane', 'wall'] }, { key: path.planeId, types: ['plane', 'wall', 'cap'] },
graph graph
) )
if (err(plane)) return plane if (err(plane)) return plane
@ -892,7 +947,7 @@ function getPlaneFromPath(
function getPlaneFromSegment( function getPlaneFromSegment(
segment: SegmentArtifact, segment: SegmentArtifact,
graph: ArtifactGraph graph: ArtifactGraph
): PlaneArtifact | WallArtifact | Error { ): PlaneArtifact | WallArtifact | CapArtifact | Error {
const path = getArtifactOfTypes( const path = getArtifactOfTypes(
{ key: segment.pathId, types: ['path'] }, { key: segment.pathId, types: ['path'] },
graph graph
@ -903,7 +958,7 @@ function getPlaneFromSegment(
function getPlaneFromSolid2D( function getPlaneFromSolid2D(
solid2D: Solid2DArtifact, solid2D: Solid2DArtifact,
graph: ArtifactGraph graph: ArtifactGraph
): PlaneArtifact | WallArtifact | Error { ): PlaneArtifact | WallArtifact | CapArtifact | Error {
const path = getArtifactOfTypes( const path = getArtifactOfTypes(
{ key: solid2D.pathId, types: ['path'] }, { key: solid2D.pathId, types: ['path'] },
graph graph
@ -914,7 +969,7 @@ function getPlaneFromSolid2D(
function getPlaneFromCap( function getPlaneFromCap(
cap: CapArtifact, cap: CapArtifact,
graph: ArtifactGraph graph: ArtifactGraph
): PlaneArtifact | WallArtifact | Error { ): PlaneArtifact | WallArtifact | CapArtifact | Error {
const sweep = getArtifactOfTypes( const sweep = getArtifactOfTypes(
{ key: cap.sweepId, types: ['sweep'] }, { key: cap.sweepId, types: ['sweep'] },
graph graph
@ -927,7 +982,7 @@ function getPlaneFromCap(
function getPlaneFromWall( function getPlaneFromWall(
wall: WallArtifact, wall: WallArtifact,
graph: ArtifactGraph graph: ArtifactGraph
): PlaneArtifact | WallArtifact | Error { ): PlaneArtifact | WallArtifact | CapArtifact | Error {
const sweep = getArtifactOfTypes( const sweep = getArtifactOfTypes(
{ key: wall.sweepId, types: ['sweep'] }, { key: wall.sweepId, types: ['sweep'] },
graph graph
@ -951,7 +1006,7 @@ function getPlaneFromSweepEdge(edge: SweepEdgeArtifact, graph: ArtifactGraph) {
export function getPlaneFromArtifact( export function getPlaneFromArtifact(
artifact: Artifact | undefined, artifact: Artifact | undefined,
graph: ArtifactGraph graph: ArtifactGraph
): PlaneArtifact | WallArtifact | Error { ): PlaneArtifact | WallArtifact | CapArtifact | Error {
if (!artifact) return new Error(`Artifact is undefined`) if (!artifact) return new Error(`Artifact is undefined`)
if (artifact.type === 'plane') return artifact if (artifact.type === 'plane') return artifact
if (artifact.type === 'path') return getPlaneFromPath(artifact, graph) if (artifact.type === 'path') return getPlaneFromPath(artifact, graph)
@ -1075,3 +1130,82 @@ function isNodeSafe(node: Expr): boolean {
} }
return false return false
} }
/** {@deprecated} this information should come from the ArtifactGraph not digging around in the AST */
function getWallOrCapPlaneCodeRef(
ast: Node<Program>,
pathToNode: PathToNode
): CodeRef | Error {
const varDec = getNodeFromPath<VariableDeclaration>(
ast,
pathToNode,
'VariableDeclaration'
)
if (err(varDec)) return varDec
if (varDec.node.type !== 'VariableDeclaration')
return new Error('Expected VariableDeclaration')
const init = varDec.node.declaration.init
let varName = ''
if (
init.type === 'CallExpression' &&
init.callee.type === 'Identifier' &&
(init.callee.name === 'circle' || init.callee.name === 'startProfileAt')
) {
const secondArg = init.arguments[1]
if (secondArg.type === 'Identifier') {
varName = secondArg.name
}
} else if (init.type === 'PipeExpression') {
const firstExpr = init.body[0]
if (
firstExpr.type === 'CallExpression' &&
firstExpr.callee.type === 'Identifier' &&
firstExpr.callee.name === 'startProfileAt'
) {
const secondArg = firstExpr.arguments[1]
if (secondArg.type === 'Identifier') {
varName = secondArg.name
}
}
}
if (varName === '') return new Error('Could not find variable name')
let currentVariableName = ''
const planeCodeRef: Array<{
path: PathToNode
sketchName: string
range: SourceRange
}> = []
traverse(ast, {
leave: (node) => {
if (node.type === 'VariableDeclaration') {
currentVariableName = ''
}
},
enter: (node, path) => {
if (node.type === 'VariableDeclaration') {
currentVariableName = node.declaration.id.name
}
if (
// match `${varName} = startSketchOn(...)`
node.type === 'CallExpression' &&
node.callee.name === 'startSketchOn' &&
node.arguments[0].type === 'Identifier' &&
currentVariableName === varName
) {
planeCodeRef.push({
path,
sketchName: currentVariableName,
range: [node.start, node.end, true],
})
}
},
})
if (!planeCodeRef.length)
return new Error('No paths found depending on extrude')
return {
pathToNode: planeCodeRef[0].path,
range: planeCodeRef[0].range,
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 568 KiB

After

Width:  |  Height:  |  Size: 560 KiB

View File

@ -37,6 +37,7 @@ import { KclManager } from 'lang/KclSingleton'
import { reportRejection } from 'lib/trap' import { reportRejection } from 'lib/trap'
import { markOnce } from 'lib/performance' import { markOnce } from 'lib/performance'
import { MachineManager } from 'components/MachineManagerProvider' import { MachineManager } from 'components/MachineManagerProvider'
import { Node } from 'wasm-lib/kcl/bindings/Node'
// TODO(paultag): This ought to be tweakable. // TODO(paultag): This ought to be tweakable.
const pingIntervalMs = 5_000 const pingIntervalMs = 5_000
@ -2115,7 +2116,7 @@ export class EngineCommandManager extends EventTarget {
Object.values(this.pendingCommands).map((a) => a.promise) Object.values(this.pendingCommands).map((a) => a.promise)
) )
} }
updateArtifactGraph(ast: Program) { updateArtifactGraph(ast: Node<Program>) {
this.artifactGraph = createArtifactGraph({ this.artifactGraph = createArtifactGraph({
orderedCommands: this.orderedCommands, orderedCommands: this.orderedCommands,
responseMap: this.responseMap, responseMap: this.responseMap,

File diff suppressed because one or more lines are too long