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'