diff --git a/src/components/Stream.tsx b/src/components/Stream.tsx index b2eeb26a0..3298e1d32 100644 --- a/src/components/Stream.tsx +++ b/src/components/Stream.tsx @@ -14,7 +14,13 @@ import { useGlobalStateContext } from 'hooks/useGlobalStateContext' import { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/models' import { Models } from '@kittycad/lib' import { addStartSketch } from 'lang/modifyAst' -import { addNewSketchLn } from 'lang/std/sketch' +import { + addCloseToPipe, + addNewSketchLn, + compareVec2Epsilon, +} from 'lang/std/sketch' +import { getNodeFromPath } from 'lang/queryAst' +import { Program, VariableDeclarator } from 'lang/abstractSyntaxTreeTypes' export const Stream = ({ className = '' }) => { const [isLoading, setIsLoading] = useState(true) @@ -204,8 +210,8 @@ export const Stream = ({ className = '' }) => { window: { x, y }, } } - engineCommandManager?.sendSceneCommand(command).then(async ({ data }) => { - if (command.cmd.type !== 'mouse_click' || !ast) return + engineCommandManager?.sendSceneCommand(command).then(async (resp) => { + if (command?.cmd?.type !== 'mouse_click' || !ast) return if ( !( guiMode.mode === 'sketch' && @@ -214,13 +220,16 @@ export const Stream = ({ className = '' }) => { ) return - if (data?.data?.entities_modified?.length && guiMode.waitingFirstClick) { + if ( + resp?.data?.data?.entities_modified?.length && + guiMode.waitingFirstClick + ) { const curve = await engineCommandManager?.sendSceneCommand({ type: 'modeling_cmd_req', cmd_id: uuidv4(), cmd: { type: 'curve_get_control_points', - curve_id: data?.data?.entities_modified[0], + curve_id: resp?.data?.data?.entities_modified[0], }, }) const coords: { x: number; y: number }[] = @@ -243,7 +252,7 @@ export const Stream = ({ className = '' }) => { }) updateAst(_modifiedAst) } else if ( - data?.data?.entities_modified?.length && + resp?.data?.data?.entities_modified?.length && !guiMode.waitingFirstClick ) { const curve = await engineCommandManager?.sendSceneCommand({ @@ -251,18 +260,46 @@ export const Stream = ({ className = '' }) => { cmd_id: uuidv4(), cmd: { type: 'curve_get_control_points', - curve_id: data?.data?.entities_modified[0], + curve_id: resp?.data?.data?.entities_modified[0], }, }) const coords: { x: number; y: number }[] = curve.data.data.control_points - const _modifiedAst = addNewSketchLn({ - node: ast, - programMemory, - to: [coords[1].x, coords[1].y], - fnName: 'line', - pathToNode: guiMode.pathToNode, - }).modifiedAst + + const { node: varDec } = getNodeFromPath( + ast, + guiMode.pathToNode, + 'VariableDeclarator' + ) + const variableName = varDec.id.name + const sketchGroup = programMemory.root[variableName] + if (!sketchGroup || sketchGroup.type !== 'SketchGroup') return + const initialCoords = sketchGroup.value[0].from + + const isClose = compareVec2Epsilon(initialCoords, [ + coords[1].x, + coords[1].y, + ]) + + let _modifiedAst: Program + if (!isClose) { + _modifiedAst = addNewSketchLn({ + node: ast, + programMemory, + to: [coords[1].x, coords[1].y], + fnName: 'line', + pathToNode: guiMode.pathToNode, + }).modifiedAst + } else { + _modifiedAst = addCloseToPipe({ + node: ast, + programMemory, + pathToNode: guiMode.pathToNode, + }) + setGuiMode({ + mode: 'default', + }) + } updateAst(_modifiedAst) } }) diff --git a/src/lang/std/sketch.test.ts b/src/lang/std/sketch.test.ts index 6612070e1..187e76167 100644 --- a/src/lang/std/sketch.test.ts +++ b/src/lang/std/sketch.test.ts @@ -4,6 +4,7 @@ import { addNewSketchLn, getYComponent, getXComponent, + addCloseToPipe, } from './sketch' import { parser_wasm } from '../abstractSyntaxTree' import { getNodePathFromSourceRange } from '../queryAst' @@ -146,7 +147,7 @@ show(mySketch001)` const programMemory = await enginelessExecutor(ast) const sourceStart = code.indexOf(lineToChange) expect(sourceStart).toBe(66) - const { modifiedAst } = addNewSketchLn({ + let { modifiedAst } = addNewSketchLn({ node: ast, programMemory, to: [2, 3], @@ -160,12 +161,33 @@ show(mySketch001)` ], }) // Enable rotations #152 - const expectedCode = `const mySketch001 = startSketchAt([0, 0]) + let expectedCode = `const mySketch001 = startSketchAt([0, 0]) // |> rx(45, %) |> lineTo([-1.59, -1.54], %) |> lineTo([0.46, -5.82], %) |> lineTo([2, 3], %) show(mySketch001) +` + expect(recast(modifiedAst)).toBe(expectedCode) + + modifiedAst = addCloseToPipe({ + node: ast, + programMemory, + pathToNode: [ + ['body', ''], + [0, 'index'], + ['declarations', 'VariableDeclaration'], + [0, 'index'], + ['init', 'VariableDeclarator'], + ], + }) + + expectedCode = `const mySketch001 = startSketchAt([0, 0]) + // |> rx(45, %) + |> lineTo([-1.59, -1.54], %) + |> lineTo([0.46, -5.82], %) + |> close(%) +show(mySketch001) ` expect(recast(modifiedAst)).toBe(expectedCode) }) diff --git a/src/lang/std/sketch.ts b/src/lang/std/sketch.ts index 9022affc0..9007fcf40 100644 --- a/src/lang/std/sketch.ts +++ b/src/lang/std/sketch.ts @@ -947,13 +947,25 @@ interface CreateLineFnCallArgs { pathToNode: PathToNode } +export function compareVec2Epsilon( + vec1: [number, number], + vec2: [number, number] +) { + const compareEpsilon = 0.015625 // or 2^-6 + const xDifference = Math.abs(vec1[0] - vec2[0]) + const yDifference = Math.abs(vec1[0] - vec2[0]) + return xDifference < compareEpsilon && yDifference < compareEpsilon +} + export function addNewSketchLn({ node: _node, programMemory: previousProgramMemory, to, fnName, pathToNode, -}: Omit): { modifiedAst: Program } { +}: Omit): { + modifiedAst: Program +} { const node = JSON.parse(JSON.stringify(_node)) const { add, updateArgs } = sketchLineHelperMap?.[fnName] || {} if (!add || !updateArgs) throw new Error('not a sketch line helper') @@ -971,7 +983,6 @@ export function addNewSketchLn({ const last = sketch.value[sketch.value.length - 1] || sketch.start const from = last.to - return add({ node, previousProgramMemory, @@ -982,6 +993,29 @@ export function addNewSketchLn({ }) } +export function addCloseToPipe({ + node, + pathToNode, +}: { + node: Program + programMemory: ProgramMemory + pathToNode: PathToNode +}) { + const _node = { ...node } + const closeExpression = createCallExpression('close', [ + createPipeSubstitution(), + ]) + const pipeExpression = getNodeFromPath( + _node, + pathToNode, + 'PipeExpression' + ).node + if (pipeExpression.type !== 'PipeExpression') + throw new Error('not a pipe expression') + pipeExpression.body = [...pipeExpression.body, closeExpression] + return _node +} + export function replaceSketchLine({ node, programMemory,