initial set horz-vert distance constraint (#42)

inital set horz-vert distance constraint
This commit is contained in:
Kurt Hutten
2023-03-05 07:34:56 +11:00
committed by GitHub
parent 68dbe8e035
commit 4c554b6549
8 changed files with 357 additions and 32 deletions

View File

@ -3,6 +3,7 @@ import { extrudeSketch, sketchOnExtrudedFace } from './lang/modifyAst'
import { getNodePathFromSourceRange } from './lang/queryAst'
import { HorzVert } from './components/Toolbar/HorzVert'
import { Equal } from './components/Toolbar/Equal'
import { SetHorzDistance } from './components/Toolbar/SetHorzDistance'
export const Toolbar = () => {
const {
@ -156,6 +157,8 @@ export const Toolbar = () => {
<HorzVert horOrVert="horizontal" />
<HorzVert horOrVert="vertical" />
<Equal />
<SetHorzDistance horOrVert="setHorzDistance" />
<SetHorzDistance horOrVert="setVertDistance" />
</div>
)
}

View File

@ -0,0 +1,97 @@
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,
transformAstForSketchLines,
getTransformInfos,
} from '../../lang/std/sketchcombos'
export const SetHorzDistance = ({
horOrVert,
}: {
horOrVert: 'setHorzDistance' | 'setVertDistance'
}) => {
const { guiMode, selectionRanges, ast, programMemory, updateAst } = useStore(
(s) => ({
guiMode: s.guiMode,
ast: s.ast,
updateAst: s.updateAst,
selectionRanges: s.selectionRanges,
programMemory: s.programMemory,
})
)
const [enable, setEnable] = useState(false)
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
useEffect(() => {
if (!ast) return
const paths = selectionRanges.map((selectionRange) =>
getNodePathFromSourceRange(ast, selectionRange)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node
)
const varDecs = paths.map(
(pathToNode) =>
getNodeFromPath<VariableDeclarator>(
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,
horOrVert
)
setTransformInfos(theTransforms)
const _enableEqual =
secondaryVarDecs.length === 1 &&
isAllTooltips &&
isOthersLinkedToPrimary &&
theTransforms.every(Boolean)
setEnable(_enableEqual)
}, [guiMode, selectionRanges])
if (guiMode.mode !== 'sketch') return null
return (
<button
onClick={() =>
transformInfos &&
ast &&
updateAst(
transformAstForSketchLines({
ast,
selectionRanges,
transformInfos,
programMemory,
})?.modifiedAst
)
}
className={`border m-1 px-1 rounded ${
enable ? 'bg-gray-50 text-gray-800' : 'bg-gray-200 text-gray-400'
}`}
disabled={!enable}
title="yo dawg"
>
{horOrVert}
</button>
)
}

View File

@ -89,12 +89,11 @@ export function createFirstArg(
export const lineTo: SketchLineHelper = {
fn: (
{ sourceRange, programMemory },
{ sourceRange },
data:
| [number, number]
| {
to: [number, number]
// name?: string
tag?: string
},
previousSketch: SketchGroup
@ -135,18 +134,38 @@ export const lineTo: SketchLineHelper = {
value: [...sketchGroup.value, currentPath],
}
},
add: ({ node, pathToNode, to }) => {
add: ({
node,
pathToNode,
to,
createCallback,
replaceExisting,
referencedSegment,
}) => {
const _node = { ...node }
const { node: pipe } = getNodeFromPath<PipeExpression>(
_node,
pathToNode,
'PipeExpression'
)
const newLine = createCallExpression('lineTo', [
createArrayExpression([createLiteral(to[0]), createLiteral(to[1])]),
const newVals: [Value, Value] = [
createLiteral(roundOff(to[0], 2)),
createLiteral(roundOff(to[1], 2)),
]
const newLine = createCallback
? createCallback(newVals, referencedSegment)
: createCallExpression('lineTo', [
createArrayExpression(newVals),
createPipeSubstitution(),
])
const callIndex = getLastIndex(pathToNode)
if (replaceExisting) {
pipe.body[callIndex] = newLine
} else {
pipe.body = [...pipe.body, newLine]
}
return {
modifiedAst: _node,
pathToNode,
@ -237,7 +256,6 @@ export const line: SketchLineHelper = {
pathToNode,
'PipeExpression'
)
if (!from) throw new Error('no from') // todo #29 remove
const { node: varDec } = getNodeFromPath<VariableDeclarator>(
_node,
pathToNode,
@ -431,7 +449,6 @@ export const xLine: SketchLineHelper = {
add: ({ node, pathToNode, to, from, replaceExisting, createCallback }) => {
const _node = { ...node }
const getNode = getNodeFromPathCurry(_node, pathToNode)
if (!from) throw new Error('no from') // todo #29 remove
const { node: pipe } = getNode<PipeExpression>('PipeExpression')
const newVal = createLiteral(roundOff(to[0] - from[0], 2))
@ -487,7 +504,6 @@ export const yLine: SketchLineHelper = {
add: ({ node, pathToNode, to, from, replaceExisting, createCallback }) => {
const _node = { ...node }
const getNode = getNodeFromPathCurry(_node, pathToNode)
if (!from) throw new Error('no from') // todo #29 remove
const { node: pipe } = getNode<PipeExpression>('PipeExpression')
const newVal = createLiteral(roundOff(to[1] - from[1], 2))
const newLine = createCallback
@ -577,7 +593,6 @@ export const angledLine: SketchLineHelper = {
const getNode = getNodeFromPathCurry(_node, pathToNode)
const { node: pipe } = getNode<PipeExpression>('PipeExpression')
if (!from) throw new Error('no from') // todo #29 remove
const newAngleVal = createLiteral(roundOff(getAngle(from, to), 0))
const newLengthVal = createLiteral(roundOff(getLength(from, to), 2))
const newLine = createCallback
@ -668,7 +683,6 @@ export const angledLineOfXLength: SketchLineHelper = {
const variableName = varDec.id.name
const sketch = previousProgramMemory?.root?.[variableName]
if (sketch.type !== 'sketchGroup') throw new Error('not a sketchGroup')
if (!from) throw new Error('no from') // todo #29 remove
const angle = createLiteral(roundOff(getAngle(from, to), 0))
const xLength = createLiteral(roundOff(Math.abs(from[0] - to[0]), 2) || 0.1)
const newLine = createCallback
@ -762,7 +776,6 @@ export const angledLineOfYLength: SketchLineHelper = {
const variableName = varDec.id.name
const sketch = previousProgramMemory?.root?.[variableName]
if (sketch.type !== 'sketchGroup') throw new Error('not a sketchGroup')
if (!from) throw new Error('no from') // todo #29 remove
const angle = createLiteral(roundOff(getAngle(from, to), 0))
const yLength = createLiteral(roundOff(Math.abs(from[1] - to[1]), 2) || 0.1)
@ -848,7 +861,6 @@ export const angledLineToX: SketchLineHelper = {
pathToNode,
'PipeExpression'
)
if (!from) throw new Error('no from') // todo #29 remove
const angle = createLiteral(roundOff(getAngle(from, to), 0))
const xArg = createLiteral(roundOff(to[0], 2))
const newLine = createCallback
@ -930,7 +942,6 @@ export const angledLineToY: SketchLineHelper = {
pathToNode,
'PipeExpression'
)
if (!from) throw new Error('no from') // todo #29 remove
const angle = createLiteral(roundOff(getAngle(from, to), 0))
const yArg = createLiteral(roundOff(to[1], 2))
const newLine = createCallback
@ -1067,6 +1078,7 @@ export function replaceSketchLine({
to,
from,
createCallback,
referencedSegment,
}: {
node: Program
programMemory: ProgramMemory
@ -1075,6 +1087,7 @@ export function replaceSketchLine({
to: [number, number]
from: [number, number]
createCallback: TransformCallback
referencedSegment?: Path
}): { modifiedAst: Program } {
if (!toolTips.includes(fnName)) throw new Error('not a tooltip')
const _node = { ...node }
@ -1085,6 +1098,7 @@ export function replaceSketchLine({
node: _node,
previousProgramMemory: programMemory,
pathToNode: thePath,
referencedSegment,
to,
from,
replaceExisting: true,

View File

@ -33,6 +33,28 @@ export const segLen: InternalFn = (
)
}
function segEndFactory(which: 'x' | 'y'): InternalFn {
return (_, 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 which === 'x' ? line.to[0] : line.to[1]
}
}
export const segEndX: InternalFn = segEndFactory('x')
export const segEndY: InternalFn = segEndFactory('y')
function lastSegFactory(which: 'x' | 'y'): InternalFn {
return (_, sketchGroup: SketchGroup): number => {
const lastLine = sketchGroup?.value[sketchGroup.value.length - 1]
return which === 'x' ? lastLine.to[0] : lastLine.to[1]
}
}
export const lastSegX: InternalFn = lastSegFactory('x')
export const lastSegY: InternalFn = lastSegFactory('y')
function angleToMatchLengthFactory(which: 'x' | 'y'): InternalFn {
return (_, segName: string, to: number, sketchGroup: SketchGroup): number => {
const isX = which === 'x'

View File

@ -5,9 +5,10 @@ import {
getTransformInfos,
transformAstForSketchLines,
transformAstForHorzVert,
ConstraintType,
} from './sketchcombos'
import { initPromise } from '../rust'
import { Ranges, TooTip } from '../../useStore'
import { TooTip } from '../../useStore'
import { executor } from '../../lang/executor'
import { recast } from '../../lang/recast'
@ -242,14 +243,6 @@ const part001 = startSketchAt([0, 0])
|> angledLineToY([301, myVar], %) // select for vertical constraint 10
show(part001)`
it('It should transform horizontal lines the ast', () => {
// const inputScript = `const myVar = 2
// const part001 = startSketchAt([0, 0])
// |> lineTo([1, 1], %)
// |> line([-6.28, 1.4], %) // select for horizontal constraint 1
// |> line([-1.07, myVar], %) // select for vertical constraint 1
// |> line([myVar, 4.32], %) // select for horizontal constraint 2
// |> line([6.35, -1.12], %) // select for vertical constraint 2
// show(part001)`
const expectModifiedScript = `const myVar = 2
const myVar2 = 12
const myVar3 = -10
@ -348,3 +341,89 @@ show(part001)`
expect(newCode).toBe(expectModifiedScript)
})
})
describe('testing transformAstForSketchLines for vertical and horizontal distance constraints', () => {
describe('testing setHorzDistance for line', () => {
const inputScript = `const myVar = 1
const part001 = startSketchAt([0, 0])
|> line([0.31, 1.67], %) // base selection
|> line([0.45, 1.46], %)
|> line([0.45, 1.46], %) // free
|> line([myVar, 0.01], %) // xRelative
|> line([0.7, myVar], %) // yRelative
show(part001)`
it('testing for free to horizontal and vertical distance', () => {
const expectedHorizontalCode = helperThing(
inputScript,
['// base selection', '// free'],
'setHorzDistance'
)
const expectedVerticalCode = helperThing(
inputScript,
['// base selection', '// free'],
'setVertDistance'
)
expect(expectedHorizontalCode).toContain(
`lineTo([segEndX('seg01', %) + 1.21, 4.59], %) // free`
)
expect(expectedVerticalCode).toContain(
`lineTo([1.21, segEndY('seg01', %) + 4.59], %) // free`
)
})
it('testing for xRelative to vertical distance', () => {
const expectedCode = helperThing(
inputScript,
['// base selection', '// xRelative'],
'setVertDistance'
)
expect(expectedCode).toContain(`|> lineTo([
lastSegX(%) + myVar,
segEndY('seg01', %) + 4.6
], %) // xRelative`)
})
it('testing for yRelative to horizontal distance', () => {
const expectedCode = helperThing(
inputScript,
['// base selection', '// yRelative'],
'setHorzDistance'
)
expect(expectedCode).toContain(`|> lineTo([
segEndX('seg01', %) + 2.91,
lastSegY(%) + myVar
], %) // yRelative`)
})
})
})
function helperThing(
inputScript: string,
linesOfInterest: string[],
constraint: ConstraintType
): string {
const ast = abstractSyntaxTree(lexer(inputScript))
const selectionRanges = inputScript
.split('\n')
.filter((ln) =>
linesOfInterest.some((lineOfInterest) => ln.includes(lineOfInterest))
)
.map((ln) => {
const comment = ln.split('//')[1]
const start = inputScript.indexOf('//' + comment) - 7
return [start, start]
}) as [number, number][]
const programMemory = executor(ast)
const transformInfos = getTransformInfos(
selectionRanges.slice(1),
ast,
constraint
)
const newAst = transformAstForSketchLines({
ast,
selectionRanges,
transformInfos,
programMemory,
})?.modifiedAst
return recast(newAst)
}

View File

@ -21,8 +21,9 @@ import {
giveSketchFnCallTag,
} from '../modifyAst'
import { createFirstArg, getFirstArg, replaceSketchLine } from './sketch'
import { ProgramMemory } from '../executor'
import { Path, ProgramMemory } from '../executor'
import { getSketchSegmentIndexFromSourceRange } from './sketchConstraints'
import { roundOff } from '../../lib/utils'
type LineInputsType =
| 'xAbsolute'
@ -37,6 +38,8 @@ export type ConstraintType =
| 'vertical'
| 'horizontal'
| 'equalangle'
| 'setHorzDistance'
| 'setVertDistance'
function createCallWrapper(
a: TooTip,
@ -54,7 +57,8 @@ export function replaceSketchCall(
ast: Program,
range: Range,
transformTo: TooTip,
createCallback: TransformCallback
createCallback: TransformCallback,
referenceSegName: string
): { modifiedAst: Program } {
const path = getNodePathFromSourceRange(ast, range)
const getNode = getNodeFromPathCurry(ast, path)
@ -65,11 +69,15 @@ export function replaceSketchCall(
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 { to, from } = seg
const { modifiedAst } = replaceSketchLine({
node: ast,
programMemory,
sourceRange: range,
referencedSegment,
fnName: transformTo || (callExp.callee.name as TooTip),
to,
from,
@ -85,7 +93,7 @@ export type TransformInfo = {
varValB: Value // y / length or x y for angledLineOfXlength etc
referenceSegName: string
tag?: Value
}) => (args: [Value, Value]) => Value
}) => (args: [Value, Value], referencedSegment?: Path) => Value
}
type TransformMap = {
@ -163,6 +171,57 @@ const getAngleLengthSign = (arg: Value, legAngleVal: BinaryPart) => {
return normalisedAngle > 90 ? createUnaryExpression(legAngleVal) : legAngleVal
}
const setHorzVertDistanceCreateNode =
(isX = true): TransformInfo['createNode'] =>
({ referenceSegName, tag }) => {
return (args, referencedSegment) => {
const makeBinExp = (index: 0 | 1) => {
const arg = getArgLiteralVal(args?.[index])
return createBinaryExpression([
createSegEnd(referenceSegName, isX),
'+',
createLiteral(
roundOff(arg - (referencedSegment?.to?.[index] || 0), 2)
),
])
}
return createCallWrapper(
'lineTo',
isX ? [makeBinExp(0), args[1]] : [args[0], makeBinExp(1)],
tag
)
}
}
const setHorzVertDistanceConstraintLineCreateNode =
(isX: boolean): TransformInfo['createNode'] =>
({ referenceSegName, tag, varValA, varValB }) => {
const varVal = (isX ? varValB : varValA) as BinaryPart
const varValBinExp = createBinaryExpression([
createLastSeg(!isX),
'+',
varVal,
])
return (args, referencedSegment) => {
const makeBinExp = (index: 0 | 1) => {
const arg = getArgLiteralVal(args?.[index])
return createBinaryExpression([
createSegEnd(referenceSegName, isX),
'+',
createLiteral(
roundOff(arg - (referencedSegment?.to?.[index] || 0), 2)
),
])
}
return createCallWrapper(
'lineTo',
isX ? [makeBinExp(0), varValBinExp] : [varValBinExp, makeBinExp(1)],
tag
)
}
}
const transformMap: TransformMap = {
line: {
xRelative: {
@ -188,6 +247,10 @@ const transformMap: TransformMap = {
() =>
createCallWrapper('xLine', varValA, tag),
},
setVertDistance: {
tooltip: 'lineTo',
createNode: setHorzVertDistanceConstraintLineCreateNode(false),
},
},
yRelative: {
equalLength: {
@ -212,6 +275,10 @@ const transformMap: TransformMap = {
() =>
createCallWrapper('yLine', varValB, tag),
},
setHorzDistance: {
tooltip: 'lineTo',
createNode: setHorzVertDistanceConstraintLineCreateNode(true),
},
},
free: {
equalLength: {
@ -232,6 +299,14 @@ const transformMap: TransformMap = {
(args) =>
createCallWrapper('yLine', args[1], tag),
},
setHorzDistance: {
tooltip: 'lineTo',
createNode: setHorzVertDistanceCreateNode(true),
},
setVertDistance: {
tooltip: 'lineTo',
createNode: setHorzVertDistanceCreateNode(false),
},
},
},
lineTo: {
@ -792,7 +867,8 @@ export function transformAstForSketchLines({
referenceSegName: tag,
varValA,
varValB,
})
}),
tag
)
node = modifiedAst
})
@ -837,7 +913,8 @@ export function transformAstForHorzVert({
varValA,
varValB,
tag,
})
}),
tag?.type === 'Literal' ? String(tag.value) : ''
)
node = modifiedAst
})
@ -850,3 +927,20 @@ function createSegLen(referenceSegName: string): Value {
createPipeSubstitution(),
])
}
function createSegEnd(referenceSegName: string, isX: boolean): CallExpression {
return createCallExpression(isX ? 'segEndX' : 'segEndY', [
createLiteral(referenceSegName),
createPipeSubstitution(),
])
}
function createLastSeg(isX: boolean): CallExpression {
return createCallExpression(isX ? 'lastSegX' : 'lastSegY', [
createPipeSubstitution(),
])
}
function getArgLiteralVal(arg: Value): number {
return arg?.type === 'Literal' ? Number(arg.value) : 0
}

View File

@ -17,6 +17,10 @@ import {
segLen,
angleToMatchLengthX,
angleToMatchLengthY,
segEndX,
segEndY,
lastSegX,
lastSegY,
} from './sketchConstraints'
import { extrude, getExtrudeWallTransform } from './extrude'
import { Quaternion, Vector3 } from 'three'
@ -100,6 +104,10 @@ export const internalFns: { [key in InternalFnNames]: InternalFn } = {
legLen,
legAngX,
legAngY,
segEndX,
segEndY,
lastSegX,
lastSegY,
segLen,
angleToMatchLengthX,
angleToMatchLengthY,

View File

@ -24,6 +24,10 @@ export type InternalFnNames =
| 'legLen'
| 'legAngX'
| 'legAngY'
| 'segEndX'
| 'segEndY'
| 'lastSegX'
| 'lastSegY'
| 'segLen'
| 'angleToMatchLengthX'
| 'angleToMatchLengthY'
@ -52,7 +56,8 @@ export interface ModifyAstBase {
interface addCall extends ModifyAstBase {
to: [number, number]
from?: [number, number]
from: [number, number]
referencedSegment?: Path
replaceExisting?: boolean
createCallback?: TransformCallback // TODO: #29 probably should not be optional
}
@ -62,7 +67,10 @@ interface updateArgs extends ModifyAstBase {
to: [number, number]
}
export type TransformCallback = (args: [Value, Value]) => Value
export type TransformCallback = (
args: [Value, Value],
referencedSegment?: Path
) => Value
export type SketchCallTransfromMap = {
[key in TooTip]: TransformCallback