Add equal-length constraints & implement UnaryExpressions (#35)
* add segLen help to lang std * adding helpers functions to sketchConstraints * update tokeniser tests because they were annoying me not being 100% * compare async lexer with sync lexer instead * add helper functions * remove unneeded nesting * update add ast modifier function for angledLine * initial equal ast modification It adds a tag to the primary line, and converts any secondary lines to angledLine, but doesn't reference the taged/primary line yet * Update fn call with refernce to previous line using segLen * add test for giveSketchFnCallTag * fix excutor bug, executing call expression in array expression * fix small issue in executor * add CallExpressions to BinaryExpressions * add unary Expressions * tweaks to unaryExpression logic * add recasting for unaryExpressions and CallExpressions in BinaryExpressions * ensure pipe substitution info is passed down to unary expressions and others * allow binary expressions in function argumentns * inital setup, new way of organising sketch fn transforms Starting with equal length * overhaul equalLength button * add equal length support for angledLine * line with one variable supports signed legLength * fix indentation when recasting long arrayExpressions in a pipeExpression * improve modifyAst consision * further modify ast tidy * equalLength transfroms far angledLineOfXLength * add transforms for line-yRelative * add equal constraint for angledLineOfYLength * quick test fix * add equal length constrain transforms for lineTo * add equal length constraints for angledLineToX * add equalLength constraints for angledLineToY * test tidy * setup new vertical-horizontal constraints * Add equal Length constraints for vertical/horizontal lines * migrate old tests, and refactor callback tag * tweaks and refactor horzVert component * fix leg len with small negative leg length
This commit is contained in:
@ -1,46 +1,13 @@
|
||||
import { Range, TooTip } from '../../useStore'
|
||||
import { Range, TooTip, toolTips } 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'
|
||||
CallExpression,
|
||||
} from '../abstractSyntaxTree'
|
||||
import { SketchGroup } from '../executor'
|
||||
import { InternalFn } from './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(
|
||||
export function getSketchSegmentIndexFromSourceRange(
|
||||
sketchGroup: SketchGroup,
|
||||
[rangeStart, rangeEnd]: Range
|
||||
): SketchGroup['value'][number] {
|
||||
@ -51,3 +18,94 @@ function getSketchSegmentIndexFromSourceRange(
|
||||
if (!line) throw new Error('could not find matching line')
|
||||
return line
|
||||
}
|
||||
|
||||
export const segLen: InternalFn = (
|
||||
_,
|
||||
segName: string,
|
||||
sketchGroup: SketchGroup
|
||||
): number => {
|
||||
const line = sketchGroup?.value.find((seg) => seg.name === segName)
|
||||
// maybe this should throw, but the language doesn't have a way to handle errors yet
|
||||
if (!line) return 0
|
||||
|
||||
return Math.sqrt(
|
||||
(line.from[1] - line.to[1]) ** 2 + (line.from[0] - line.to[0]) ** 2
|
||||
)
|
||||
}
|
||||
|
||||
function angleToMatchLengthFactory(which: 'x' | 'y'): InternalFn {
|
||||
return (_, segName: string, to: number, sketchGroup: SketchGroup): number => {
|
||||
const isX = which === 'x'
|
||||
const lineToMatch = sketchGroup?.value.find((seg) => seg.name === segName)
|
||||
// maybe this should throw, but the language doesn't have a way to handle errors yet
|
||||
if (!lineToMatch) return 0
|
||||
const lengthToMatch = Math.sqrt(
|
||||
(lineToMatch.from[1] - lineToMatch.to[1]) ** 2 +
|
||||
(lineToMatch.from[0] - lineToMatch.to[0]) ** 2
|
||||
)
|
||||
|
||||
const lastLine = sketchGroup?.value[sketchGroup.value.length - 1]
|
||||
const diff = Math.abs(to - (isX ? lastLine.to[0] : lastLine.to[1]))
|
||||
|
||||
const angleR = Math[isX ? 'acos' : 'asin'](diff / lengthToMatch)
|
||||
|
||||
return diff > lengthToMatch ? 0 : (angleR * 180) / Math.PI
|
||||
}
|
||||
}
|
||||
|
||||
export const angleToMatchLengthX: InternalFn = angleToMatchLengthFactory('x')
|
||||
export const angleToMatchLengthY: InternalFn = angleToMatchLengthFactory('y')
|
||||
|
||||
export function isSketchVariablesLinked(
|
||||
secondaryVarDec: VariableDeclarator,
|
||||
primaryVarDec: VariableDeclarator,
|
||||
ast: Program
|
||||
): boolean {
|
||||
/*
|
||||
checks if two callExpressions are part of the same pipe
|
||||
if not than checks if the second argument is a variable that is linked to the primary variable declaration
|
||||
and will keep checking the second arguments recursively until it runs out of variable declarations
|
||||
to check or it finds a match.
|
||||
that way it can find fn calls that are linked to each other through variables eg:
|
||||
const part001 = startSketchAt([0, 0])
|
||||
|> xLineTo(1.69, %)
|
||||
|> line([myVar, 0.38], %) // ❗️ <- cursor in this fn call (the primary)
|
||||
|> line([0.41, baz], %)
|
||||
|> xLine(0.91, %)
|
||||
|> angledLine([37, 2], %)
|
||||
const yo = line([myVar, 0.38], part001)
|
||||
|> line([1, 1], %)
|
||||
const yo2 = line([myVar, 0.38], yo)
|
||||
|> line([1, 1], %) // ❗️ <- and cursor here (secondary) is linked to the one above through variables
|
||||
*/
|
||||
const secondaryVarName = secondaryVarDec?.id?.name
|
||||
if (!secondaryVarName) return false
|
||||
if (secondaryVarName === primaryVarDec?.id?.name) return true
|
||||
const { init } = secondaryVarDec
|
||||
if (
|
||||
!init ||
|
||||
!(init.type === 'CallExpression' || init.type === 'PipeExpression')
|
||||
)
|
||||
return false
|
||||
const firstCallExp = // first in pipe expression or just the call expression
|
||||
init?.type === 'CallExpression' ? init : (init?.body[0] as CallExpression)
|
||||
if (!firstCallExp || !toolTips.includes(firstCallExp?.callee?.name as TooTip))
|
||||
return false
|
||||
// convention for sketch fns is that the second argument is the sketch group
|
||||
const secondArg = firstCallExp?.arguments[1]
|
||||
if (!secondArg || secondArg?.type !== 'Identifier') return false
|
||||
if (secondArg.name === primaryVarDec?.id?.name) return true
|
||||
|
||||
let nextVarDec: VariableDeclarator | undefined
|
||||
for (const node of ast.body) {
|
||||
if (node.type !== 'VariableDeclaration') continue
|
||||
const found = node.declarations.find(
|
||||
({ id }) => id?.name === secondArg.name
|
||||
)
|
||||
if (!found) continue
|
||||
nextVarDec = found
|
||||
break
|
||||
}
|
||||
if (!nextVarDec) return false
|
||||
return isSketchVariablesLinked(nextVarDec, primaryVarDec, ast)
|
||||
}
|
||||
|
Reference in New Issue
Block a user