diff --git a/src/Toolbar.tsx b/src/Toolbar.tsx
index 64eddd669..3e69de67f 100644
--- a/src/Toolbar.tsx
+++ b/src/Toolbar.tsx
@@ -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 = () => {
+
+
)
}
diff --git a/src/components/Toolbar/SetHorzDistance.tsx b/src/components/Toolbar/SetHorzDistance.tsx
new file mode 100644
index 000000000..627f3cb56
--- /dev/null
+++ b/src/components/Toolbar/SetHorzDistance.tsx
@@ -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()
+ 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,
+ horOrVert
+ )
+ setTransformInfos(theTransforms)
+
+ const _enableEqual =
+ secondaryVarDecs.length === 1 &&
+ isAllTooltips &&
+ isOthersLinkedToPrimary &&
+ theTransforms.every(Boolean)
+ setEnable(_enableEqual)
+ }, [guiMode, selectionRanges])
+ if (guiMode.mode !== 'sketch') return null
+
+ return (
+
+ )
+}
diff --git a/src/lang/std/sketch.ts b/src/lang/std/sketch.ts
index 14f52224c..30354ac44 100644
--- a/src/lang/std/sketch.ts
+++ b/src/lang/std/sketch.ts
@@ -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(
_node,
pathToNode,
'PipeExpression'
)
- const newLine = createCallExpression('lineTo', [
- createArrayExpression([createLiteral(to[0]), createLiteral(to[1])]),
- createPipeSubstitution(),
- ])
- pipe.body = [...pipe.body, newLine]
+
+ 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(
_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')
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')
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')
- 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,
diff --git a/src/lang/std/sketchConstraints.ts b/src/lang/std/sketchConstraints.ts
index 19beca929..759d752c2 100644
--- a/src/lang/std/sketchConstraints.ts
+++ b/src/lang/std/sketchConstraints.ts
@@ -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'
diff --git a/src/lang/std/sketchcombos.test.ts b/src/lang/std/sketchcombos.test.ts
index 152036547..d57fffad4 100644
--- a/src/lang/std/sketchcombos.test.ts
+++ b/src/lang/std/sketchcombos.test.ts
@@ -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)
+}
diff --git a/src/lang/std/sketchcombos.ts b/src/lang/std/sketchcombos.ts
index 09e19b35e..adb5149c6 100644
--- a/src/lang/std/sketchcombos.ts
+++ b/src/lang/std/sketchcombos.ts
@@ -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
+}
diff --git a/src/lang/std/std.ts b/src/lang/std/std.ts
index c2ce6e654..0d4edc41b 100644
--- a/src/lang/std/std.ts
+++ b/src/lang/std/std.ts
@@ -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,
diff --git a/src/lang/std/stdTypes.ts b/src/lang/std/stdTypes.ts
index f3dd5941e..98d203740 100644
--- a/src/lang/std/stdTypes.ts
+++ b/src/lang/std/stdTypes.ts
@@ -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