Files
modeling-app/src/lang/std/sketchConstraints.ts
Adam Chalmers ecec739632 Fix test 'yRelative to horizontal distance'
Fixes:
 - Make a lineTo helper
 - Fix pathToNode to go through the labeled arg .arg property
2025-01-27 08:56:36 +13:00

136 lines
4.3 KiB
TypeScript

import { getNodeFromPath } from 'lang/queryAst'
import { ToolTip, toolTips } from 'lang/langHelpers'
import { Node } from 'wasm-lib/kcl/bindings/Node'
import {
Program,
VariableDeclarator,
CallExpression,
Sketch,
SourceRange,
Path,
PathToNode,
Expr,
topLevelRange,
LabeledArg,
} from '../wasm'
import { err } from 'lib/trap'
export function getSketchSegmentFromPathToNode(
sketch: Sketch,
ast: Program,
pathToNode: PathToNode
):
| {
segment: Sketch['paths'][number]
index: number
}
| Error {
// TODO: once pathToNode is stored on program memory as part of execution,
// we can check if the pathToNode matches the pathToNode of the sketch.
// For now we fall back to the sourceRange
const nodeMeta = getNodeFromPath<Node<Expr> | LabeledArg>(ast, pathToNode)
if (err(nodeMeta)) return nodeMeta
const _node = nodeMeta.node
const node = (() => {
switch (_node.type) {
// LabeledArg wraps the expression being assigned to a parameter.
// So, undo the wrapper. Used for keyword arguments.
case 'LabeledArg':
return _node.arg
// Other nodes aren't wrapped, we can return them directly.
default:
return _node
}
})()
if (!node || typeof node.start !== 'number' || !node.end)
return new Error('no node found')
const sourceRange = topLevelRange(node.start, node.end)
return getSketchSegmentFromSourceRange(sketch, sourceRange)
}
export function getSketchSegmentFromSourceRange(
sketch: Sketch,
[rangeStart, rangeEnd]: SourceRange
):
| {
segment: Sketch['paths'][number]
index: number
}
| Error {
const lineIndex = sketch.paths.findIndex(
({ __geoMeta: { sourceRange } }: Path) =>
sourceRange[0] <= rangeStart && sourceRange[1] >= rangeEnd
)
const line = sketch.paths[lineIndex]
if (line) {
return {
segment: line,
index: lineIndex,
}
}
const startSourceRange = sketch.start?.__geoMeta.sourceRange
if (
startSourceRange &&
startSourceRange[0] <= rangeStart &&
startSourceRange[1] >= rangeEnd &&
sketch.start
)
return { segment: { ...sketch.start, type: 'Base' }, index: -1 }
return new Error('could not find matching segment')
}
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 ToolTip)
)
return false
// convention for sketch fns is that the second argument is the sketch
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
if (node.declaration.id.name === secondArg.name) {
nextVarDec = node.declaration
break
}
}
if (!nextVarDec) return false
return isSketchVariablesLinked(nextVarDec, primaryVarDec, ast)
}