Constraint setup + Horizontal & Vertical implementation (#33)
* start of horizontal/vert constraint * horz vert constraint working with variable * quick fix * add tests for horz constraint * clean up
This commit is contained in:
@ -38,7 +38,7 @@
|
|||||||
"simpleserver:ci": "http-server ./public --cors -p 3000 &",
|
"simpleserver:ci": "http-server ./public --cors -p 3000 &",
|
||||||
"simpleserver": "http-server ./public --cors -p 3000",
|
"simpleserver": "http-server ./public --cors -p 3000",
|
||||||
"eject": "react-scripts eject",
|
"eject": "react-scripts eject",
|
||||||
"fmt": "prettier --write ./src/**.{ts,tsx} && prettier --write ./src/**/*.{ts,tsx} && prettier --write ./src/lang/**/*.{ts,tsx} && prettier --write ./src/wasm-lib/**/*.{js,ts}",
|
"fmt": "prettier --write './src/**/*.{ts,tsx,js}'",
|
||||||
"remove-importmeta": "sed -i '' 's/import.meta.url//g' \"./src/wasm-lib/pkg/wasm_lib.js\"",
|
"remove-importmeta": "sed -i '' 's/import.meta.url//g' \"./src/wasm-lib/pkg/wasm_lib.js\"",
|
||||||
"remove-importmeta:ci": "sed -i 's/import.meta.url//g' \"./src/wasm-lib/pkg/wasm_lib.js\"",
|
"remove-importmeta:ci": "sed -i 's/import.meta.url//g' \"./src/wasm-lib/pkg/wasm_lib.js\"",
|
||||||
"add-missing-import": "echo \"import util from 'util'; if (typeof window !== 'undefined' && !window.TextEncoder) { window.TextEncoder = util.TextEncoder; window.TextDecoder = util.TextDecoder}\" | cat - ./src/wasm-lib/pkg/wasm_lib.js > temp && mv temp ./src/wasm-lib/pkg/wasm_lib.js",
|
"add-missing-import": "echo \"import util from 'util'; if (typeof window !== 'undefined' && !window.TextEncoder) { window.TextEncoder = util.TextEncoder; window.TextDecoder = util.TextDecoder}\" | cat - ./src/wasm-lib/pkg/wasm_lib.js > temp && mv temp ./src/wasm-lib/pkg/wasm_lib.js",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { useStore, toolTips } from './useStore'
|
import { useStore, toolTips } from './useStore'
|
||||||
import { extrudeSketch, sketchOnExtrudedFace } from './lang/modifyAst'
|
import { extrudeSketch, sketchOnExtrudedFace } from './lang/modifyAst'
|
||||||
import { getNodePathFromSourceRange } from './lang/abstractSyntaxTree'
|
import { getNodePathFromSourceRange } from './lang/abstractSyntaxTree'
|
||||||
|
import { HorzVert } from './components/Toolbar/HorzVert'
|
||||||
|
|
||||||
export const Toolbar = () => {
|
export const Toolbar = () => {
|
||||||
const {
|
const {
|
||||||
@ -136,6 +137,7 @@ export const Toolbar = () => {
|
|||||||
...(guiMode.sketchMode === sketchFnName
|
...(guiMode.sketchMode === sketchFnName
|
||||||
? {
|
? {
|
||||||
sketchMode: 'sketchEdit',
|
sketchMode: 'sketchEdit',
|
||||||
|
// todo: ...guiMod is adding isTooltip: true, will probably just fix with xstate migtaion
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
sketchMode: sketchFnName,
|
sketchMode: sketchFnName,
|
||||||
@ -149,6 +151,8 @@ export const Toolbar = () => {
|
|||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
<br></br>
|
||||||
|
<HorzVert />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useStore } from '../useStore'
|
import { useStore } from '../useStore'
|
||||||
import { DoubleSide, Vector3, Quaternion } from 'three'
|
import { DoubleSide, Vector3, Quaternion } from 'three'
|
||||||
import { Program } from '../lang/abstractSyntaxTree'
|
import { Program } from '../lang/abstractSyntaxTree'
|
||||||
import { toolTipModification } from '../lang/std/sketch'
|
import { addNewSketchLn } from '../lang/std/sketch'
|
||||||
import { roundOff } from '../lib/utils'
|
import { roundOff } from '../lib/utils'
|
||||||
|
|
||||||
export const SketchPlane = () => {
|
export const SketchPlane = () => {
|
||||||
@ -38,7 +38,7 @@ export const SketchPlane = () => {
|
|||||||
quaternion={clickDetectQuaternion}
|
quaternion={clickDetectQuaternion}
|
||||||
position={position}
|
position={position}
|
||||||
name={sketchGridName}
|
name={sketchGridName}
|
||||||
onClick={(e) => {
|
onPointerDown={(e) => {
|
||||||
if (!('isTooltip' in guiMode)) {
|
if (!('isTooltip' in guiMode)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -64,13 +64,13 @@ export const SketchPlane = () => {
|
|||||||
body: [],
|
body: [],
|
||||||
nonCodeMeta: {},
|
nonCodeMeta: {},
|
||||||
}
|
}
|
||||||
const addLinePoint: [number, number] = [point.x, point.y]
|
const { modifiedAst } = addNewSketchLn({
|
||||||
const { modifiedAst } = toolTipModification(
|
node: _ast,
|
||||||
_ast,
|
|
||||||
programMemory,
|
programMemory,
|
||||||
addLinePoint,
|
to: [point.x, point.y],
|
||||||
guiMode
|
fnName: guiMode.sketchMode,
|
||||||
)
|
pathToNode: guiMode.pathToNode,
|
||||||
|
})
|
||||||
updateAst(modifiedAst)
|
updateAst(modifiedAst)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
136
src/components/Toolbar/HorzVert.tsx
Normal file
136
src/components/Toolbar/HorzVert.tsx
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
import { TooTip, useStore } from '../../useStore'
|
||||||
|
import {
|
||||||
|
getNodePathFromSourceRange,
|
||||||
|
getNodeFromPath,
|
||||||
|
Value,
|
||||||
|
CallExpression,
|
||||||
|
} from '../../lang/abstractSyntaxTree'
|
||||||
|
import { toolTips } from '../../useStore'
|
||||||
|
import { allowedTransforms } from '../../lang/std/sketch'
|
||||||
|
import { swapSketchHelper } from '../../lang/std/sketchConstraints'
|
||||||
|
|
||||||
|
export const HorzVert = () => {
|
||||||
|
const { guiMode, selectionRanges, ast, programMemory, updateAst } = useStore(
|
||||||
|
(s) => ({
|
||||||
|
guiMode: s.guiMode,
|
||||||
|
ast: s.ast,
|
||||||
|
updateAst: s.updateAst,
|
||||||
|
selectionRanges: s.selectionRanges,
|
||||||
|
programMemory: s.programMemory,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
const [enableHorz, setEnableHorz] = useState(false)
|
||||||
|
const [enableVert, setEnableVert] = useState(false)
|
||||||
|
const [allowedTransformsMap, setAllowedTransformsMap] = useState<
|
||||||
|
ReturnType<typeof allowedTransforms>[]
|
||||||
|
>([])
|
||||||
|
useEffect(() => {
|
||||||
|
if (!ast) return
|
||||||
|
const islineFn = (expression: Value): boolean => {
|
||||||
|
if (expression?.type !== 'CallExpression') return false
|
||||||
|
if (!toolTips.includes(expression.callee.name as any)) return false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
const paths = selectionRanges.map((selectionRange) =>
|
||||||
|
getNodePathFromSourceRange(ast, selectionRange)
|
||||||
|
)
|
||||||
|
const nodes = paths.map(
|
||||||
|
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node
|
||||||
|
)
|
||||||
|
const allowedSwaps = paths.map((a) =>
|
||||||
|
allowedTransforms({
|
||||||
|
node: ast,
|
||||||
|
pathToNode: a,
|
||||||
|
previousProgramMemory: programMemory,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
setAllowedTransformsMap(allowedSwaps)
|
||||||
|
const allowedSwapsnames = allowedSwaps.map((a) =>
|
||||||
|
Object.keys(a)
|
||||||
|
) as TooTip[][]
|
||||||
|
const horzAllowed = includedInAll(allowedSwapsnames, ['xLine', 'xLineTo'])
|
||||||
|
const vertAllowed = includedInAll(allowedSwapsnames, ['yLine', 'yLineTo'])
|
||||||
|
const isCursorsInLineFns = nodes.every(islineFn)
|
||||||
|
const _enableHorz =
|
||||||
|
isCursorsInLineFns &&
|
||||||
|
horzAllowed &&
|
||||||
|
guiMode.mode === 'sketch' &&
|
||||||
|
guiMode.sketchMode === 'sketchEdit'
|
||||||
|
if (enableHorz !== _enableHorz) setEnableHorz(_enableHorz)
|
||||||
|
const _enableVert =
|
||||||
|
isCursorsInLineFns &&
|
||||||
|
vertAllowed &&
|
||||||
|
guiMode.mode === 'sketch' &&
|
||||||
|
guiMode.sketchMode === 'sketchEdit'
|
||||||
|
if (enableVert !== _enableVert) setEnableVert(_enableVert)
|
||||||
|
}, [guiMode, selectionRanges, guiMode])
|
||||||
|
if (guiMode.mode !== 'sketch') return null
|
||||||
|
|
||||||
|
const onClick = (vertOrHor: 'vert' | 'horz') => () => {
|
||||||
|
if (ast) {
|
||||||
|
// deep clone since we are mutating in a loop, of which any could fail
|
||||||
|
let node = JSON.parse(JSON.stringify(ast))
|
||||||
|
selectionRanges.forEach((range, index) => {
|
||||||
|
const { node: callExpression } = getNodeFromPath<CallExpression>(
|
||||||
|
node,
|
||||||
|
getNodePathFromSourceRange(node, range)
|
||||||
|
)
|
||||||
|
const [relLine, absLine]: [TooTip, TooTip] =
|
||||||
|
vertOrHor === 'vert' ? ['yLine', 'yLineTo'] : ['xLine', 'xLineTo']
|
||||||
|
const finalLine = [
|
||||||
|
'line',
|
||||||
|
'angledLine',
|
||||||
|
'angledLineOfXLength',
|
||||||
|
'angledLineOfYLength',
|
||||||
|
].includes(callExpression.callee.name)
|
||||||
|
? relLine
|
||||||
|
: absLine
|
||||||
|
const createCallBackHelper = allowedTransformsMap[index][finalLine]
|
||||||
|
if (!createCallBackHelper) throw new Error('no callback helper')
|
||||||
|
const { modifiedAst } = swapSketchHelper(
|
||||||
|
programMemory,
|
||||||
|
node,
|
||||||
|
range,
|
||||||
|
finalLine,
|
||||||
|
createCallBackHelper
|
||||||
|
)
|
||||||
|
node = modifiedAst
|
||||||
|
})
|
||||||
|
updateAst(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
onClick={onClick('horz')}
|
||||||
|
className={`border m-1 px-1 rounded ${
|
||||||
|
enableHorz ? 'bg-gray-50 text-gray-800' : 'bg-gray-200 text-gray-400'
|
||||||
|
}`}
|
||||||
|
disabled={!enableHorz}
|
||||||
|
title="yo dawg"
|
||||||
|
>
|
||||||
|
Horz
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={onClick('vert')}
|
||||||
|
className={`border m-1 px-1 rounded ${
|
||||||
|
enableVert ? 'bg-gray-50 text-gray-800' : 'bg-gray-200 text-gray-400'
|
||||||
|
}`}
|
||||||
|
disabled={!enableVert}
|
||||||
|
>
|
||||||
|
Vert
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function includedInAll(
|
||||||
|
allowedsOfEach: TooTip[][],
|
||||||
|
isIncludes: TooTip[]
|
||||||
|
): boolean {
|
||||||
|
return allowedsOfEach.every((alloweds) =>
|
||||||
|
isIncludes.some((isInclude) => alloweds.includes(isInclude))
|
||||||
|
)
|
||||||
|
}
|
@ -7,7 +7,6 @@ export function useSetCursor(sourceRange: Range) {
|
|||||||
isShiftDown: s.isShiftDown,
|
isShiftDown: s.isShiftDown,
|
||||||
}))
|
}))
|
||||||
return () => {
|
return () => {
|
||||||
console.log('isShiftDown', isShiftDown, selectionRanges, sourceRange)
|
|
||||||
const ranges = isShiftDown
|
const ranges = isShiftDown
|
||||||
? [...selectionRanges, sourceRange]
|
? [...selectionRanges, sourceRange]
|
||||||
: [sourceRange]
|
: [sourceRange]
|
||||||
|
@ -1493,15 +1493,28 @@ export function getNodeFromPath<T>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Path = (string | number)[]
|
export function getNodeFromPathCurry(
|
||||||
|
node: Program,
|
||||||
|
path: (string | number)[]
|
||||||
|
): <T>(
|
||||||
|
stopAt: string,
|
||||||
|
returnEarly?: boolean
|
||||||
|
) => {
|
||||||
|
node: T
|
||||||
|
path: PathToNode
|
||||||
|
} {
|
||||||
|
return <T>(stopAt: string = '', returnEarly = false) => {
|
||||||
|
return getNodeFromPath<T>(node, path, stopAt, returnEarly)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function getNodePathFromSourceRange(
|
export function getNodePathFromSourceRange(
|
||||||
node: Program,
|
node: Program,
|
||||||
sourceRange: [number, number],
|
sourceRange: [number, number],
|
||||||
previousPath: Path = []
|
previousPath: PathToNode = []
|
||||||
): Path {
|
): PathToNode {
|
||||||
const [start, end] = sourceRange
|
const [start, end] = sourceRange
|
||||||
let path: Path = [...previousPath, 'body']
|
let path: PathToNode = [...previousPath, 'body']
|
||||||
const _node = { ...node }
|
const _node = { ...node }
|
||||||
// loop over each statement in body getting the index with a for loop
|
// loop over each statement in body getting the index with a for loop
|
||||||
for (
|
for (
|
||||||
|
@ -394,7 +394,7 @@ export function sketchOnExtrudedFace(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getLastIndex = (pathToNode: PathToNode): number => {
|
export const getLastIndex = (pathToNode: PathToNode): number => {
|
||||||
const last = pathToNode[pathToNode.length - 1]
|
const last = pathToNode[pathToNode.length - 1]
|
||||||
if (typeof last === 'number') {
|
if (typeof last === 'number') {
|
||||||
return last
|
return last
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
changeSketchArguments,
|
changeSketchArguments,
|
||||||
toolTipModification,
|
|
||||||
addTagForSketchOnFace,
|
addTagForSketchOnFace,
|
||||||
|
addNewSketchLn,
|
||||||
getYComponent,
|
getYComponent,
|
||||||
getXComponent,
|
getXComponent,
|
||||||
} from './sketch'
|
} from './sketch'
|
||||||
@ -128,10 +128,10 @@ show(mySketch001)`
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('testing toolTipModification', () => {
|
describe('testing addNewSketchLn', () => {
|
||||||
const lineToChange = 'lineTo([-1.59, -1.54], %)'
|
const lineToChange = 'lineTo([-1.59, -1.54], %)'
|
||||||
const lineAfterChange = 'lineTo([2, 3], %)'
|
const lineAfterChange = 'lineTo([2, 3], %)'
|
||||||
test('toolTipModification', () => {
|
test('addNewSketchLn', () => {
|
||||||
const code = `
|
const code = `
|
||||||
const mySketch001 = startSketchAt([0, 0])
|
const mySketch001 = startSketchAt([0, 0])
|
||||||
|> rx(45, %)
|
|> rx(45, %)
|
||||||
@ -141,12 +141,11 @@ show(mySketch001)`
|
|||||||
const ast = abstractSyntaxTree(lexer(code))
|
const ast = abstractSyntaxTree(lexer(code))
|
||||||
const programMemory = executor(ast)
|
const programMemory = executor(ast)
|
||||||
const sourceStart = code.indexOf(lineToChange)
|
const sourceStart = code.indexOf(lineToChange)
|
||||||
const { modifiedAst } = toolTipModification(ast, programMemory, [2, 3], {
|
const { modifiedAst } = addNewSketchLn({
|
||||||
mode: 'sketch',
|
node: ast,
|
||||||
sketchMode: 'lineTo',
|
programMemory,
|
||||||
isTooltip: true,
|
to: [2, 3],
|
||||||
rotation: [0, 0, 0, 1],
|
fnName: 'lineTo',
|
||||||
position: [0, 0, 0],
|
|
||||||
pathToNode: ['body', 0, 'declarations', '0', 'init'],
|
pathToNode: ['body', 0, 'declarations', '0', 'init'],
|
||||||
})
|
})
|
||||||
const expectedCode = `
|
const expectedCode = `
|
||||||
|
@ -1,18 +1,32 @@
|
|||||||
import { ProgramMemory, Path, SketchGroup, SourceRange } from '../executor'
|
import {
|
||||||
|
ProgramMemory,
|
||||||
|
Path,
|
||||||
|
SketchGroup,
|
||||||
|
SourceRange,
|
||||||
|
PathToNode,
|
||||||
|
} from '../executor'
|
||||||
import {
|
import {
|
||||||
Program,
|
Program,
|
||||||
PipeExpression,
|
PipeExpression,
|
||||||
CallExpression,
|
CallExpression,
|
||||||
VariableDeclarator,
|
VariableDeclarator,
|
||||||
getNodeFromPath,
|
getNodeFromPath,
|
||||||
|
getNodeFromPathCurry,
|
||||||
getNodePathFromSourceRange,
|
getNodePathFromSourceRange,
|
||||||
Value,
|
Value,
|
||||||
Literal,
|
Literal,
|
||||||
} from '../abstractSyntaxTree'
|
} from '../abstractSyntaxTree'
|
||||||
import { lineGeo } from '../engine'
|
import { lineGeo } from '../engine'
|
||||||
import { GuiModes } from '../../useStore'
|
import { GuiModes, toolTips, TooTip } from '../../useStore'
|
||||||
|
import { getLastIndex } from '../modifyAst'
|
||||||
|
|
||||||
import { SketchLineHelper, ModifyAstBase, InternalFn } from './stdTypes'
|
import {
|
||||||
|
SketchLineHelper,
|
||||||
|
ModifyAstBase,
|
||||||
|
InternalFn,
|
||||||
|
SketchCallTransfromMap,
|
||||||
|
TransformCallback,
|
||||||
|
} from './stdTypes'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
createLiteral,
|
createLiteral,
|
||||||
@ -26,8 +40,6 @@ import {
|
|||||||
} from '../modifyAst'
|
} from '../modifyAst'
|
||||||
import { roundOff, getLength, getAngle } from '../../lib/utils'
|
import { roundOff, getLength, getAngle } from '../../lib/utils'
|
||||||
|
|
||||||
// import { getYComponent, getXComponent } from '../sketch'
|
|
||||||
|
|
||||||
export type Coords2d = [number, number]
|
export type Coords2d = [number, number]
|
||||||
|
|
||||||
export function getCoordsFromPaths(skGroup: SketchGroup, index = 0): Coords2d {
|
export function getCoordsFromPaths(skGroup: SketchGroup, index = 0): Coords2d {
|
||||||
@ -43,6 +55,288 @@ export function getCoordsFromPaths(skGroup: SketchGroup, index = 0): Coords2d {
|
|||||||
return [0, 0]
|
return [0, 0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createCallWrapper(
|
||||||
|
a: TooTip,
|
||||||
|
val: [Value, Value] | Value,
|
||||||
|
tag?: Value
|
||||||
|
) {
|
||||||
|
return createCallExpression(a, [
|
||||||
|
createFirstArg(a, val, tag),
|
||||||
|
createPipeSubstitution(),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
function createFirstArg(
|
||||||
|
sketchFn: TooTip,
|
||||||
|
val: Value | [Value, Value],
|
||||||
|
tag?: Value
|
||||||
|
): Value {
|
||||||
|
if (!tag) {
|
||||||
|
if (Array.isArray(val)) {
|
||||||
|
return createArrayExpression(val)
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
if (Array.isArray(val)) {
|
||||||
|
if (['line', 'lineTo'].includes(sketchFn))
|
||||||
|
return createObjectExpression({ to: createArrayExpression(val), tag })
|
||||||
|
if (
|
||||||
|
['angledLine', 'angledLineOfXLength', 'angledLineOfYLength'].includes(
|
||||||
|
sketchFn
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return createObjectExpression({ angle: val[0], length: val[1], tag })
|
||||||
|
if (['angledLineToX', 'angledLineToY'].includes(sketchFn))
|
||||||
|
return createObjectExpression({ angle: val[0], to: val[1], tag })
|
||||||
|
} else {
|
||||||
|
if (['xLine', 'yLine'].includes(sketchFn))
|
||||||
|
return createObjectExpression({ length: val, tag })
|
||||||
|
if (['xLineTo', 'yLineTo'].includes(sketchFn))
|
||||||
|
return createObjectExpression({ to: val, tag })
|
||||||
|
}
|
||||||
|
throw new Error('all sketch line types should have been covered')
|
||||||
|
}
|
||||||
|
|
||||||
|
function tranformerDefaults(
|
||||||
|
filterOut: TooTip[] = [],
|
||||||
|
tag?: Value
|
||||||
|
): Partial<SketchCallTransfromMap> {
|
||||||
|
const All: SketchCallTransfromMap = {
|
||||||
|
line: (args) => createCallWrapper('line', args, tag),
|
||||||
|
lineTo: (args) => createCallWrapper('lineTo', args, tag),
|
||||||
|
angledLine: (args) => createCallWrapper('angledLine', args, tag),
|
||||||
|
angledLineOfXLength: (args) =>
|
||||||
|
createCallWrapper('angledLineOfXLength', args, tag),
|
||||||
|
angledLineOfYLength: (args) =>
|
||||||
|
createCallWrapper('angledLineOfYLength', args, tag),
|
||||||
|
angledLineToX: (args) => createCallWrapper('angledLineToX', args, tag),
|
||||||
|
angledLineToY: (args) => createCallWrapper('angledLineToY', args, tag),
|
||||||
|
xLine: (args) => createCallWrapper('xLine', args[0], tag),
|
||||||
|
xLineTo: (args) => createCallWrapper('xLineTo', args[0], tag),
|
||||||
|
yLine: (args) => createCallWrapper('yLine', args[1], tag),
|
||||||
|
yLineTo: (args) => createCallWrapper('yLineTo', args[1], tag),
|
||||||
|
}
|
||||||
|
const result: Partial<SketchCallTransfromMap> = {}
|
||||||
|
toolTips.forEach((key) => {
|
||||||
|
if (!filterOut.includes(key)) {
|
||||||
|
result[key] = All[key]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
const lineAndLineToAllowedTransforms: SketchLineHelper['allowedTransforms'] = ({
|
||||||
|
node,
|
||||||
|
pathToNode,
|
||||||
|
}: {
|
||||||
|
node: Program
|
||||||
|
pathToNode: PathToNode
|
||||||
|
}) => {
|
||||||
|
const { node: callExpression } = getNodeFromPath<CallExpression>(
|
||||||
|
node,
|
||||||
|
pathToNode
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
callExpression.type !== 'CallExpression' ||
|
||||||
|
!toolTips.includes(callExpression.callee.name as any)
|
||||||
|
)
|
||||||
|
return {}
|
||||||
|
const fnName = callExpression.callee.name as TooTip
|
||||||
|
const firstArg = callExpression.arguments?.[0]
|
||||||
|
if (
|
||||||
|
firstArg.type !== 'ArrayExpression' &&
|
||||||
|
!(firstArg.type === 'ObjectExpression')
|
||||||
|
)
|
||||||
|
return {}
|
||||||
|
const { val, tag } = getFirstArgValuesForXYFns(callExpression)
|
||||||
|
const [x, y] = val
|
||||||
|
if (x.type !== 'Literal' && y.type !== 'Literal') return {}
|
||||||
|
if (x.type !== 'Literal' && y.type === 'Literal' && fnName === 'line')
|
||||||
|
return {
|
||||||
|
xLine: (args) => createCallWrapper('xLine', x, tag),
|
||||||
|
angledLineOfXLength: (args) =>
|
||||||
|
createCallWrapper('angledLineOfXLength', [args[0], x], tag),
|
||||||
|
}
|
||||||
|
if (x.type !== 'Literal' && y.type === 'Literal' && fnName === 'lineTo')
|
||||||
|
return {
|
||||||
|
xLineTo: (args) => createCallWrapper('xLineTo', x, tag),
|
||||||
|
angledLineToX: (args) =>
|
||||||
|
createCallWrapper('angledLineToX', [args[0], x], tag),
|
||||||
|
}
|
||||||
|
if (x.type === 'Literal' && y.type !== 'Literal' && fnName === 'line')
|
||||||
|
return {
|
||||||
|
yLine: (args) => createCallWrapper('yLine', y, tag),
|
||||||
|
angledLineOfYLength: (args) =>
|
||||||
|
createCallWrapper('angledLineOfYLength', [args[0], y], tag),
|
||||||
|
}
|
||||||
|
if (x.type === 'Literal' && y.type !== 'Literal' && fnName === 'lineTo')
|
||||||
|
return {
|
||||||
|
yLineTo: (args) => createCallWrapper('yLineTo', y, tag),
|
||||||
|
angledLineToY: (args) =>
|
||||||
|
createCallWrapper('angledLineToY', [args[0], y], tag),
|
||||||
|
}
|
||||||
|
if (x.type === 'Literal' && y.type === 'Literal')
|
||||||
|
return tranformerDefaults([], tag)
|
||||||
|
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const xyLineAllowedTransforms: SketchLineHelper['allowedTransforms'] = ({
|
||||||
|
node,
|
||||||
|
pathToNode,
|
||||||
|
}: {
|
||||||
|
node: Program
|
||||||
|
pathToNode: PathToNode
|
||||||
|
}) => {
|
||||||
|
const { node: callExpression } = getNodeFromPath<CallExpression>(
|
||||||
|
node,
|
||||||
|
pathToNode
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
callExpression.type !== 'CallExpression' ||
|
||||||
|
!toolTips.includes(callExpression.callee.name as any)
|
||||||
|
)
|
||||||
|
return {}
|
||||||
|
const fnName = callExpression.callee.name
|
||||||
|
const firstArg = callExpression.arguments?.[0]
|
||||||
|
if (firstArg.type !== 'Literal' && !(firstArg.type === 'ObjectExpression'))
|
||||||
|
return {}
|
||||||
|
const { val, tag } = getFirstArgValuesForXYLineFns(callExpression)
|
||||||
|
const x = val
|
||||||
|
if (x.type !== 'Literal' && fnName === 'xLine')
|
||||||
|
return {
|
||||||
|
xLine: (args) => createCallWrapper('xLine', x, tag),
|
||||||
|
line: (args) => createCallWrapper('line', [x, args[1]], tag),
|
||||||
|
angledLineOfXLength: (args) =>
|
||||||
|
createCallWrapper('angledLineOfXLength', [args[0], x], tag),
|
||||||
|
}
|
||||||
|
if (x.type !== 'Literal' && fnName === 'xLineTo')
|
||||||
|
return {
|
||||||
|
xLineTo: (args) => createCallWrapper('xLineTo', x, tag),
|
||||||
|
lineTo: (args) => createCallWrapper('lineTo', [x, args[1]], tag),
|
||||||
|
angledLineToX: (args) =>
|
||||||
|
createCallWrapper('angledLineToX', [args[0], x], tag),
|
||||||
|
}
|
||||||
|
if (x.type !== 'Literal' && fnName === 'yLine')
|
||||||
|
return {
|
||||||
|
yLine: (args) => createCallWrapper('yLine', x, tag),
|
||||||
|
line: (args) => createCallWrapper('line', [args[0], x], tag),
|
||||||
|
angledLineOfYLength: (args) =>
|
||||||
|
createCallWrapper('angledLineOfYLength', [args[0], x], tag),
|
||||||
|
}
|
||||||
|
if (x.type !== 'Literal' && fnName === 'yLineTo')
|
||||||
|
return {
|
||||||
|
yLineTo: (args) => createCallWrapper('yLineTo', x, tag),
|
||||||
|
lineTo: (args) => createCallWrapper('lineTo', [args[0], x], tag),
|
||||||
|
angledLineToY: (args) =>
|
||||||
|
createCallWrapper('angledLineToY', [args[0], x], tag),
|
||||||
|
}
|
||||||
|
if (x.type === 'Literal' && fnName.startsWith('yLine'))
|
||||||
|
return tranformerDefaults(['yLine'], tag)
|
||||||
|
if (x.type === 'Literal' && fnName.startsWith('xLine'))
|
||||||
|
return tranformerDefaults(['xLine'], tag)
|
||||||
|
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const angledLineAllowedTransforms: SketchLineHelper['allowedTransforms'] = ({
|
||||||
|
node,
|
||||||
|
pathToNode,
|
||||||
|
}: {
|
||||||
|
node: Program
|
||||||
|
pathToNode: PathToNode
|
||||||
|
}) => {
|
||||||
|
const { node: callExpression } = getNodeFromPath<CallExpression>(
|
||||||
|
node,
|
||||||
|
pathToNode
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
callExpression.type !== 'CallExpression' ||
|
||||||
|
!toolTips.includes(callExpression.callee.name as any)
|
||||||
|
)
|
||||||
|
return {}
|
||||||
|
const fnName = callExpression.callee.name as TooTip
|
||||||
|
const firstArg = callExpression.arguments?.[0]
|
||||||
|
if (
|
||||||
|
firstArg.type !== 'ArrayExpression' &&
|
||||||
|
!(firstArg.type === 'ObjectExpression')
|
||||||
|
)
|
||||||
|
return {}
|
||||||
|
const { val, tag } = getFirstArgValuesForAngleFns(callExpression)
|
||||||
|
const [angle, length] = val
|
||||||
|
if (angle.type !== 'Literal' && length.type !== 'Literal') return {}
|
||||||
|
if (angle.type !== 'Literal' && length.type === 'Literal')
|
||||||
|
return {
|
||||||
|
angledLineOfYLength: (args) =>
|
||||||
|
createCallWrapper('angledLineOfYLength', [angle, args[1]], tag),
|
||||||
|
angledLineOfXLength: (args) =>
|
||||||
|
createCallWrapper('angledLineOfXLength', [angle, args[1]], tag),
|
||||||
|
angledLineToY: (args) =>
|
||||||
|
createCallWrapper('angledLineToY', [angle, args[1]], tag),
|
||||||
|
angledLineToX: (args) =>
|
||||||
|
createCallWrapper('angledLineToX', [angle, args[1]], tag),
|
||||||
|
angledLine: (args) =>
|
||||||
|
createCallWrapper('angledLine', [angle, args[1]], tag),
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
angle.type === 'Literal' &&
|
||||||
|
length.type !== 'Literal' &&
|
||||||
|
fnName === 'angledLine'
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
angledLine: (args) =>
|
||||||
|
createCallWrapper('angledLine', [args[0], length], tag),
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
angle.type === 'Literal' &&
|
||||||
|
length.type !== 'Literal' &&
|
||||||
|
fnName === 'angledLineOfXLength'
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
angledLineOfXLength: (args) =>
|
||||||
|
createCallWrapper('angledLineOfXLength', [angle, args[1]], tag),
|
||||||
|
line: (args) => createCallWrapper('line', [length, args[1]], tag),
|
||||||
|
xLine: (args) => createCallWrapper('xLine', length, tag),
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
angle.type === 'Literal' &&
|
||||||
|
length.type !== 'Literal' &&
|
||||||
|
fnName === 'angledLineOfYLength'
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
angledLineOfYLength: (args) =>
|
||||||
|
createCallWrapper('angledLineOfYLength', [args[0], length], tag),
|
||||||
|
line: (args) => createCallWrapper('line', [args[0], length], tag),
|
||||||
|
yLine: (args) => createCallWrapper('yLine', length, tag),
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
angle.type === 'Literal' &&
|
||||||
|
length.type !== 'Literal' &&
|
||||||
|
fnName === 'angledLineToX'
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
angledLineToX: (args) =>
|
||||||
|
createCallWrapper('angledLineToX', [args[0], length], tag),
|
||||||
|
lineTo: (args) => createCallWrapper('lineTo', [length, args[1]], tag),
|
||||||
|
xLineTo: (args) => createCallWrapper('xLineTo', length, tag),
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
angle.type === 'Literal' &&
|
||||||
|
length.type !== 'Literal' &&
|
||||||
|
fnName === 'angledLineToY'
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
angledLineToY: (args) =>
|
||||||
|
createCallWrapper('angledLineToY', [args[0], length], tag),
|
||||||
|
lineTo: (args) => createCallWrapper('lineTo', [args[0], length], tag),
|
||||||
|
yLineTo: (args) => createCallWrapper('yLineTo', length, tag),
|
||||||
|
}
|
||||||
|
if (angle.type === 'Literal' && length.type === 'Literal')
|
||||||
|
return tranformerDefaults([], tag)
|
||||||
|
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
export const lineTo: SketchLineHelper = {
|
export const lineTo: SketchLineHelper = {
|
||||||
fn: (
|
fn: (
|
||||||
{ sourceRange, programMemory },
|
{ sourceRange, programMemory },
|
||||||
@ -128,6 +422,7 @@ export const lineTo: SketchLineHelper = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
addTag: addTagWithTo('default'),
|
addTag: addTagWithTo('default'),
|
||||||
|
allowedTransforms: lineAndLineToAllowedTransforms,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const line: SketchLineHelper = {
|
export const line: SketchLineHelper = {
|
||||||
@ -234,6 +529,7 @@ export const line: SketchLineHelper = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
addTag: addTagWithTo('default'),
|
addTag: addTagWithTo('default'),
|
||||||
|
allowedTransforms: lineAndLineToAllowedTransforms,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const xLineTo: SketchLineHelper = {
|
export const xLineTo: SketchLineHelper = {
|
||||||
@ -256,18 +552,23 @@ export const xLineTo: SketchLineHelper = {
|
|||||||
const [xVal, tag] = typeof data !== 'number' ? [data.to, data.tag] : [data]
|
const [xVal, tag] = typeof data !== 'number' ? [data.to, data.tag] : [data]
|
||||||
return lineTo.fn(meta, { to: [xVal, from[1]], tag }, previousSketch)
|
return lineTo.fn(meta, { to: [xVal, from[1]], tag }, previousSketch)
|
||||||
},
|
},
|
||||||
add: ({ node, previousProgramMemory, pathToNode, to }) => {
|
add: ({ node, pathToNode, to, replaceExisting, createCallback }) => {
|
||||||
const _node = { ...node }
|
const _node = { ...node }
|
||||||
const { node: pipe } = getNodeFromPath<PipeExpression>(
|
const getNode = getNodeFromPathCurry(_node, pathToNode)
|
||||||
_node,
|
const { node: pipe } = getNode<PipeExpression>('PipeExpression')
|
||||||
pathToNode,
|
|
||||||
'PipeExpression'
|
const newVal = createLiteral(roundOff(to[0], 2))
|
||||||
)
|
const firstArg = newVal
|
||||||
const newLine = createCallExpression('xLineTo', [
|
const newLine = createCallback
|
||||||
createLiteral(to[0]),
|
? createCallback([firstArg, firstArg])
|
||||||
createPipeSubstitution(),
|
: createCallExpression('xLineTo', [firstArg, createPipeSubstitution()])
|
||||||
])
|
|
||||||
|
const callIndex = getLastIndex(pathToNode)
|
||||||
|
if (replaceExisting) {
|
||||||
|
pipe.body[callIndex] = newLine
|
||||||
|
} else {
|
||||||
pipe.body = [...pipe.body, newLine]
|
pipe.body = [...pipe.body, newLine]
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
modifiedAst: _node,
|
modifiedAst: _node,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
@ -291,6 +592,7 @@ export const xLineTo: SketchLineHelper = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
addTag: addTagWithTo('default'),
|
addTag: addTagWithTo('default'),
|
||||||
|
allowedTransforms: xyLineAllowedTransforms,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const yLineTo: SketchLineHelper = {
|
export const yLineTo: SketchLineHelper = {
|
||||||
@ -313,18 +615,21 @@ export const yLineTo: SketchLineHelper = {
|
|||||||
const [yVal, tag] = typeof data !== 'number' ? [data.to, data.tag] : [data]
|
const [yVal, tag] = typeof data !== 'number' ? [data.to, data.tag] : [data]
|
||||||
return lineTo.fn(meta, { to: [from[0], yVal], tag }, previousSketch)
|
return lineTo.fn(meta, { to: [from[0], yVal], tag }, previousSketch)
|
||||||
},
|
},
|
||||||
add: ({ node, previousProgramMemory, pathToNode, to }) => {
|
add: ({ node, pathToNode, to, replaceExisting, createCallback }) => {
|
||||||
const _node = { ...node }
|
const _node = { ...node }
|
||||||
const { node: pipe } = getNodeFromPath<PipeExpression>(
|
const getNode = getNodeFromPathCurry(_node, pathToNode)
|
||||||
_node,
|
const { node: pipe } = getNode<PipeExpression>('PipeExpression')
|
||||||
pathToNode,
|
|
||||||
'PipeExpression'
|
const newVal = createLiteral(roundOff(to[1], 2))
|
||||||
)
|
const newLine = createCallback
|
||||||
const newLine = createCallExpression('yLineTo', [
|
? createCallback([newVal, newVal])
|
||||||
createLiteral(to[1]),
|
: createCallExpression('yLineTo', [newVal, createPipeSubstitution()])
|
||||||
createPipeSubstitution(),
|
const callIndex = getLastIndex(pathToNode)
|
||||||
])
|
if (replaceExisting) {
|
||||||
|
pipe.body[callIndex] = newLine
|
||||||
|
} else {
|
||||||
pipe.body = [...pipe.body, newLine]
|
pipe.body = [...pipe.body, newLine]
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
modifiedAst: _node,
|
modifiedAst: _node,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
@ -348,6 +653,7 @@ export const yLineTo: SketchLineHelper = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
addTag: addTagWithTo('default'),
|
addTag: addTagWithTo('default'),
|
||||||
|
allowedTransforms: xyLineAllowedTransforms,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const xLine: SketchLineHelper = {
|
export const xLine: SketchLineHelper = {
|
||||||
@ -366,37 +672,24 @@ export const xLine: SketchLineHelper = {
|
|||||||
typeof data !== 'number' ? [data.length, data.tag] : [data]
|
typeof data !== 'number' ? [data.length, data.tag] : [data]
|
||||||
return line.fn(meta, { to: [xVal, 0], tag }, previousSketch)
|
return line.fn(meta, { to: [xVal, 0], tag }, previousSketch)
|
||||||
},
|
},
|
||||||
add: ({
|
add: ({ node, pathToNode, to, from, replaceExisting, createCallback }) => {
|
||||||
node,
|
|
||||||
previousProgramMemory,
|
|
||||||
pathToNode,
|
|
||||||
to: length,
|
|
||||||
// from: [number, number],
|
|
||||||
}) => {
|
|
||||||
const _node = { ...node }
|
const _node = { ...node }
|
||||||
const { node: pipe } = getNodeFromPath<PipeExpression>(
|
const getNode = getNodeFromPathCurry(_node, pathToNode)
|
||||||
_node,
|
if (!from) throw new Error('no from') // todo #29 remove
|
||||||
pathToNode,
|
const { node: pipe } = getNode<PipeExpression>('PipeExpression')
|
||||||
'PipeExpression'
|
|
||||||
)
|
const newVal = createLiteral(roundOff(to[0] - from[0], 2))
|
||||||
const { node: varDec } = getNodeFromPath<VariableDeclarator>(
|
const firstArg = newVal
|
||||||
_node,
|
const newLine = createCallback
|
||||||
pathToNode,
|
? createCallback([firstArg, firstArg])
|
||||||
'VariableDeclarator'
|
: createCallExpression('xLine', [firstArg, createPipeSubstitution()])
|
||||||
)
|
const callIndex = getLastIndex(pathToNode)
|
||||||
const variableName = varDec.id.name
|
if (replaceExisting) {
|
||||||
const sketch = previousProgramMemory?.root?.[variableName]
|
pipe.body[callIndex] = newLine
|
||||||
if (sketch.type !== 'sketchGroup') throw new Error('not a sketchGroup')
|
} else {
|
||||||
const last = sketch.value[sketch.value.length - 1]
|
|
||||||
const newLine = createCallExpression('xLine', [
|
|
||||||
createLiteral(roundOff(length[0] - last.to[0], 2)),
|
|
||||||
createPipeSubstitution(),
|
|
||||||
])
|
|
||||||
pipe.body = [...pipe.body, newLine]
|
pipe.body = [...pipe.body, newLine]
|
||||||
return {
|
|
||||||
modifiedAst: _node,
|
|
||||||
pathToNode,
|
|
||||||
}
|
}
|
||||||
|
return { modifiedAst: _node, pathToNode }
|
||||||
},
|
},
|
||||||
updateArgs: ({ node, pathToNode, to, from }) => {
|
updateArgs: ({ node, pathToNode, to, from }) => {
|
||||||
const _node = { ...node }
|
const _node = { ...node }
|
||||||
@ -416,6 +709,7 @@ export const xLine: SketchLineHelper = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
addTag: addTagWithTo('length'),
|
addTag: addTagWithTo('length'),
|
||||||
|
allowedTransforms: xyLineAllowedTransforms,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const yLine: SketchLineHelper = {
|
export const yLine: SketchLineHelper = {
|
||||||
@ -435,37 +729,22 @@ export const yLine: SketchLineHelper = {
|
|||||||
typeof data !== 'number' ? [data.length, data.tag] : [data]
|
typeof data !== 'number' ? [data.length, data.tag] : [data]
|
||||||
return line.fn(meta, { to: [0, yVal], tag }, previousSketch)
|
return line.fn(meta, { to: [0, yVal], tag }, previousSketch)
|
||||||
},
|
},
|
||||||
add: ({
|
add: ({ node, pathToNode, to, from, replaceExisting, createCallback }) => {
|
||||||
node,
|
|
||||||
previousProgramMemory,
|
|
||||||
pathToNode,
|
|
||||||
to,
|
|
||||||
// from: [number, number],
|
|
||||||
}) => {
|
|
||||||
const _node = { ...node }
|
const _node = { ...node }
|
||||||
const { node: pipe } = getNodeFromPath<PipeExpression>(
|
const getNode = getNodeFromPathCurry(_node, pathToNode)
|
||||||
_node,
|
if (!from) throw new Error('no from') // todo #29 remove
|
||||||
pathToNode,
|
const { node: pipe } = getNode<PipeExpression>('PipeExpression')
|
||||||
'PipeExpression'
|
const newVal = createLiteral(roundOff(to[1] - from[1], 2))
|
||||||
)
|
const newLine = createCallback
|
||||||
const { node: varDec } = getNodeFromPath<VariableDeclarator>(
|
? createCallback([newVal, newVal])
|
||||||
_node,
|
: createCallExpression('yLine', [newVal, createPipeSubstitution()])
|
||||||
pathToNode,
|
const callIndex = getLastIndex(pathToNode)
|
||||||
'VariableDeclarator'
|
if (replaceExisting) {
|
||||||
)
|
pipe.body[callIndex] = newLine
|
||||||
const variableName = varDec.id.name
|
} else {
|
||||||
const sketch = previousProgramMemory?.root?.[variableName]
|
|
||||||
if (sketch.type !== 'sketchGroup') throw new Error('not a sketchGroup')
|
|
||||||
const last = sketch.value[sketch.value.length - 1]
|
|
||||||
const newLine = createCallExpression('yLine', [
|
|
||||||
createLiteral(roundOff(to[1] - last.to[1], 2)),
|
|
||||||
createPipeSubstitution(),
|
|
||||||
])
|
|
||||||
pipe.body = [...pipe.body, newLine]
|
pipe.body = [...pipe.body, newLine]
|
||||||
return {
|
|
||||||
modifiedAst: _node,
|
|
||||||
pathToNode,
|
|
||||||
}
|
}
|
||||||
|
return { modifiedAst: _node, pathToNode }
|
||||||
},
|
},
|
||||||
updateArgs: ({ node, pathToNode, to, from }) => {
|
updateArgs: ({ node, pathToNode, to, from }) => {
|
||||||
const _node = { ...node }
|
const _node = { ...node }
|
||||||
@ -485,6 +764,7 @@ export const yLine: SketchLineHelper = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
addTag: addTagWithTo('length'),
|
addTag: addTagWithTo('length'),
|
||||||
|
allowedTransforms: xyLineAllowedTransforms,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const angledLine: SketchLineHelper = {
|
export const angledLine: SketchLineHelper = {
|
||||||
@ -575,7 +855,7 @@ export const angledLine: SketchLineHelper = {
|
|||||||
},
|
},
|
||||||
updateArgs: ({ node, pathToNode, to, from }) => {
|
updateArgs: ({ node, pathToNode, to, from }) => {
|
||||||
const _node = { ...node }
|
const _node = { ...node }
|
||||||
const { node: callExpression, path } = getNodeFromPath<CallExpression>(
|
const { node: callExpression } = getNodeFromPath<CallExpression>(
|
||||||
_node,
|
_node,
|
||||||
pathToNode
|
pathToNode
|
||||||
)
|
)
|
||||||
@ -597,6 +877,7 @@ export const angledLine: SketchLineHelper = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
addTag: addTagWithTo('angleLength'),
|
addTag: addTagWithTo('angleLength'),
|
||||||
|
allowedTransforms: angledLineAllowedTransforms,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const angledLineOfXLength: SketchLineHelper = {
|
export const angledLineOfXLength: SketchLineHelper = {
|
||||||
@ -683,6 +964,7 @@ export const angledLineOfXLength: SketchLineHelper = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
addTag: addTagWithTo('angleLength'),
|
addTag: addTagWithTo('angleLength'),
|
||||||
|
allowedTransforms: angledLineAllowedTransforms,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const angledLineOfYLength: SketchLineHelper = {
|
export const angledLineOfYLength: SketchLineHelper = {
|
||||||
@ -768,6 +1050,7 @@ export const angledLineOfYLength: SketchLineHelper = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
addTag: addTagWithTo('angleLength'),
|
addTag: addTagWithTo('angleLength'),
|
||||||
|
allowedTransforms: angledLineAllowedTransforms,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const angledLineToX: SketchLineHelper = {
|
export const angledLineToX: SketchLineHelper = {
|
||||||
@ -860,6 +1143,7 @@ export const angledLineToX: SketchLineHelper = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
addTag: addTagWithTo('angleTo'),
|
addTag: addTagWithTo('angleTo'),
|
||||||
|
allowedTransforms: angledLineAllowedTransforms,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const angledLineToY: SketchLineHelper = {
|
export const angledLineToY: SketchLineHelper = {
|
||||||
@ -952,6 +1236,7 @@ export const angledLineToY: SketchLineHelper = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
addTag: addTagWithTo('angleTo'),
|
addTag: addTagWithTo('angleTo'),
|
||||||
|
allowedTransforms: angledLineAllowedTransforms,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const sketchLineHelperMap: { [key: string]: SketchLineHelper } = {
|
export const sketchLineHelperMap: { [key: string]: SketchLineHelper } = {
|
||||||
@ -998,26 +1283,76 @@ export function changeSketchArguments(
|
|||||||
throw new Error('not a sketch line helper')
|
throw new Error('not a sketch line helper')
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toolTipModification(
|
interface CreateLineFnCallArgs {
|
||||||
node: Program,
|
node: Program
|
||||||
previousProgramMemory: ProgramMemory,
|
programMemory: ProgramMemory
|
||||||
to: [number, number],
|
to: [number, number]
|
||||||
guiMode: GuiModes
|
from: [number, number]
|
||||||
): { modifiedAst: Program } {
|
fnName: TooTip
|
||||||
if (guiMode.mode !== 'sketch') throw new Error('expected sketch mode')
|
pathToNode: PathToNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addNewSketchLn({
|
||||||
|
node,
|
||||||
|
programMemory: previousProgramMemory,
|
||||||
|
to,
|
||||||
|
fnName,
|
||||||
|
pathToNode,
|
||||||
|
}: Omit<CreateLineFnCallArgs, 'from'>): { modifiedAst: Program } {
|
||||||
|
const { add } = sketchLineHelperMap[fnName]
|
||||||
|
const { node: varDec } = getNodeFromPath<VariableDeclarator>(
|
||||||
|
node,
|
||||||
|
pathToNode,
|
||||||
|
'VariableDeclarator'
|
||||||
|
)
|
||||||
|
const variableName = varDec.id.name
|
||||||
|
const sketch = previousProgramMemory?.root?.[variableName]
|
||||||
|
if (sketch.type !== 'sketchGroup') throw new Error('not a sketchGroup')
|
||||||
|
const last = sketch.value[sketch.value.length - 1]
|
||||||
|
const from = last.to
|
||||||
|
|
||||||
const mode = guiMode.sketchMode
|
|
||||||
if (mode in sketchLineHelperMap && mode !== 'selectFace') {
|
|
||||||
const { add } = sketchLineHelperMap[guiMode.sketchMode]
|
|
||||||
return add({
|
return add({
|
||||||
node,
|
node,
|
||||||
previousProgramMemory,
|
previousProgramMemory,
|
||||||
pathToNode: guiMode.pathToNode,
|
pathToNode,
|
||||||
to,
|
to,
|
||||||
|
from,
|
||||||
|
replaceExisting: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error('not a sketch line helper')
|
export function replaceSketchLine({
|
||||||
|
node,
|
||||||
|
programMemory,
|
||||||
|
sourceRange,
|
||||||
|
fnName,
|
||||||
|
to,
|
||||||
|
from,
|
||||||
|
createCallback,
|
||||||
|
}: {
|
||||||
|
node: Program
|
||||||
|
programMemory: ProgramMemory
|
||||||
|
sourceRange: SourceRange
|
||||||
|
fnName: TooTip
|
||||||
|
to: [number, number]
|
||||||
|
from: [number, number]
|
||||||
|
createCallback: TransformCallback
|
||||||
|
}): { modifiedAst: Program } {
|
||||||
|
if (!toolTips.includes(fnName)) throw new Error('not a tooltip')
|
||||||
|
const _node = { ...node }
|
||||||
|
const thePath = getNodePathFromSourceRange(_node, sourceRange)
|
||||||
|
|
||||||
|
const { add } = sketchLineHelperMap[fnName]
|
||||||
|
const { modifiedAst } = add({
|
||||||
|
node: _node,
|
||||||
|
previousProgramMemory: programMemory,
|
||||||
|
pathToNode: thePath,
|
||||||
|
to,
|
||||||
|
from,
|
||||||
|
replaceExisting: true,
|
||||||
|
createCallback,
|
||||||
|
})
|
||||||
|
return { modifiedAst }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addTagForSketchOnFace(
|
export function addTagForSketchOnFace(
|
||||||
@ -1211,3 +1546,90 @@ export function getXComponent(
|
|||||||
const sign = normalisedAngle > 180 && normalisedAngle <= 360 ? -1 : 1
|
const sign = normalisedAngle > 180 && normalisedAngle <= 360 ? -1 : 1
|
||||||
return [sign * xComponent, sign * yComponent]
|
return [sign * xComponent, sign * yComponent]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getFirstArgValuesForXYFns(callExpression: CallExpression): {
|
||||||
|
val: [Value, Value]
|
||||||
|
tag?: Value
|
||||||
|
} {
|
||||||
|
// used for lineTo, line
|
||||||
|
const firstArg = callExpression.arguments[0]
|
||||||
|
if (firstArg.type === 'ArrayExpression') {
|
||||||
|
return { val: [firstArg.elements[0], firstArg.elements[1]] }
|
||||||
|
}
|
||||||
|
if (firstArg.type === 'ObjectExpression') {
|
||||||
|
const to = firstArg.properties.find((p) => p.key.name === 'to')?.value
|
||||||
|
const tag = firstArg.properties.find((p) => p.key.name === 'tag')?.value
|
||||||
|
if (to?.type === 'ArrayExpression') {
|
||||||
|
const [x, y] = to.elements
|
||||||
|
return { val: [x, y], tag }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error('expected ArrayExpression or ObjectExpression')
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFirstArgValuesForAngleFns(callExpression: CallExpression): {
|
||||||
|
val: [Value, Value]
|
||||||
|
tag?: Value
|
||||||
|
} {
|
||||||
|
// used for angledLine, angledLineOfXLength, angledLineToX, angledLineOfYLength, angledLineToY
|
||||||
|
const firstArg = callExpression.arguments[0]
|
||||||
|
if (firstArg.type === 'ArrayExpression') {
|
||||||
|
return { val: [firstArg.elements[0], firstArg.elements[1]] }
|
||||||
|
}
|
||||||
|
if (firstArg.type === 'ObjectExpression') {
|
||||||
|
const tag = firstArg.properties.find((p) => p.key.name === 'tag')?.value
|
||||||
|
const angle = firstArg.properties.find((p) => p.key.name === 'angle')?.value
|
||||||
|
const secondArgName = ['angledLineToX', 'angledLineToY'].includes(
|
||||||
|
callExpression?.callee?.name as TooTip
|
||||||
|
)
|
||||||
|
? 'to'
|
||||||
|
: 'length'
|
||||||
|
const length = firstArg.properties.find(
|
||||||
|
(p) => p.key.name === secondArgName
|
||||||
|
)?.value
|
||||||
|
if (angle && length) {
|
||||||
|
return { val: [angle, length], tag }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error('expected ArrayExpression or ObjectExpression')
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFirstArgValuesForXYLineFns(callExpression: CallExpression): {
|
||||||
|
val: Value
|
||||||
|
tag?: Value
|
||||||
|
} {
|
||||||
|
// used for xLine, yLine, xLineTo, yLineTo
|
||||||
|
const firstArg = callExpression.arguments[0]
|
||||||
|
if (firstArg.type !== 'ObjectExpression') {
|
||||||
|
return { val: firstArg }
|
||||||
|
}
|
||||||
|
const tag = firstArg.properties.find((p) => p.key.name === 'tag')?.value
|
||||||
|
const secondArgName = ['xLineTo', 'yLineTo'].includes(
|
||||||
|
// const secondArgName = ['xLineTo', 'yLineTo', 'angledLineToX', 'angledLineToY'].includes(
|
||||||
|
callExpression?.callee?.name
|
||||||
|
)
|
||||||
|
? 'to'
|
||||||
|
: 'length'
|
||||||
|
const length = firstArg.properties.find(
|
||||||
|
(p) => p.key.name === secondArgName
|
||||||
|
)?.value
|
||||||
|
if (length) {
|
||||||
|
return { val: length, tag }
|
||||||
|
}
|
||||||
|
throw new Error('expected ArrayExpression or ObjectExpression')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function allowedTransforms(
|
||||||
|
a: ModifyAstBase
|
||||||
|
): Partial<SketchCallTransfromMap> {
|
||||||
|
const { node, pathToNode } = a
|
||||||
|
const { node: callExpression } = getNodeFromPath<CallExpression>(
|
||||||
|
node,
|
||||||
|
pathToNode
|
||||||
|
)
|
||||||
|
if (callExpression.type !== 'CallExpression') return {}
|
||||||
|
const expressionName = callExpression?.callee?.name
|
||||||
|
const fn = sketchLineHelperMap?.[expressionName]?.allowedTransforms
|
||||||
|
if (fn) return fn(a)
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
372
src/lang/std/sketchConstraints.test.ts
Normal file
372
src/lang/std/sketchConstraints.test.ts
Normal file
@ -0,0 +1,372 @@
|
|||||||
|
import {
|
||||||
|
abstractSyntaxTree,
|
||||||
|
getNodePathFromSourceRange,
|
||||||
|
} from '../abstractSyntaxTree'
|
||||||
|
import { executor } from '../executor'
|
||||||
|
import { lexer } from '../tokeniser'
|
||||||
|
import { swapSketchHelper } from './sketchConstraints'
|
||||||
|
import { allowedTransforms } from './sketch'
|
||||||
|
import { recast } from '../recast'
|
||||||
|
import { initPromise } from '../rust'
|
||||||
|
import { TooTip } from '../../useStore'
|
||||||
|
|
||||||
|
beforeAll(() => initPromise)
|
||||||
|
|
||||||
|
// testing helper function
|
||||||
|
function testingSwapSketchFnCall({
|
||||||
|
inputCode,
|
||||||
|
callToSwap,
|
||||||
|
// expectedNewCall,
|
||||||
|
toFnCallName,
|
||||||
|
}: {
|
||||||
|
inputCode: string
|
||||||
|
callToSwap: string
|
||||||
|
// expectedNewCall: string
|
||||||
|
toFnCallName: TooTip
|
||||||
|
}): {
|
||||||
|
newCode: string
|
||||||
|
originalRange: [number, number]
|
||||||
|
} {
|
||||||
|
const startIndex = inputCode.indexOf(callToSwap)
|
||||||
|
const range: [number, number] = [startIndex, startIndex + callToSwap.length]
|
||||||
|
const tokens = lexer(inputCode)
|
||||||
|
const ast = abstractSyntaxTree(tokens)
|
||||||
|
const programMemory = executor(ast)
|
||||||
|
const pathToNode = getNodePathFromSourceRange(ast, range)
|
||||||
|
const _allowedTransforms = allowedTransforms({
|
||||||
|
node: ast,
|
||||||
|
previousProgramMemory: programMemory,
|
||||||
|
pathToNode,
|
||||||
|
})
|
||||||
|
const transformCallback = _allowedTransforms[toFnCallName]
|
||||||
|
if (!transformCallback) throw new Error('nope')
|
||||||
|
const { modifiedAst } = swapSketchHelper(
|
||||||
|
programMemory,
|
||||||
|
ast,
|
||||||
|
range,
|
||||||
|
toFnCallName,
|
||||||
|
transformCallback
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
newCode: recast(modifiedAst),
|
||||||
|
originalRange: range,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('testing swaping out sketch calls with xLine/xLineTo', () => {
|
||||||
|
const bigExampleArr = [
|
||||||
|
`const part001 = startSketchAt([0, 0])`,
|
||||||
|
` |> lineTo({ to: [1, 1], tag: 'abc1' }, %)`,
|
||||||
|
` |> line({ to: [-2.04, -0.7], tag: 'abc2' }, %)`,
|
||||||
|
` |> angledLine({`,
|
||||||
|
` angle: 157,`,
|
||||||
|
` length: 1.69,`,
|
||||||
|
` tag: 'abc3'`,
|
||||||
|
`}, %)`,
|
||||||
|
` |> angledLineOfXLength({`,
|
||||||
|
` angle: 217,`,
|
||||||
|
` length: 0.86,`,
|
||||||
|
` tag: 'abc4'`,
|
||||||
|
`}, %)`,
|
||||||
|
` |> angledLineOfYLength({`,
|
||||||
|
` angle: 104,`,
|
||||||
|
` length: 1.58,`,
|
||||||
|
` tag: 'abc5'`,
|
||||||
|
`}, %)`,
|
||||||
|
` |> angledLineToX({ angle: 55, to: -2.89, tag: 'abc6' }, %)`,
|
||||||
|
` |> angledLineToY({ angle: 330, to: 2.53, tag: 'abc7' }, %)`,
|
||||||
|
` |> xLine({ length: 1.47, tag: 'abc8' }, %)`,
|
||||||
|
` |> yLine({ length: 1.57, tag: 'abc9' }, %)`,
|
||||||
|
` |> xLineTo({ to: 1.49, tag: 'abc10' }, %)`,
|
||||||
|
` |> yLineTo({ to: 2.64, tag: 'abc11' }, %)`,
|
||||||
|
` |> lineTo([2.55, 3.58], %) // lineTo`,
|
||||||
|
` |> line([0.73, -0.75], %)`,
|
||||||
|
` |> angledLine([63, 1.38], %) // angledLine`,
|
||||||
|
` |> angledLineOfXLength([319, 1.15], %) // angledLineOfXLength`,
|
||||||
|
` |> angledLineOfYLength([50, 1.35], %) // angledLineOfYLength`,
|
||||||
|
` |> angledLineToX([291, 6.66], %) // angledLineToX`,
|
||||||
|
` |> angledLineToY([228, 2.14], %) // angledLineToY`,
|
||||||
|
` |> xLine(-1.33, %)`,
|
||||||
|
` |> yLine(-1.07, %)`,
|
||||||
|
` |> xLineTo(3.27, %)`,
|
||||||
|
` |> yLineTo(2.14, %)`,
|
||||||
|
`show(part001)`,
|
||||||
|
]
|
||||||
|
const bigExample = bigExampleArr.join('\n')
|
||||||
|
it('line with tag converts to xLine', () => {
|
||||||
|
const callToSwap = "line({ to: [-2.04, -0.7], tag: 'abc2' }, %)"
|
||||||
|
const expectedLine = "xLine({ length: -2.04, tag: 'abc2' }, %)"
|
||||||
|
const { newCode, originalRange } = testingSwapSketchFnCall({
|
||||||
|
inputCode: bigExample,
|
||||||
|
callToSwap,
|
||||||
|
toFnCallName: 'xLine',
|
||||||
|
})
|
||||||
|
expect(newCode).toContain(expectedLine)
|
||||||
|
// new line should start at the same place as the old line
|
||||||
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
||||||
|
})
|
||||||
|
it('line w/o tag converts to xLine', () => {
|
||||||
|
const callToSwap = 'line([0.73, -0.75], %)'
|
||||||
|
const expectedLine = 'xLine(0.73, %)'
|
||||||
|
const { newCode, originalRange } = testingSwapSketchFnCall({
|
||||||
|
inputCode: bigExample,
|
||||||
|
callToSwap,
|
||||||
|
toFnCallName: 'xLine',
|
||||||
|
})
|
||||||
|
expect(newCode).toContain(expectedLine)
|
||||||
|
// new line should start at the same place as the old line
|
||||||
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
||||||
|
})
|
||||||
|
it('lineTo with tag converts to xLineTo', () => {
|
||||||
|
const { newCode, originalRange } = testingSwapSketchFnCall({
|
||||||
|
inputCode: bigExample,
|
||||||
|
callToSwap: "lineTo({ to: [1, 1], tag: 'abc1' }, %)",
|
||||||
|
toFnCallName: 'xLineTo',
|
||||||
|
})
|
||||||
|
const expectedLine = "xLineTo({ to: 1, tag: 'abc1' }, %)"
|
||||||
|
expect(newCode).toContain(expectedLine)
|
||||||
|
// new line should start at the same place as the old line
|
||||||
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
||||||
|
})
|
||||||
|
it('lineTo w/o tag converts to xLineTo', () => {
|
||||||
|
const { newCode, originalRange } = testingSwapSketchFnCall({
|
||||||
|
inputCode: bigExample,
|
||||||
|
callToSwap: 'lineTo([2.55, 3.58], %)',
|
||||||
|
toFnCallName: 'xLineTo',
|
||||||
|
})
|
||||||
|
const expectedLine = 'xLineTo(2.55, %) // lineTo'
|
||||||
|
expect(newCode).toContain(expectedLine)
|
||||||
|
// new line should start at the same place as the old line
|
||||||
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
||||||
|
})
|
||||||
|
it('angledLine with tag converts to xLine', () => {
|
||||||
|
const { newCode, originalRange } = testingSwapSketchFnCall({
|
||||||
|
inputCode: bigExample,
|
||||||
|
callToSwap: [
|
||||||
|
`angledLine({`,
|
||||||
|
` angle: 157,`,
|
||||||
|
` length: 1.69,`,
|
||||||
|
` tag: 'abc3'`,
|
||||||
|
`}, %)`,
|
||||||
|
].join('\n'),
|
||||||
|
toFnCallName: 'xLine',
|
||||||
|
})
|
||||||
|
const expectedLine = "xLine({ length: -1.56, tag: 'abc3' }, %)"
|
||||||
|
expect(newCode).toContain(expectedLine)
|
||||||
|
// new line should start at the same place as the old line
|
||||||
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
||||||
|
})
|
||||||
|
it('angledLine w/o tag converts to xLine', () => {
|
||||||
|
const { newCode, originalRange } = testingSwapSketchFnCall({
|
||||||
|
inputCode: bigExample,
|
||||||
|
callToSwap: 'angledLine([63, 1.38], %)',
|
||||||
|
toFnCallName: 'xLine',
|
||||||
|
})
|
||||||
|
const expectedLine = 'xLine(0.63, %) // angledLine'
|
||||||
|
expect(newCode).toContain(expectedLine)
|
||||||
|
// new line should start at the same place as the old line
|
||||||
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
||||||
|
})
|
||||||
|
it('angledLineOfXLength with tag converts to xLine', () => {
|
||||||
|
const { newCode, originalRange } = testingSwapSketchFnCall({
|
||||||
|
inputCode: bigExample,
|
||||||
|
callToSwap: [
|
||||||
|
`angledLineOfXLength({`,
|
||||||
|
` angle: 217,`,
|
||||||
|
` length: 0.86,`,
|
||||||
|
` tag: 'abc4'`,
|
||||||
|
`}, %)`,
|
||||||
|
].join('\n'),
|
||||||
|
toFnCallName: 'xLine',
|
||||||
|
})
|
||||||
|
const expectedLine = "xLine({ length: -0.86, tag: 'abc4' }, %)"
|
||||||
|
// hmm "-0.86" is correct since the angle is 104, but need to make sure this is compatiable `-myVar`
|
||||||
|
expect(newCode).toContain(expectedLine)
|
||||||
|
// new line should start at the same place as the old line
|
||||||
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
||||||
|
})
|
||||||
|
it('angledLineOfXLength w/o tag converts to xLine', () => {
|
||||||
|
const { newCode, originalRange } = testingSwapSketchFnCall({
|
||||||
|
inputCode: bigExample,
|
||||||
|
callToSwap: 'angledLineOfXLength([319, 1.15], %)',
|
||||||
|
toFnCallName: 'xLine',
|
||||||
|
})
|
||||||
|
const expectedLine = 'xLine(1.15, %) // angledLineOfXLength'
|
||||||
|
expect(newCode).toContain(expectedLine)
|
||||||
|
// new line should start at the same place as the old line
|
||||||
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
||||||
|
})
|
||||||
|
it('angledLineOfYLength with tag converts to yLine', () => {
|
||||||
|
const { newCode, originalRange } = testingSwapSketchFnCall({
|
||||||
|
inputCode: bigExample,
|
||||||
|
callToSwap: [
|
||||||
|
`angledLineOfYLength({`,
|
||||||
|
` angle: 104,`,
|
||||||
|
` length: 1.58,`,
|
||||||
|
` tag: 'abc5'`,
|
||||||
|
`}, %)`,
|
||||||
|
].join('\n'),
|
||||||
|
toFnCallName: 'yLine',
|
||||||
|
})
|
||||||
|
const expectedLine = "yLine({ length: 1.58, tag: 'abc5' }, %)"
|
||||||
|
expect(newCode).toContain(expectedLine)
|
||||||
|
// new line should start at the same place as the old line
|
||||||
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
||||||
|
})
|
||||||
|
it('angledLineOfYLength w/o tag converts to yLine', () => {
|
||||||
|
const { newCode, originalRange } = testingSwapSketchFnCall({
|
||||||
|
inputCode: bigExample,
|
||||||
|
callToSwap: 'angledLineOfYLength([50, 1.35], %)',
|
||||||
|
toFnCallName: 'yLine',
|
||||||
|
})
|
||||||
|
const expectedLine = 'yLine(1.35, %) // angledLineOfYLength'
|
||||||
|
expect(newCode).toContain(expectedLine)
|
||||||
|
// new line should start at the same place as the old line
|
||||||
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
||||||
|
})
|
||||||
|
it('angledLineToX with tag converts to xLineTo', () => {
|
||||||
|
const { newCode, originalRange } = testingSwapSketchFnCall({
|
||||||
|
inputCode: bigExample,
|
||||||
|
callToSwap: "angledLineToX({ angle: 55, to: -2.89, tag: 'abc6' }, %)",
|
||||||
|
toFnCallName: 'xLineTo',
|
||||||
|
})
|
||||||
|
const expectedLine = "xLineTo({ to: -2.89, tag: 'abc6' }, %)"
|
||||||
|
expect(newCode).toContain(expectedLine)
|
||||||
|
// new line should start at the same place as the old line
|
||||||
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
||||||
|
})
|
||||||
|
it('angledLineToX w/o tag converts to xLineTo', () => {
|
||||||
|
const { newCode, originalRange } = testingSwapSketchFnCall({
|
||||||
|
inputCode: bigExample,
|
||||||
|
callToSwap: 'angledLineToX([291, 6.66], %)',
|
||||||
|
toFnCallName: 'xLineTo',
|
||||||
|
})
|
||||||
|
const expectedLine = 'xLineTo(6.66, %) // angledLineToX'
|
||||||
|
expect(newCode).toContain(expectedLine)
|
||||||
|
// new line should start at the same place as the old line
|
||||||
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
||||||
|
})
|
||||||
|
it('angledLineToY with tag converts to yLineTo', () => {
|
||||||
|
const { newCode, originalRange } = testingSwapSketchFnCall({
|
||||||
|
inputCode: bigExample,
|
||||||
|
callToSwap: "angledLineToY({ angle: 330, to: 2.53, tag: 'abc7' }, %)",
|
||||||
|
toFnCallName: 'yLineTo',
|
||||||
|
})
|
||||||
|
const expectedLine = "yLineTo({ to: 2.53, tag: 'abc7' }, %)"
|
||||||
|
expect(newCode).toContain(expectedLine)
|
||||||
|
// new line should start at the same place as the old line
|
||||||
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
||||||
|
})
|
||||||
|
it('angledLineToY w/o tag converts to yLineTo', () => {
|
||||||
|
const { newCode, originalRange } = testingSwapSketchFnCall({
|
||||||
|
inputCode: bigExample,
|
||||||
|
callToSwap: 'angledLineToY([228, 2.14], %)',
|
||||||
|
toFnCallName: 'yLineTo',
|
||||||
|
})
|
||||||
|
const expectedLine = 'yLineTo(2.14, %) // angledLineToY'
|
||||||
|
expect(newCode).toContain(expectedLine)
|
||||||
|
// new line should start at the same place as the old line
|
||||||
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('testing swaping out sketch calls with xLine/xLineTo while keeping variable/identifiers intact', () => {
|
||||||
|
const variablesExampleArr = [
|
||||||
|
`const lineX = -1`,
|
||||||
|
`const lineToX = -1.3`,
|
||||||
|
`const angledLineAngle = 207`,
|
||||||
|
`const angledLineOfXLengthX = 0.8`,
|
||||||
|
`const angledLineOfYLengthY = 0.89`,
|
||||||
|
`const angledLineToXx = -1.86`,
|
||||||
|
`const angledLineToYy = -0.76`,
|
||||||
|
`const part001 = startSketchAt([0, 0])`,
|
||||||
|
` |> rx(90, %)`,
|
||||||
|
` |> lineTo([1, 1], %)`,
|
||||||
|
` |> line([lineX, 2.13], %)`,
|
||||||
|
` |> lineTo([lineToX, 2.85], %)`,
|
||||||
|
` |> angledLine([angledLineAngle, 1.64], %)`,
|
||||||
|
` |> angledLineOfXLength([329, angledLineOfXLengthX], %)`,
|
||||||
|
` |> angledLineOfYLength([222, angledLineOfYLengthY], %)`,
|
||||||
|
` |> angledLineToX([330, angledLineToXx], %)`,
|
||||||
|
` |> angledLineToY([217, angledLineToYy], %)`,
|
||||||
|
` |> line([0.89, -0.1], %)`,
|
||||||
|
`show(part001)`,
|
||||||
|
]
|
||||||
|
const varExample = variablesExampleArr.join('\n')
|
||||||
|
it('line keeps variable when converted to xLine', () => {
|
||||||
|
const { newCode, originalRange } = testingSwapSketchFnCall({
|
||||||
|
inputCode: varExample,
|
||||||
|
callToSwap: 'line([lineX, 2.13], %)',
|
||||||
|
toFnCallName: 'xLine',
|
||||||
|
})
|
||||||
|
const expectedLine = 'xLine(lineX, %)'
|
||||||
|
expect(newCode).toContain(expectedLine)
|
||||||
|
// new line should start at the same place as the old line
|
||||||
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
||||||
|
})
|
||||||
|
it('lineTo keeps variable when converted to xLineTo', () => {
|
||||||
|
const { newCode, originalRange } = testingSwapSketchFnCall({
|
||||||
|
inputCode: varExample,
|
||||||
|
callToSwap: 'lineTo([lineToX, 2.85], %)',
|
||||||
|
toFnCallName: 'xLineTo',
|
||||||
|
})
|
||||||
|
const expectedLine = 'xLineTo(lineToX, %)'
|
||||||
|
expect(newCode).toContain(expectedLine)
|
||||||
|
// new line should start at the same place as the old line
|
||||||
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
||||||
|
})
|
||||||
|
it('angledLineOfXLength keeps variable when converted to xLine', () => {
|
||||||
|
const { newCode, originalRange } = testingSwapSketchFnCall({
|
||||||
|
inputCode: varExample,
|
||||||
|
callToSwap: 'angledLineOfXLength([329, angledLineOfXLengthX], %)',
|
||||||
|
toFnCallName: 'xLine',
|
||||||
|
})
|
||||||
|
const expectedLine = 'xLine(angledLineOfXLengthX, %)'
|
||||||
|
expect(newCode).toContain(expectedLine)
|
||||||
|
// new line should start at the same place as the old line
|
||||||
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
||||||
|
})
|
||||||
|
it('angledLineOfYLength keeps variable when converted to yLine', () => {
|
||||||
|
const { newCode, originalRange } = testingSwapSketchFnCall({
|
||||||
|
inputCode: varExample,
|
||||||
|
callToSwap: 'angledLineOfYLength([222, angledLineOfYLengthY], %)',
|
||||||
|
toFnCallName: 'yLine',
|
||||||
|
})
|
||||||
|
const expectedLine = 'yLine(angledLineOfYLengthY, %)'
|
||||||
|
expect(newCode).toContain(expectedLine)
|
||||||
|
// new line should start at the same place as the old line
|
||||||
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
||||||
|
})
|
||||||
|
it('angledLineToX keeps variable when converted to xLineTo', () => {
|
||||||
|
const { newCode, originalRange } = testingSwapSketchFnCall({
|
||||||
|
inputCode: varExample,
|
||||||
|
callToSwap: 'angledLineToX([330, angledLineToXx], %)',
|
||||||
|
toFnCallName: 'xLineTo',
|
||||||
|
})
|
||||||
|
const expectedLine = 'xLineTo(angledLineToXx, %)'
|
||||||
|
expect(newCode).toContain(expectedLine)
|
||||||
|
// new line should start at the same place as the old line
|
||||||
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
||||||
|
})
|
||||||
|
it('angledLineToY keeps variable when converted to yLineTo', () => {
|
||||||
|
const { newCode, originalRange } = testingSwapSketchFnCall({
|
||||||
|
inputCode: varExample,
|
||||||
|
callToSwap: 'angledLineToY([217, angledLineToYy], %)',
|
||||||
|
toFnCallName: 'yLineTo',
|
||||||
|
})
|
||||||
|
const expectedLine = 'yLineTo(angledLineToYy, %)'
|
||||||
|
expect(newCode).toContain(expectedLine)
|
||||||
|
// new line should start at the same place as the old line
|
||||||
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('trying to convert angledLineToY to xLineTo should not work because of the variable', () => {
|
||||||
|
const illegalConvert = () =>
|
||||||
|
testingSwapSketchFnCall({
|
||||||
|
inputCode: varExample,
|
||||||
|
callToSwap: 'angledLineToY([217, angledLineToYy], %)',
|
||||||
|
toFnCallName: 'xLineTo',
|
||||||
|
})
|
||||||
|
expect(illegalConvert).toThrowError()
|
||||||
|
})
|
||||||
|
})
|
53
src/lang/std/sketchConstraints.ts
Normal file
53
src/lang/std/sketchConstraints.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { Range, TooTip } from '../../useStore'
|
||||||
|
import {
|
||||||
|
getNodePathFromSourceRange,
|
||||||
|
getNodeFromPath,
|
||||||
|
Program,
|
||||||
|
VariableDeclarator,
|
||||||
|
} from '../../lang/abstractSyntaxTree'
|
||||||
|
import { replaceSketchLine } from '../../lang/std/sketch'
|
||||||
|
import { ProgramMemory, SketchGroup } from '../../lang/executor'
|
||||||
|
import { TransformCallback } from '../../lang/std/stdTypes'
|
||||||
|
|
||||||
|
export function swapSketchHelper(
|
||||||
|
programMemory: ProgramMemory,
|
||||||
|
ast: Program,
|
||||||
|
range: Range,
|
||||||
|
newFnName: TooTip,
|
||||||
|
createCallback: TransformCallback
|
||||||
|
): { modifiedAst: Program } {
|
||||||
|
const path = getNodePathFromSourceRange(ast, range)
|
||||||
|
const varDec = getNodeFromPath<VariableDeclarator>(
|
||||||
|
ast,
|
||||||
|
path,
|
||||||
|
'VariableDeclarator'
|
||||||
|
).node
|
||||||
|
const varName = varDec.id.name
|
||||||
|
const sketchGroup = programMemory.root?.[varName]
|
||||||
|
if (!sketchGroup || sketchGroup.type !== 'sketchGroup')
|
||||||
|
throw new Error('not a sketch group')
|
||||||
|
const seg = getSketchSegmentIndexFromSourceRange(sketchGroup, range)
|
||||||
|
const { to, from } = seg
|
||||||
|
const { modifiedAst } = replaceSketchLine({
|
||||||
|
node: ast,
|
||||||
|
sourceRange: range,
|
||||||
|
programMemory,
|
||||||
|
fnName: newFnName,
|
||||||
|
to,
|
||||||
|
from,
|
||||||
|
createCallback,
|
||||||
|
})
|
||||||
|
return { modifiedAst }
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSketchSegmentIndexFromSourceRange(
|
||||||
|
sketchGroup: SketchGroup,
|
||||||
|
[rangeStart, rangeEnd]: Range
|
||||||
|
): SketchGroup['value'][number] {
|
||||||
|
const line = sketchGroup.value.find(
|
||||||
|
({ __geoMeta: { sourceRange } }) =>
|
||||||
|
sourceRange[0] <= rangeStart && sourceRange[1] >= rangeEnd
|
||||||
|
)
|
||||||
|
if (!line) throw new Error('could not find matching line')
|
||||||
|
return line
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import { ProgramMemory, Path, SourceRange } from '../executor'
|
import { ProgramMemory, Path, SourceRange } from '../executor'
|
||||||
import { Program } from '../abstractSyntaxTree'
|
import { Program, Value } from '../abstractSyntaxTree'
|
||||||
|
import { TooTip } from '../../useStore'
|
||||||
|
|
||||||
export interface InternalFirstArg {
|
export interface InternalFirstArg {
|
||||||
programMemory: ProgramMemory
|
programMemory: ProgramMemory
|
||||||
@ -44,6 +45,9 @@ export interface ModifyAstBase {
|
|||||||
|
|
||||||
interface addCall extends ModifyAstBase {
|
interface addCall extends ModifyAstBase {
|
||||||
to: [number, number]
|
to: [number, number]
|
||||||
|
from?: [number, number]
|
||||||
|
replaceExisting?: boolean
|
||||||
|
createCallback?: TransformCallback // TODO: #29 probably should not be optional
|
||||||
}
|
}
|
||||||
|
|
||||||
interface updateArgs extends ModifyAstBase {
|
interface updateArgs extends ModifyAstBase {
|
||||||
@ -51,6 +55,12 @@ interface updateArgs extends ModifyAstBase {
|
|||||||
to: [number, number]
|
to: [number, number]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type TransformCallback = (args: [Value, Value]) => Value
|
||||||
|
|
||||||
|
export type SketchCallTransfromMap = {
|
||||||
|
[key in TooTip]: TransformCallback
|
||||||
|
}
|
||||||
|
|
||||||
export interface SketchLineHelper {
|
export interface SketchLineHelper {
|
||||||
fn: InternalFn
|
fn: InternalFn
|
||||||
add: (a: addCall) => {
|
add: (a: addCall) => {
|
||||||
@ -65,4 +75,5 @@ export interface SketchLineHelper {
|
|||||||
modifiedAst: Program
|
modifiedAst: Program
|
||||||
tag: string
|
tag: string
|
||||||
}
|
}
|
||||||
|
allowedTransforms: (a: ModifyAstBase) => Partial<SketchCallTransfromMap>
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user