Symbols overlay (#2033)

* start of overlay work

* add new icons

* add constraint symbols

* add three dots

* add primary colours

* refactor how we get constraint info for overlays

* refactor how we get constraint info for overlays

* get symbols working for tangential arc too

* extra data on constraint info

* add initial delete

* fix types and circular dep issue after rebase

* fix quirk with horz vert line overlays

* fix setup and tear down of overlays

* remove overlays that are too small

* throttle overlay updates and prove tests selecting html instead of hardcoded px coords

* initial show overaly on segment hover

* remove overlays when tool is equipped

* dounce overlay updates

* tsc

* make higlighting robust to small changes in source ranges

* replace with variable for unconstrained values, and improve styles for popover

* background tweak

* make overlays unconstrain inputs

* fix small regression

* write query for finding related tag references

* make delete segment safe

* typo

* un used imports

* test deleteSegmentFromPipeExpression

* add getConstraintInfo test

* test removeSingleConstraintInfo

* more tests

* tsc

* add tests for overlay buttons

* rename tests

* fmt

* better naming structure

* more reliablity

* more test tweaks

* fix selection test

* add delete segments with overlays tests

* dependant tag tests for segment delet

* typo

* test clean up

* fix some perf issus

* clean up

* clean up

* make things a little more dry

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* trigger ci

* Make constraint hover popovers readable on light mode

* Touch up the new variable dialog

* Little touch-up to three-dot menu style

* fix highlight issue

* fmt

* use optional chain

* Revert "A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)"

This reverts commit be3d61e4a3.

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* disable var panel in sketch mode

* fix overlay tests after mergi in main

* test tweak

* try fix ubuntu

* fmt

* more test tweaks

* tweak

* tweaks

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Frank Noirot <frank@kittycad.io>
This commit is contained in:
Kurt Hutten
2024-05-24 20:54:42 +10:00
committed by GitHub
parent 87c551b869
commit cf52e151fb
28 changed files with 4359 additions and 216 deletions

View File

@ -1,4 +1,4 @@
import { TransformCallback } from './stdTypes'
import { TransformCallback, VarValues } from './stdTypes'
import { toolTips, ToolTip } from '../../useStore'
import { Selections, Selection } from 'lib/selections'
import {
@ -17,6 +17,7 @@ import {
isValueZero,
} from '../queryAst'
import {
createArrayExpression,
createBinaryExpression,
createBinaryExpressionWithUnary,
createCallExpression,
@ -27,17 +28,27 @@ import {
createUnaryExpression,
giveSketchFnCallTag,
} from '../modifyAst'
import { createFirstArg, getFirstArg, replaceSketchLine } from './sketch'
import { getSketchSegmentFromSourceRange } from './sketchConstraints'
import {
createFirstArg,
getConstraintInfo,
getFirstArg,
replaceSketchLine,
} from './sketch'
import {
getSketchSegmentFromPathToNode,
getSketchSegmentFromSourceRange,
} from './sketchConstraints'
import { getAngle, roundOff, normaliseAngle } from '../../lib/utils'
type LineInputsType =
export type LineInputsType =
| 'xAbsolute'
| 'yAbsolute'
| 'xRelative'
| 'yRelative'
| 'angle'
| 'length'
| 'intersectionOffset'
| 'intersectionTag'
export type ConstraintType =
| 'equalLength'
@ -70,6 +81,31 @@ function createCallWrapper(
}
}
/**
* Abstracts creation of a callExpression ready for use for a sketchCombo transform
* Assume it exists within a pipe and adds the pipe substitution
* @param tool line, lineTo, angledLine, etc
* @param val The first argument to the function
* @param tag
* @param valueUsedInTransform
* @returns
*/
function createStdlibCallExpression(
tool: ToolTip,
val: Value,
tag?: Value,
valueUsedInTransform?: number
): ReturnType<TransformCallback> {
const args = [val, createPipeSubstitution()]
if (tag) {
args.push(tag)
}
return {
callExp: createCallExpression(tool, args),
valueUsedInTransform,
}
}
function intersectCallWrapper({
fnName,
angleVal,
@ -106,6 +142,7 @@ function intersectCallWrapper({
export type TransformInfo = {
tooltip: ToolTip
createNode: (a: {
varValues: VarValues
varValA: Value // x / angle
varValB: Value // y / length or x y for angledLineOfXlength etc
referenceSegName: string
@ -145,7 +182,7 @@ const basicAngledLineCreateNode =
varValToUse: 'ang' | 'len' | 'none' = 'none'
): TransformInfo['createNode'] =>
({ referenceSegName, tag, forceValueUsedInTransform, varValA, varValB }) =>
(args, path) => {
(args, _, path) => {
const refAng = path ? getAngle(path?.from, path?.to) : 0
const nonForcedAng =
varValToUse === 'ang'
@ -252,7 +289,7 @@ const setHorzVertDistanceCreateNode =
index = xOrY === 'x' ? 0 : 1
): TransformInfo['createNode'] =>
({ referenceSegName, tag, forceValueUsedInTransform }) => {
return (args, referencedSegment) => {
return (args, _, referencedSegment) => {
const valueUsedInTransform = roundOff(
getArgLiteralVal(args?.[index]) - (referencedSegment?.to?.[index] || 0),
2
@ -279,7 +316,7 @@ const setHorzVertDistanceForAngleLineCreateNode =
index = xOrY === 'x' ? 0 : 1
): TransformInfo['createNode'] =>
({ referenceSegName, tag, forceValueUsedInTransform, varValA }) => {
return (args, referencedSegment) => {
return (args, _, referencedSegment) => {
const valueUsedInTransform = roundOff(
getArgLiteralVal(args?.[1]) - (referencedSegment?.to?.[index] || 0),
2
@ -305,7 +342,7 @@ const setAbsDistanceCreateNode =
index = xOrY === 'x' ? 0 : 1
): TransformInfo['createNode'] =>
({ tag, forceValueUsedInTransform }) => {
return (args, referencedSegment) => {
return (args, _, referencedSegment) => {
const valueUsedInTransform = roundOff(
getArgLiteralVal(args?.[index]) - (referencedSegment?.to?.[index] || 0),
2
@ -335,7 +372,7 @@ const setAbsDistanceForAngleLineCreateNode =
index = xOrY === 'x' ? 0 : 1
): TransformInfo['createNode'] =>
({ tag, forceValueUsedInTransform, varValA }) => {
return (args, referencedSegment) => {
return (args) => {
const valueUsedInTransform = roundOff(getArgLiteralVal(args?.[1]), 2)
const val =
(forceValueUsedInTransform as BinaryPart) ||
@ -352,7 +389,7 @@ const setAbsDistanceForAngleLineCreateNode =
const setHorVertDistanceForXYLines =
(xOrY: 'x' | 'y'): TransformInfo['createNode'] =>
({ referenceSegName, tag, forceValueUsedInTransform }) => {
return (args, referencedSegment) => {
return (args, _, referencedSegment) => {
const index = xOrY === 'x' ? 0 : 1
const valueUsedInTransform = roundOff(
getArgLiteralVal(args?.[index]) - (referencedSegment?.to?.[index] || 0),
@ -381,7 +418,7 @@ const setHorzVertDistanceConstraintLineCreateNode =
varVal,
])
return (args, referencedSegment) => {
return (args, _, referencedSegment) => {
const makeBinExp = (index: 0 | 1) => {
const arg = getArgLiteralVal(args?.[index])
return createBinaryExpressionWithUnary([
@ -449,7 +486,7 @@ const setAngledIntersectForAngledLines: TransformInfo['createNode'] =
const setAngleBetweenCreateNode =
(tranformToType: 'none' | 'xAbs' | 'yAbs'): TransformInfo['createNode'] =>
({ referenceSegName, tag, forceValueUsedInTransform, varValA, varValB }) => {
return (args, referencedSegment) => {
return (args, _, referencedSegment) => {
const refAngle = referencedSegment
? getAngle(referencedSegment?.from, referencedSegment?.to)
: 0
@ -1168,6 +1205,92 @@ export function getRemoveConstraintsTransform(
return false
}
export function removeSingleConstraint({
pathToCallExp,
arrayIndex,
objectProperty,
ast,
}: {
pathToCallExp: PathToNode
arrayIndex?: number
objectProperty?: string
ast: Program
}): TransformInfo | false {
const callExp = getNodeFromPath<CallExpression>(
ast,
pathToCallExp,
'CallExpression'
).node
if (callExp.type !== 'CallExpression') throw new Error('Invalid node type')
const transform: TransformInfo = {
tooltip: callExp.callee.name as any,
createNode:
({ tag, referenceSegName, varValues }) =>
(_, rawValues) => {
if (objectProperty) {
const expression: Parameters<typeof createObjectExpression>[0] = {}
varValues.forEach((varValue) => {
if (
varValue.type !== 'objectProperty' &&
varValue.type !== 'arrayOrObjItem'
)
return
const literal = rawValues.find(
(rawValue) =>
(rawValue.type === 'objectProperty' ||
rawValue.type === 'arrayOrObjItem') &&
rawValue.key === objectProperty
)?.value
const value =
(varValue.key === objectProperty && literal) || varValue.value
expression[varValue.key] = value
})
const objExp = createObjectExpression(expression)
return createStdlibCallExpression(
callExp.callee.name as any,
objExp,
tag
)
}
if (typeof arrayIndex === 'number') {
const values = varValues.map((varValue) => {
if (
(varValue.type === 'arrayItem' ||
varValue.type === 'arrayOrObjItem') &&
varValue.index === arrayIndex
) {
const literal = rawValues.find(
(rawValue) =>
(rawValue.type === 'arrayItem' ||
rawValue.type === 'arrayOrObjItem') &&
rawValue.index === arrayIndex
)?.value
return (
(varValue.index === arrayIndex && literal) || varValue.value
)
}
return varValue.value
})
return createStdlibCallExpression(
callExp.callee.name as any,
createArrayExpression(values),
tag
)
}
// if (typeof arrayIndex !== 'number' || !objectProperty) must be single value input xLine, yLineTo etc
return createCallWrapper(
callExp.callee.name as any,
rawValues[0].value,
tag
)
},
}
return transform
}
function getTransformMapPath(
sketchFnExp: CallExpression,
constraintType: ConstraintType
@ -1392,7 +1515,7 @@ export function transformAstSketchLines({
referencedSegmentRange,
}: {
ast: Program
selectionRanges: Selections
selectionRanges: Selections | PathToNode[]
transformInfos: TransformInfo[]
programMemory: ProgramMemory
referenceSegName: string
@ -1408,15 +1531,13 @@ export function transformAstSketchLines({
let _valueUsedInTransform // TODO should this be an array?
const pathToNodeMap: PathToNodeMap = {}
selectionRanges.codeBasedSelections.forEach(({ range }, index) => {
const processSelection = (_pathToNode: PathToNode, index: number) => {
const callBack = transformInfos?.[index].createNode
const transformTo = transformInfos?.[index].tooltip
if (!callBack || !transformTo) throw new Error('no callback helper')
const getNode = getNodeFromPathCurry(
node,
getNodePathFromSourceRange(node, range)
)
const getNode = getNodeFromPathCurry(node, _pathToNode)
const callExp = getNode<CallExpression>('CallExpression')?.node
const varDec = getNode<VariableDeclarator>('VariableDeclarator').node
@ -1436,11 +1557,47 @@ export function transformAstSketchLines({
''
const [varValA, varValB] = Array.isArray(val) ? val : [val, val]
const varValues: VarValues = []
getConstraintInfo(callExp, '', _pathToNode).forEach((a) => {
if (
a.type === 'tangentialWithPrevious' ||
a.type === 'horizontal' ||
a.type === 'vertical'
)
return
if (a?.argPosition?.type === 'arrayItem') {
varValues.push({
type: 'arrayItem',
index: a.argPosition.index,
value: getNodeFromPath<Value>(ast, a.pathToNode).node,
argType: a.type,
})
} else if (a?.argPosition?.type === 'objectProperty') {
varValues.push({
type: 'objectProperty',
key: a.argPosition.key,
value: getNodeFromPath<Value>(ast, a.pathToNode).node,
argType: a.type,
})
} else if (a?.argPosition?.type === 'singleValue') {
varValues.push({
type: 'singleValue',
argType: a.type,
value: getNodeFromPath<Value>(ast, a.pathToNode).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 = getSketchSegmentFromSourceRange(sketchGroup, range).segment
const seg = getSketchSegmentFromPathToNode(
sketchGroup,
ast,
_pathToNode
).segment
const referencedSegment = referencedSegmentRange
? getSketchSegmentFromSourceRange(sketchGroup, referencedSegmentRange)
.segment
@ -1450,13 +1607,14 @@ export function transformAstSketchLines({
{
node: node,
programMemory,
sourceRange: range,
pathToNode: _pathToNode,
referencedSegment,
fnName: transformTo || (callExp.callee.name as ToolTip),
to,
from,
createCallback: callBack({
referenceSegName: _referencedSegmentName,
varValues,
varValA,
varValB,
tag: callBackTag,
@ -1470,7 +1628,16 @@ export function transformAstSketchLines({
if (typeof valueUsedInTransform === 'number') {
_valueUsedInTransform = valueUsedInTransform
}
})
}
if ('codeBasedSelections' in selectionRanges) {
selectionRanges.codeBasedSelections.forEach(({ range }, index) =>
processSelection(getNodePathFromSourceRange(node, range), index)
)
} else {
selectionRanges.forEach(processSelection)
}
return {
modifiedAst: node,
valueUsedInTransform: _valueUsedInTransform,