diff --git a/src/Toolbar.tsx b/src/Toolbar.tsx index 84391b693..f9af745c8 100644 --- a/src/Toolbar.tsx +++ b/src/Toolbar.tsx @@ -2,7 +2,8 @@ import { useStore, toolTips } from './useStore' import { extrudeSketch, sketchOnExtrudedFace } from './lang/modifyAst' import { getNodePathFromSourceRange } from './lang/queryAst' import { HorzVert } from './components/Toolbar/HorzVert' -import { Equal } from './components/Toolbar/Equal' +import { EqualLength } from './components/Toolbar/EqualLength' +import { EqualAngle } from './components/Toolbar/EqualAngle' import { SetHorzDistance } from './components/Toolbar/SetHorzDistance' import { SetAngleLength } from './components/Toolbar/SetAngleLength' @@ -157,7 +158,8 @@ export const Toolbar = () => {

- + + diff --git a/src/components/Toolbar/EqualAngle.tsx b/src/components/Toolbar/EqualAngle.tsx new file mode 100644 index 000000000..14f52b995 --- /dev/null +++ b/src/components/Toolbar/EqualAngle.tsx @@ -0,0 +1,93 @@ +import { useState, useEffect } from 'react' +import { toolTips, useStore } from '../../useStore' +import { Value, VariableDeclarator } from '../../lang/abstractSyntaxTree' +import { + getNodePathFromSourceRange, + getNodeFromPath, +} from '../../lang/queryAst' +import { isSketchVariablesLinked } from '../../lang/std/sketchConstraints' +import { + TransformInfo, + transformSecondarySketchLinesTagFirst, + getTransformInfos, +} from '../../lang/std/sketchcombos' + +export const EqualAngle = () => { + const { guiMode, selectionRanges, ast, programMemory, updateAst } = useStore( + (s) => ({ + guiMode: s.guiMode, + ast: s.ast, + updateAst: s.updateAst, + selectionRanges: s.selectionRanges, + programMemory: s.programMemory, + }) + ) + const [enableEqual, setEnableEqual] = useState(false) + const [transformInfos, setTransformInfos] = useState() + useEffect(() => { + if (!ast) return + const paths = selectionRanges.map((selectionRange) => + getNodePathFromSourceRange(ast, selectionRange) + ) + const nodes = paths.map( + (pathToNode) => getNodeFromPath(ast, pathToNode).node + ) + const varDecs = paths.map( + (pathToNode) => + getNodeFromPath( + ast, + pathToNode, + 'VariableDeclarator' + )?.node + ) + const primaryLine = varDecs[0] + const secondaryVarDecs = varDecs.slice(1) + const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) => + isSketchVariablesLinked(secondary, primaryLine, ast) + ) + const isAllTooltips = nodes.every( + (node) => + node?.type === 'CallExpression' && + toolTips.includes(node.callee.name as any) + ) + + const theTransforms = getTransformInfos( + selectionRanges.slice(1), + ast, + 'equalAngle' + ) + setTransformInfos(theTransforms) + + const _enableEqual = + !!secondaryVarDecs.length && + isAllTooltips && + isOthersLinkedToPrimary && + theTransforms.every(Boolean) + setEnableEqual(_enableEqual) + }, [guiMode, selectionRanges]) + if (guiMode.mode !== 'sketch') return null + + return ( + + ) +} diff --git a/src/components/Toolbar/Equal.tsx b/src/components/Toolbar/EqualLength.tsx similarity index 98% rename from src/components/Toolbar/Equal.tsx rename to src/components/Toolbar/EqualLength.tsx index 397dd8698..98e20d854 100644 --- a/src/components/Toolbar/Equal.tsx +++ b/src/components/Toolbar/EqualLength.tsx @@ -12,7 +12,7 @@ import { getTransformInfos, } from '../../lang/std/sketchcombos' -export const Equal = () => { +export const EqualLength = () => { const { guiMode, selectionRanges, ast, programMemory, updateAst } = useStore( (s) => ({ guiMode: s.guiMode, @@ -87,7 +87,7 @@ export const Equal = () => { disabled={!enableEqual} title="yo dawg" > - Equal + EqualLength ) } diff --git a/src/lang/std/sketch.ts b/src/lang/std/sketch.ts index b628ca6ce..316b1b607 100644 --- a/src/lang/std/sketch.ts +++ b/src/lang/std/sketch.ts @@ -265,6 +265,7 @@ export const line: SketchLineHelper = { to, from, replaceExisting, + referencedSegment, createCallback, }) => { const _node = { ...node } @@ -285,19 +286,25 @@ export const line: SketchLineHelper = { const newXVal = createLiteral(roundOff(to[0] - from[0], 2)) const newYVal = createLiteral(roundOff(to[1] - from[1], 2)) - const newLine = createCallback - ? createCallback([newXVal, newYVal]).callExp - : createCallExpression('line', [ - createArrayExpression([newXVal, newYVal]), - createPipeSubstitution(), - ]) - const callIndex = getLastIndex(pathToNode) - if (replaceExisting) { - pipe.body[callIndex] = newLine - } else { - pipe.body = [...pipe.body, newLine] + if (replaceExisting && createCallback) { + const callIndex = getLastIndex(pathToNode) + const { callExp, valueUsedInTransform } = createCallback( + [newXVal, newYVal], + referencedSegment + ) + pipe.body[callIndex] = callExp + return { + modifiedAst: _node, + pathToNode, + valueUsedInTransform, + } } + const callExp = createCallExpression('line', [ + createArrayExpression([newXVal, newYVal]), + createPipeSubstitution(), + ]) + pipe.body = [...pipe.body, callExp] return { modifiedAst: _node, pathToNode, @@ -369,16 +376,22 @@ export const xLineTo: SketchLineHelper = { const { node: pipe } = getNode('PipeExpression') const newVal = createLiteral(roundOff(to[0], 2)) - const newLine = createCallback - ? createCallback([newVal, newVal]).callExp - : createCallExpression('xLineTo', [newVal, createPipeSubstitution()]) - const callIndex = getLastIndex(pathToNode) - if (replaceExisting) { - pipe.body[callIndex] = newLine - } else { - pipe.body = [...pipe.body, newLine] + if (replaceExisting && createCallback) { + const callIndex = getLastIndex(pathToNode) + const { callExp, valueUsedInTransform } = createCallback([newVal, newVal]) + pipe.body[callIndex] = callExp + return { + modifiedAst: _node, + pathToNode, + valueUsedInTransform, + } } + const callExp = createCallExpression('xLineTo', [ + newVal, + createPipeSubstitution(), + ]) + pipe.body = [...pipe.body, callExp] return { modifiedAst: _node, pathToNode, @@ -430,15 +443,22 @@ export const yLineTo: SketchLineHelper = { const { node: pipe } = getNode('PipeExpression') const newVal = createLiteral(roundOff(to[1], 2)) - const newLine = createCallback - ? createCallback([newVal, newVal]).callExp - : createCallExpression('yLineTo', [newVal, createPipeSubstitution()]) - const callIndex = getLastIndex(pathToNode) - if (replaceExisting) { - pipe.body[callIndex] = newLine - } else { - pipe.body = [...pipe.body, newLine] + + if (replaceExisting && createCallback) { + const callIndex = getLastIndex(pathToNode) + const { callExp, valueUsedInTransform } = createCallback([newVal, newVal]) + pipe.body[callIndex] = callExp + return { + modifiedAst: _node, + pathToNode, + valueUsedInTransform, + } } + const callExp = createCallExpression('yLineTo', [ + newVal, + createPipeSubstitution(), + ]) + pipe.body = [...pipe.body, callExp] return { modifiedAst: _node, pathToNode, @@ -640,7 +660,15 @@ export const angledLine: SketchLineHelper = { value: [...sketchGroup.value, currentPath], } }, - add: ({ node, pathToNode, to, from, createCallback, replaceExisting }) => { + add: ({ + node, + pathToNode, + to, + from, + createCallback, + replaceExisting, + referencedSegment, + }) => { const _node = { ...node } const getNode = getNodeFromPathCurry(_node, pathToNode) const { node: pipe } = getNode('PipeExpression') @@ -652,12 +680,12 @@ export const angledLine: SketchLineHelper = { createPipeSubstitution(), ]) - const callIndex = getLastIndex(pathToNode) if (replaceExisting && createCallback) { - const { callExp, valueUsedInTransform } = createCallback([ - newAngleVal, - newLengthVal, - ]) + const callIndex = getLastIndex(pathToNode) + const { callExp, valueUsedInTransform } = createCallback( + [newAngleVal, newLengthVal], + referencedSegment + ) pipe.body[callIndex] = callExp return { modifiedAst: _node, diff --git a/src/lang/std/sketchConstraints.ts b/src/lang/std/sketchConstraints.ts index 5ed5e8aae..d496a3601 100644 --- a/src/lang/std/sketchConstraints.ts +++ b/src/lang/std/sketchConstraints.ts @@ -1,3 +1,4 @@ +import { getAngle } from '../../lib/utils' import { Range, TooTip, toolTips } from '../../useStore' import { Program, @@ -33,6 +34,17 @@ export const segLen: InternalFn = ( ) } +export const segAng: 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 getAngle(line.from, line.to) +} + function segEndFactory(which: 'x' | 'y'): InternalFn { return (_, segName: string, sketchGroup: SketchGroup): number => { const line = diff --git a/src/lang/std/sketchcombos.test.ts b/src/lang/std/sketchcombos.test.ts index d46217632..a8e20db5a 100644 --- a/src/lang/std/sketchcombos.test.ts +++ b/src/lang/std/sketchcombos.test.ts @@ -366,10 +366,10 @@ show(part001)` 'setVertDistance' ) expect(expectedHorizontalCode).toContain( - `lineTo([segEndX('seg01', %) + 1.21, 4.59], %) // free` + `lineTo([segEndX('seg01', %) + 0.9, 4.59], %) // free` ) expect(expectedVerticalCode).toContain( - `lineTo([1.21, segEndY('seg01', %) + 4.59], %) // free` + `lineTo([1.21, segEndY('seg01', %) + 2.92], %) // free` ) }) it('testing for xRelative to vertical distance', () => { @@ -380,7 +380,7 @@ show(part001)` ) expect(expectedCode).toContain(`|> lineTo([ lastSegX(%) + myVar, - segEndY('seg01', %) + 4.6 + segEndY('seg01', %) + 2.93 ], %) // xRelative`) }) it('testing for yRelative to horizontal distance', () => { @@ -390,7 +390,7 @@ show(part001)` 'setHorzDistance' ) expect(expectedCode).toContain(`|> lineTo([ - segEndX('seg01', %) + 2.91, + segEndX('seg01', %) + 2.6, lastSegY(%) + myVar ], %) // yRelative`) }) diff --git a/src/lang/std/sketchcombos.ts b/src/lang/std/sketchcombos.ts index 36aecd8eb..41ba2ef91 100644 --- a/src/lang/std/sketchcombos.ts +++ b/src/lang/std/sketchcombos.ts @@ -1,5 +1,5 @@ import { TransformCallback } from './stdTypes' -import { Ranges, toolTips, TooTip } from '../../useStore' +import { Ranges, toolTips, TooTip, Range } from '../../useStore' import { BinaryPart, CallExpression, @@ -24,7 +24,7 @@ import { import { createFirstArg, getFirstArg, replaceSketchLine } from './sketch' import { ProgramMemory } from '../executor' import { getSketchSegmentIndexFromSourceRange } from './sketchConstraints' -import { roundOff } from '../../lib/utils' +import { getAngle, roundOff } from '../../lib/utils' type LineInputsType = | 'xAbsolute' @@ -38,7 +38,7 @@ export type ConstraintType = | 'equalLength' | 'vertical' | 'horizontal' - | 'equalangle' + | 'equalAngle' | 'setHorzDistance' | 'setVertDistance' | 'setAngle' @@ -85,7 +85,6 @@ const xyLineSetLength = ): TransformInfo['createNode'] => ({ referenceSegName, tag, forceValueUsedInTransform }) => (args) => { - console.log('args', args) const segRef = createSegLen(referenceSegName) const lineVal = forceValueUsedInTransform ? forceValueUsedInTransform @@ -102,12 +101,17 @@ const basicAngledLineCreateNode = varValToUse: 'ang' | 'len' | 'none' = 'none' ): TransformInfo['createNode'] => ({ referenceSegName, tag, forceValueUsedInTransform, varValA, varValB }) => - (args) => { + (args, path) => { + const refAng = path ? getAngle(path?.from, path?.to) : 0 const nonForcedAng = varValToUse === 'ang' ? varValA : referenceSeg === 'ang' - ? createSegAngle(referenceSegName) + ? getClosesAngleDirection( + args[0], + refAng, + createSegAngle(referenceSegName) as BinaryPart + ) : args[0] const nonForcedLen = varValToUse === 'len' @@ -186,6 +190,19 @@ const getAngleLengthSign = (arg: Value, legAngleVal: BinaryPart) => { return normalisedAngle > 90 ? createUnaryExpression(legAngleVal) : legAngleVal } +function getClosesAngleDirection( + arg: Value, + refAngle: number, + angleVal: BinaryPart +) { + const currentAng = (arg.type === 'Literal' && Number(arg.value)) || 0 + const angDiff = Math.abs(currentAng - refAngle) + const normalisedAngle = ((angDiff % 360) + 360) % 360 // between 0 and 180 + return normalisedAngle > 90 + ? createBinaryExpression([angleVal, '+', createLiteral(180)]) + : angleVal +} + const setHorzVertDistanceCreateNode = ( xOrY: 'x' | 'y', @@ -211,6 +228,55 @@ const setHorzVertDistanceCreateNode = ) } } +const setHorzVertDistanceForAngleLineCreateNode = + ( + xOrY: 'x' | 'y', + index = xOrY === 'x' ? 0 : 1 + ): TransformInfo['createNode'] => + ({ referenceSegName, tag, forceValueUsedInTransform, varValA }) => { + return (args, referencedSegment) => { + const valueUsedInTransform = roundOff( + getArgLiteralVal(args?.[1]) - (referencedSegment?.to?.[index] || 0), + 2 + ) + const makeBinExp = createBinaryExpression([ + createSegEnd(referenceSegName, !index), + '+', + (forceValueUsedInTransform as BinaryPart) || + createLiteral(valueUsedInTransform), + ]) + return createCallWrapper( + xOrY === 'x' ? 'angledLineToX' : 'angledLineToY', + [varValA, makeBinExp], + tag, + valueUsedInTransform + ) + } + } + +const setHorVertDistanceForXYLines = + (xOrY: 'x' | 'y'): TransformInfo['createNode'] => + ({ referenceSegName, tag, forceValueUsedInTransform }) => { + return (args, referencedSegment) => { + const index = xOrY === 'x' ? 0 : 1 + const valueUsedInTransform = roundOff( + getArgLiteralVal(args?.[index]) - (referencedSegment?.to?.[index] || 0), + 2 + ) + const makeBinExp = createBinaryExpression([ + createSegEnd(referenceSegName, xOrY === 'x'), + '+', + (forceValueUsedInTransform as BinaryPart) || + createLiteral(valueUsedInTransform), + ]) + return createCallWrapper( + xOrY === 'x' ? 'xLineTo' : 'yLineTo', + makeBinExp, + tag, + valueUsedInTransform + ) + } + } const setHorzVertDistanceConstraintLineCreateNode = (isX: boolean): TransformInfo['createNode'] => @@ -334,6 +400,10 @@ const transformMap: TransformMap = { tooltip: 'angledLine', createNode: basicAngledLineCreateNode('none', 'len'), }, + equalAngle: { + tooltip: 'angledLine', + createNode: basicAngledLineCreateNode('ang'), + }, }, }, lineTo: { @@ -419,7 +489,6 @@ const transformMap: TransformMap = { createNode: ({ varValB, tag, forceValueUsedInTransform }) => (args) => { - console.log(getArgLiteralVal(args[0])) return createCallWrapper( 'angledLineToY', [forceValueUsedInTransform || args[0], varValB], @@ -447,6 +516,14 @@ const transformMap: TransformMap = { tooltip: 'angledLine', createNode: basicAngledLineCreateNode('none', 'len', 'ang'), }, + setVertDistance: { + tooltip: 'angledLineToY', + createNode: setHorzVertDistanceForAngleLineCreateNode('y'), + }, + setHorzDistance: { + tooltip: 'angledLineToX', + createNode: setHorzVertDistanceForAngleLineCreateNode('x'), + }, }, free: { equalLength: { @@ -715,27 +792,7 @@ const transformMap: TransformMap = { }, setHorzDistance: { tooltip: 'xLineTo', - createNode: ({ referenceSegName, tag, forceValueUsedInTransform }) => { - return (args, referencedSegment) => { - console.log('args', args) - const valueUsedInTransform = roundOff( - getArgLiteralVal(args?.[0]) - (referencedSegment?.to?.[0] || 0), - 2 - ) - const makeBinExp = createBinaryExpression([ - createSegEnd(referenceSegName, true), - '+', - (forceValueUsedInTransform as BinaryPart) || - createLiteral(valueUsedInTransform), - ]) - return createCallWrapper( - 'xLineTo', - makeBinExp, - tag, - valueUsedInTransform - ) - } - }, + createNode: setHorVertDistanceForXYLines('x'), }, setLength: { tooltip: 'xLine', @@ -756,6 +813,10 @@ const transformMap: TransformMap = { tooltip: 'yLine', createNode: xyLineSetLength('yLine'), }, + setVertDistance: { + tooltip: 'yLineTo', + createNode: setHorVertDistanceForXYLines('y'), + }, }, }, xLineTo: { @@ -767,6 +828,10 @@ const transformMap: TransformMap = { () => createCallWrapper('xLine', createSegLen(referenceSegName), tag), }, + setLength: { + tooltip: 'xLine', + createNode: xyLineSetLength('xLine'), + }, }, }, yLineTo: { @@ -778,6 +843,10 @@ const transformMap: TransformMap = { () => createCallWrapper('yLine', createSegLen(referenceSegName), tag), }, + setLength: { + tooltip: 'yLine', + createNode: xyLineSetLength('yLine'), + }, }, }, } @@ -946,6 +1015,7 @@ export function transformSecondarySketchLinesTagFirst({ ...transformAstSketchLines({ ast: modifiedAst, selectionRanges: selectionRanges.slice(1), + referencedSegmentRange: primarySelection, transformInfos, programMemory, referenceSegName: tag, @@ -965,6 +1035,7 @@ export function transformAstSketchLines({ programMemory, referenceSegName, forceValueUsedInTransform, + referencedSegmentRange, }: { ast: Program selectionRanges: Ranges @@ -972,6 +1043,7 @@ export function transformAstSketchLines({ programMemory: ProgramMemory referenceSegName: string forceValueUsedInTransform?: Value + referencedSegmentRange?: Range }): { modifiedAst: Program; valueUsedInTransform?: number } { // deep clone since we are mutating in a loop, of which any could fail let node = JSON.parse(JSON.stringify(ast)) @@ -998,9 +1070,12 @@ export function transformAstSketchLines({ if (!sketchGroup || sketchGroup.type !== 'sketchGroup') throw new Error('not a sketch group') const seg = getSketchSegmentIndexFromSourceRange(sketchGroup, range) - const referencedSegment = sketchGroup.value.find( - (path) => path.name === referenceSegName - ) + const referencedSegment = referencedSegmentRange + ? getSketchSegmentIndexFromSourceRange( + sketchGroup, + referencedSegmentRange + ) + : sketchGroup.value.find((path) => path.name === referenceSegName) const { to, from } = seg const { modifiedAst, valueUsedInTransform } = replaceSketchLine({ node: node, @@ -1035,7 +1110,7 @@ function createSegLen(referenceSegName: string): Value { } function createSegAngle(referenceSegName: string): Value { - return createCallExpression('segAngle', [ + return createCallExpression('segAng', [ createLiteral(referenceSegName), createPipeSubstitution(), ]) diff --git a/src/lang/std/std.ts b/src/lang/std/std.ts index 0689af36f..e1f81a6b8 100644 --- a/src/lang/std/std.ts +++ b/src/lang/std/std.ts @@ -16,6 +16,7 @@ import { } from './sketch' import { segLen, + segAng, angleToMatchLengthX, angleToMatchLengthY, segEndX, @@ -143,6 +144,7 @@ export const internalFns: { [key in InternalFnNames]: InternalFn } = { lastSegX, lastSegY, segLen, + segAng, angleToMatchLengthX, angleToMatchLengthY, lineTo: lineTo.fn, diff --git a/src/lang/std/stdTypes.ts b/src/lang/std/stdTypes.ts index f2f5672ed..e19aa7ab5 100644 --- a/src/lang/std/stdTypes.ts +++ b/src/lang/std/stdTypes.ts @@ -29,6 +29,7 @@ export type InternalFnNames = | 'lastSegX' | 'lastSegY' | 'segLen' + | 'segAng' | 'angleToMatchLengthX' | 'angleToMatchLengthY' | 'rx'