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:
Kurt Hutten
2023-03-02 21:19:11 +11:00
committed by GitHub
parent f70f0f7bc3
commit 6446601a67
21 changed files with 2280 additions and 666 deletions

View File

@ -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)
}