diff --git a/src/components/RenderViewerArtifacts.tsx b/src/components/RenderViewerArtifacts.tsx index a639be787..de86fef5e 100644 --- a/src/components/RenderViewerArtifacts.tsx +++ b/src/components/RenderViewerArtifacts.tsx @@ -17,6 +17,7 @@ import { useStore } from '../useStore' import { isOverlap, roundOff } from '../lib/utils' import { Vector3, DoubleSide, Quaternion } from 'three' import { useSetCursor } from '../hooks/useSetCursor' +import { getConstraintLevelFromSourceRange } from '../lang/std/sketchcombos' function MovingSphere({ geo, @@ -416,25 +417,43 @@ function LineRender({ rotation: Rotation position: Position }) { - const { setHighlightRange } = useStore((s) => ({ + const { setHighlightRange, guiMode, ast } = useStore((s) => ({ setHighlightRange: s.setHighlightRange, + guiMode: s.guiMode, + ast: s.ast, })) const onClick = useSetCursor(sourceRange) // This reference will give us direct access to the mesh const ref = useRef() as any const [hovered, setHover] = useState(false) + const [baseColor, setBaseColor] = useState('orange') + useEffect(() => { + if (!ast || guiMode.mode !== 'sketch') { + setBaseColor('orange') + return + } + const level = getConstraintLevelFromSourceRange(sourceRange, ast) + if (level === 'free') { + setBaseColor('orange') + } else if (level === 'partial') { + setBaseColor('IndianRed') + } else if (level === 'full') { + setBaseColor('lightgreen') + } + }, [guiMode, ast, sourceRange]) + return ( <> { + onPointerOver={(e) => { setHover(true) setHighlightRange(sourceRange) }} - onPointerOut={(event) => { + onPointerOut={(e) => { setHover(false) setHighlightRange([0, 0]) }} @@ -442,7 +461,7 @@ function LineRender({ > diff --git a/src/lang/std/sketchcombos.test.ts b/src/lang/std/sketchcombos.test.ts index a8e20db5a..22bd2853c 100644 --- a/src/lang/std/sketchcombos.test.ts +++ b/src/lang/std/sketchcombos.test.ts @@ -6,6 +6,7 @@ import { transformAstSketchLines, transformSecondarySketchLinesTagFirst, ConstraintType, + getConstraintLevelFromSourceRange, } from './sketchcombos' import { initPromise } from '../rust' import { TooTip } from '../../useStore' @@ -429,3 +430,64 @@ function helperThing( })?.modifiedAst return recast(newAst) } + +describe('testing getConstraintLevelFromSourceRange', () => { + it('should devide up lines into free, partial and fully contrained', () => { + const code = `const baseLength = 3 +const baseThick = 1 +const armThick = 0.5 +const totalHeight = 4 +const armAngle = 60 +const totalLength = 9.74 +const yDatum = 0 + +const baseThickHalf = baseThick / 2 +const halfHeight = totalHeight / 2 +const halfArmAngle = armAngle / 2 + +const part001 = startSketchAt([-0.01, -0.05]) + |> line([0.01, 0.94 + 0], %) // partial + |> xLine(3.03, %) // partial + |> angledLine({ + angle: halfArmAngle, + length: 2.45, + tag: 'seg01bing' +}, %) // partial + |> xLine(4.4, %) // partial + |> yLine(-1, %) // partial + |> xLine(-4.2 + 0, %) // full + |> angledLine([segAng('seg01bing', %) + 180, 1.79], %) // partial + |> line([1.44, -0.74], %) // free + |> xLine(3.36, %) // partial + |> line([-1.49, 1.06], %) // free + |> xLine(-3.43 + 0, %) // full + |> angledLineOfXLength([243 + 0, 1.2 + 0], %) // full +show(part001)` + const ast = abstractSyntaxTree(lexer(code)) + const constraintLevels: ReturnType< + typeof getConstraintLevelFromSourceRange + >[] = ['full', 'partial', 'free'] + constraintLevels.forEach((constraintLevel) => { + const recursivelySeachCommentsAndCheckConstraintLevel = ( + str: string, + offset: number = 0 + ): null => { + const index = str.indexOf(`// ${constraintLevel}`, offset) + if (index === -1) { + return null + } + const offsetIndex = index - 7 + const expectedConstraintLevel = getConstraintLevelFromSourceRange( + [offsetIndex, offsetIndex], + ast + ) + expect(expectedConstraintLevel).toBe(constraintLevel) + return recursivelySeachCommentsAndCheckConstraintLevel( + str, + index + constraintLevel.length + ) + } + recursivelySeachCommentsAndCheckConstraintLevel(code) + }) + }) +}) diff --git a/src/lang/std/sketchcombos.ts b/src/lang/std/sketchcombos.ts index c0631eb8f..3c1bdbef9 100644 --- a/src/lang/std/sketchcombos.ts +++ b/src/lang/std/sketchcombos.ts @@ -1220,3 +1220,38 @@ function createLastSeg(isX: boolean): CallExpression { function getArgLiteralVal(arg: Value): number { return arg?.type === 'Literal' ? Number(arg.value) : 0 } + +export function getConstraintLevelFromSourceRange( + cursorRange: Range, + ast: Program +): 'free' | 'partial' | 'full' { + const { node: sketchFnExp } = getNodeFromPath( + ast, + getNodePathFromSourceRange(ast, cursorRange) + ) + const name = sketchFnExp?.callee?.name as TooTip + if (!toolTips.includes(name)) return 'free' + + const firstArg = getFirstArg(sketchFnExp) + + // check if the function is fully constrained + if (Array.isArray(firstArg.val)) { + const [a, b] = firstArg.val + if (a?.type !== 'Literal' && b?.type !== 'Literal') return 'full' + } else { + if (firstArg.val?.type !== 'Literal') return 'full' + } + + // check if the function has no constraints + const isTwoValFree = + Array.isArray(firstArg.val) && + firstArg.val?.[0]?.type === 'Literal' && + firstArg.val?.[1]?.type === 'Literal' + const isOneValFree = + !Array.isArray(firstArg.val) && firstArg.val?.type === 'Literal' + + if (isTwoValFree) return 'free' + if (isOneValFree) return 'partial' + + return 'partial' +}