Users should be able to select points (not just lines) (#97)

* update selection datastructure to accomodate more selection metadata

* Users should be able to select points (not just lines)
This commit is contained in:
Kurt Hutten
2023-04-03 16:05:25 +10:00
committed by GitHub
parent 7013eb861d
commit a8b68bab6a
25 changed files with 292 additions and 146 deletions

View File

@ -45,6 +45,7 @@ function App() {
errorState,
setProgramMemory,
resetLogs,
selectionRangeTypeMap,
} = useStore((s) => ({
editorView: s.editorView,
setEditorView: s.setEditorView,
@ -61,6 +62,7 @@ function App() {
errorState: s.errorState,
setProgramMemory: s.setProgramMemory,
resetLogs: s.resetLogs,
selectionRangeTypeMap: s.selectionRangeTypeMap,
}))
// const onChange = React.useCallback((value: string, viewUpdate: ViewUpdate) => {
const onChange = (value: string, viewUpdate: ViewUpdate) => {
@ -76,13 +78,30 @@ function App() {
const ranges = viewUpdate.state.selection.ranges
const isChange =
ranges.length !== selectionRange.length ||
ranges.length !== selectionRange.codeBasedSelections.length ||
ranges.some(({ from, to }, i) => {
return from !== selectionRange[i][0] || to !== selectionRange[i][1]
return (
from !== selectionRange.codeBasedSelections[i].range[0] ||
to !== selectionRange.codeBasedSelections[i].range[1]
)
})
if (!isChange) return
setSelectionRanges(ranges.map(({ from, to }) => [from, to]))
setSelectionRanges({
otherSelections: [],
codeBasedSelections: ranges.map(({ from, to }, i) => {
if (selectionRangeTypeMap[to]) {
return {
type: selectionRangeTypeMap[to],
range: [from, to],
}
}
return {
type: 'default',
range: [from, to],
}
}),
})
}
const [geoArray, setGeoArray] = useState<(ExtrudeGroup | SketchGroup)[]>([])
useEffect(() => {
@ -163,7 +182,7 @@ function App() {
<div className="h-screen">
<ModalContainer />
<Allotment snap={true}>
<Allotment vertical defaultSizes={[4, 1, 1]} minSize={20}>
<Allotment vertical defaultSizes={[400, 1, 1]} minSize={20}>
<div className="h-full flex flex-col items-start">
<PanelHeader title="Editor" />
{/* <button
@ -190,7 +209,7 @@ function App() {
<MemoryPanel />
<Logs />
</Allotment>
<Allotment vertical defaultSizes={[4, 1]} minSize={20}>
<Allotment vertical defaultSizes={[400, 1]} minSize={20}>
<div className="h-full">
<PanelHeader title="Drafting Board" />
<Toolbar />

View File

@ -48,7 +48,7 @@ export const Toolbar = () => {
if (!ast) return
const pathToNode = getNodePathFromSourceRange(
ast,
selectionRanges[0]
selectionRanges.codeBasedSelections[0].range
)
const { modifiedAst } = sketchOnExtrudedFace(
ast,
@ -85,7 +85,7 @@ export const Toolbar = () => {
if (!ast) return
const pathToNode = getNodePathFromSourceRange(
ast,
selectionRanges[0]
selectionRanges.codeBasedSelections[0].range
)
const { modifiedAst, pathToExtrudeArg } = extrudeSketch(
ast,
@ -102,7 +102,7 @@ export const Toolbar = () => {
if (!ast) return
const pathToNode = getNodePathFromSourceRange(
ast,
selectionRanges[0]
selectionRanges.codeBasedSelections[0].range
)
const { modifiedAst, pathToExtrudeArg } = extrudeSketch(
ast,

View File

@ -98,7 +98,7 @@ export function useCalc({
const { ast, programMemory, selectionRange } = useStore((s) => ({
ast: s.ast,
programMemory: s.programMemory,
selectionRange: s.selectionRanges[0],
selectionRange: s.selectionRanges.codeBasedSelections[0].range,
}))
const inputRef = useRef<HTMLInputElement>(null)
const [availableVarInfo, setAvailableVarInfo] = useState<

View File

@ -28,7 +28,7 @@ import { useSetCursor } from '../hooks/useSetCursor'
import { getConstraintLevelFromSourceRange } from '../lang/std/sketchcombos'
import { createCallExpression, createPipeSubstitution } from '../lang/modifyAst'
function MovingSphere({
function LineEnd({
geo,
sourceRange,
editorCursor,
@ -51,6 +51,8 @@ function MovingSphere({
const [isMouseDown, setIsMouseDown] = useState(false)
const baseColor = useConstraintColors(sourceRange)
const setCursor = useSetCursor(sourceRange, 'line-end')
const { setHighlightRange, guiMode, ast, updateAst, programMemory } =
useStore((s) => ({
setHighlightRange: s.setHighlightRange,
@ -84,7 +86,6 @@ function MovingSphere({
useEffect(() => {
const handleMouseUp = () => {
if (isMouseDown && ast) {
const thePath = getNodePathFromSourceRange(ast, sourceRange)
const current2d = point2DRef.current.clone()
const inverseQuaternion = new Quaternion()
if (
@ -107,7 +108,7 @@ function MovingSphere({
guiMode,
from
)
if (!(current2d.x === 0 && current2d.y === 0 && current2d.z === 0))
updateAst(modifiedAst)
ref.current.position.set(...position)
}
@ -142,7 +143,10 @@ function MovingSphere({
setHover(false)
setHighlightRange([0, 0])
}}
onPointerDown={() => inEditMode && setIsMouseDown(true)}
onPointerDown={() => {
inEditMode && setIsMouseDown(true)
setCursor()
}}
>
<primitive object={geo} scale={hovered ? 2 : 1} />
<meshStandardMaterial
@ -313,8 +317,8 @@ function WallRender({
const [editorCursor, setEditorCursor] = useState(false)
useEffect(() => {
const shouldHighlight = selectionRanges.some((range) =>
isOverlap(geoInfo.__geoMeta.sourceRange, range)
const shouldHighlight = selectionRanges.codeBasedSelections.some(
({ range }) => isOverlap(geoInfo.__geoMeta.sourceRange, range)
)
setEditorCursor(shouldHighlight)
}, [selectionRanges, geoInfo])
@ -369,11 +373,17 @@ function PathRender({
guiMode: s.guiMode,
}))
const [editorCursor, setEditorCursor] = useState(false)
const [editorLineCursor, setEditorLineCursor] = useState(false)
useEffect(() => {
const shouldHighlight = selectionRanges.some((range) =>
isOverlap(geoInfo.__geoMeta.sourceRange, range)
const shouldHighlight = selectionRanges.codeBasedSelections.some(
({ range }) => isOverlap(geoInfo.__geoMeta.sourceRange, range)
)
const shouldHighlightLine = selectionRanges.codeBasedSelections.some(
({ range, type }) =>
isOverlap(geoInfo.__geoMeta.sourceRange, range) && type === 'default'
)
setEditorCursor(shouldHighlight)
setEditorLineCursor(shouldHighlightLine)
}, [selectionRanges, geoInfo])
return (
<>
@ -384,14 +394,14 @@ function PathRender({
key={i}
geo={meta.geo}
sourceRange={geoInfo.__geoMeta.sourceRange}
forceHighlight={forceHighlight || editorCursor}
forceHighlight={editorLineCursor}
rotation={rotation}
position={position}
/>
)
if (meta.type === 'lineEnd')
return (
<MovingSphere
<LineEnd
key={i}
geo={meta.geo}
from={geoInfo.from}
@ -407,7 +417,7 @@ function PathRender({
key={i}
geo={meta.geo}
sourceRange={geoInfo.__geoMeta.sourceRange}
forceHighlight={forceHighlight || editorCursor}
forceHighlight={forceHighlight || editorLineCursor}
rotation={rotation}
position={position}
onClick={() => {
@ -533,7 +543,12 @@ function useSetAppModeFromCursorLocation(artifacts: Artifact[]) {
)[] = []
artifacts?.forEach((artifact) => {
artifact.value.forEach((geo) => {
if (isOverlap(geo.__geoMeta.sourceRange, selectionRanges[0])) {
if (
isOverlap(
geo.__geoMeta.sourceRange,
selectionRanges.codeBasedSelections[0].range
)
) {
artifactsWithinCursorRange.push({
parentType: artifact.type,
isParent: false,
@ -545,7 +560,12 @@ function useSetAppModeFromCursorLocation(artifacts: Artifact[]) {
}
})
artifact.__meta.forEach((meta) => {
if (isOverlap(meta.sourceRange, selectionRanges[0])) {
if (
isOverlap(
meta.sourceRange,
selectionRanges.codeBasedSelections[0].range
)
) {
artifactsWithinCursorRange.push({
parentType: artifact.type,
isParent: true,

View File

@ -21,9 +21,12 @@ export const ConvertToVariable = () => {
useEffect(() => {
if (!ast) return
const { isSafe, value } = isNodeSafeToReplace(ast, selectionRanges[0])
const { isSafe, value } = isNodeSafeToReplace(
ast,
selectionRanges.codeBasedSelections[0].range
)
const canReplace = isSafe && value.type !== 'Identifier'
const isOnlyOneSelection = selectionRanges.length === 1
const isOnlyOneSelection = selectionRanges.codeBasedSelections.length === 1
const _enableHorz = canReplace && isOnlyOneSelection
setEnableAngLen(_enableHorz)
@ -41,7 +44,7 @@ export const ConvertToVariable = () => {
const { modifiedAst: _modifiedAst } = moveValueIntoNewVariable(
ast,
programMemory,
selectionRanges[0],
selectionRanges.codeBasedSelections[0].range,
variableName
)

View File

@ -26,8 +26,8 @@ export const EqualAngle = () => {
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
useEffect(() => {
if (!ast) return
const paths = selectionRanges.map((selectionRange) =>
getNodePathFromSourceRange(ast, selectionRange)
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(ast, range)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node
@ -52,7 +52,10 @@ export const EqualAngle = () => {
)
const theTransforms = getTransformInfos(
selectionRanges.slice(1),
{
...selectionRanges,
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
},
ast,
'equalAngle'
)

View File

@ -26,8 +26,8 @@ export const EqualLength = () => {
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
useEffect(() => {
if (!ast) return
const paths = selectionRanges.map((selectionRange) =>
getNodePathFromSourceRange(ast, selectionRange)
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(ast, range)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node
@ -52,7 +52,10 @@ export const EqualLength = () => {
)
const theTransforms = getTransformInfos(
selectionRanges.slice(1),
{
...selectionRanges,
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
},
ast,
'equalLength'
)

View File

@ -29,8 +29,8 @@ export const HorzVert = ({
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
useEffect(() => {
if (!ast) return
const paths = selectionRanges.map((selectionRange) =>
getNodePathFromSourceRange(ast, selectionRange)
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(ast, range)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node

View File

@ -39,8 +39,8 @@ export const Intersect = () => {
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
useEffect(() => {
if (!ast) return
const paths = selectionRanges.map((selectionRange) =>
getNodePathFromSourceRange(ast, selectionRange)
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(ast, range)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node
@ -68,7 +68,10 @@ export const Intersect = () => {
)
const theTransforms = getTransformInfos(
selectionRanges.slice(1),
{
...selectionRanges,
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
},
ast,
'intersect'
)
@ -150,7 +153,7 @@ export const Intersect = () => {
}`}
disabled={!enable}
>
Intersect
perpendicularDistance
</button>
)
}

View File

@ -25,8 +25,8 @@ export const RemoveConstrainingValues = () => {
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
useEffect(() => {
if (!ast) return
const paths = selectionRanges.map((selectionRange) =>
getNodePathFromSourceRange(ast, selectionRange)
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(ast, range)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node
@ -37,6 +37,7 @@ export const RemoveConstrainingValues = () => {
toolTips.includes(node.callee.name as any)
)
try {
const theTransforms = getRemoveConstraintsTransforms(
selectionRanges,
ast,
@ -46,6 +47,9 @@ export const RemoveConstrainingValues = () => {
const _enableHorz = isAllTooltips && theTransforms.every(Boolean)
setEnableHorz(_enableHorz)
} catch (e) {
console.error(e)
}
}, [guiMode, selectionRanges])
if (guiMode.mode !== 'sketch') return null

View File

@ -35,8 +35,8 @@ export const SetAngleLength = ({
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
useEffect(() => {
if (!ast) return
const paths = selectionRanges.map((selectionRange) =>
getNodePathFromSourceRange(ast, selectionRange)
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(ast, range)
)
const nodes = paths.map(
(pathToNode) =>
@ -60,7 +60,7 @@ export const SetAngleLength = ({
<button
onClick={async () => {
if (!(transformInfos && ast)) return
const { modifiedAst, valueUsedInTransform } = transformAstSketchLines({
const { valueUsedInTransform } = transformAstSketchLines({
ast: JSON.parse(JSON.stringify(ast)),
selectionRanges,
transformInfos,

View File

@ -43,8 +43,8 @@ export const SetHorzDistance = ({
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
useEffect(() => {
if (!ast) return
const paths = selectionRanges.map((selectionRange) =>
getNodePathFromSourceRange(ast, selectionRange)
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(ast, range)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node
@ -72,7 +72,10 @@ export const SetHorzDistance = ({
)
const theTransforms = getTransformInfos(
selectionRanges.slice(1),
{
...selectionRanges,
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
},
ast,
horOrVert
)
@ -127,7 +130,6 @@ export const SetHorzDistance = ({
sign,
variableName
)
console.log(finalValue)
// transform again but forcing certain values
const { modifiedAst: _modifiedAst } =
transformSecondarySketchLinesTagFirst({

View File

@ -1,15 +1,21 @@
import { useStore, Range } from '../useStore'
import { useStore, Selection, Selections } from '../useStore'
export function useSetCursor(sourceRange: Range) {
export function useSetCursor(
sourceRange: Selection['range'],
type: Selection['type'] = 'default'
) {
const { setCursor, selectionRanges, isShiftDown } = useStore((s) => ({
setCursor: s.setCursor,
selectionRanges: s.selectionRanges,
isShiftDown: s.isShiftDown,
}))
return () => {
const ranges = isShiftDown
? [...selectionRanges, sourceRange]
: [sourceRange]
setCursor(ranges)
const selections: Selections = {
...selectionRanges,
codeBasedSelections: isShiftDown
? [...selectionRanges.codeBasedSelections, { range: sourceRange, type }]
: [{ range: sourceRange, type }],
}
setCursor(selections)
}
}

View File

@ -67,14 +67,17 @@ export function lineGeo({
sign,
} = trigCalcs({ from, to })
const lineEndLength = 0.25
// create BoxGeometry with size [Hypotenuse3d, 0.1, 0.1] centered at center, with rotation of [0, ry, rz]
const lineBody = new BoxGeometry(Hypotenuse3d, 0.1, 0.1)
const lineBody = new BoxGeometry(Hypotenuse3d - lineEndLength, 0.1, 0.1)
const __sign = to[0] === from[0] ? -1 : 1
lineBody.translate((__sign * lineEndLength) / 2, 0, 0)
lineBody.rotateY(ry)
lineBody.rotateZ(rz)
lineBody.translate(centre[0], centre[1], centre[2])
// create line end points with CylinderGeometry at `to`
const lineEnd1 = new CylinderGeometry(0.05, 0.22, 0.25, 4)
const lineEnd1 = new CylinderGeometry(0.05, 0.22, lineEndLength + 0.05, 4)
lineEnd1.translate(0, -0.1, 0)
lineEnd1.rotateY(Math.PI / 4)
lineEnd1.rotateZ(rz)

View File

@ -15,7 +15,7 @@ import { internalFns } from './std/std'
import { BufferGeometry } from 'three'
export type SourceRange = [number, number]
export type PathToNode = [string | number, string][]
export type PathToNode = [string | number, string][] // [pathKey, nodeType][]
export type Metadata = {
sourceRange: SourceRange
pathToNode: PathToNode

View File

@ -1,4 +1,4 @@
import { Range, TooTip } from '../useStore'
import { Selection, TooTip } from '../useStore'
import {
Program,
CallExpression,
@ -534,7 +534,7 @@ export function createBinaryExpressionWithUnary([left, right]: [
export function giveSketchFnCallTag(
ast: Program,
range: Range,
range: Selection['range'],
tag?: string
): { modifiedAst: Program; tag: string; isTagExisting: boolean } {
const { node: primaryCallExp } = getNodeFromPath<CallExpression>(
@ -563,7 +563,7 @@ export function giveSketchFnCallTag(
export function moveValueIntoNewVariable(
ast: Program,
programMemory: ProgramMemory,
sourceRange: Range,
sourceRange: Selection['range'],
variableName: string
): {
modifiedAst: Program

View File

@ -1,5 +1,5 @@
import { PathToNode, ProgramMemory } from './executor'
import { Range } from '../useStore'
import { Selection } from '../useStore'
import {
BinaryExpression,
Program,
@ -96,7 +96,7 @@ export function getNodeFromPathCurry(
function moreNodePathFromSourceRange(
node: Value | ExpressionStatement | VariableDeclaration | ReturnStatement,
sourceRange: Range,
sourceRange: Selection['range'],
previousPath: PathToNode = [['body', '']]
): PathToNode {
const [start, end] = sourceRange
@ -239,7 +239,7 @@ function moreNodePathFromSourceRange(
export function getNodePathFromSourceRange(
node: Program,
sourceRange: Range,
sourceRange: Selection['range'],
previousPath: PathToNode = [['body', '']]
): PathToNode {
const [start, end] = sourceRange
@ -269,7 +269,7 @@ export interface PrevVariable<T> {
export function findAllPreviousVariables(
ast: Program,
programMemory: ProgramMemory,
sourceRange: Range,
sourceRange: Selection['range'],
type: 'number' | 'string' = 'number'
): {
variables: PrevVariable<typeof type extends 'number' ? number : string>[]

View File

@ -64,7 +64,7 @@ export function getCoordsFromPaths(skGroup: SketchGroup, index = 0): Coords2d {
export function createFirstArg(
sketchFn: TooTip,
val: Value | [Value, Value],
val: Value | [Value, Value] | [Value, Value, Value],
tag?: Value
): Value {
if (!tag) {
@ -84,6 +84,13 @@ export function createFirstArg(
return createObjectExpression({ angle: val[0], length: val[1], tag })
if (['angledLineToX', 'angledLineToY'].includes(sketchFn))
return createObjectExpression({ angle: val[0], to: val[1], tag })
if (['angledLineThatIntersects'].includes(sketchFn) && val[2])
return createObjectExpression({
angle: val[0],
offset: val[1],
intersectTag: val[2],
tag,
})
} else {
if (['xLine', 'yLine'].includes(sketchFn))
return createObjectExpression({ length: val, tag })
@ -1678,7 +1685,7 @@ function getFirstArgValuesForXYLineFns(callExpression: CallExpression): {
const getAngledLineThatIntersects = (
callExp: CallExpression
): {
val: [Value, Value]
val: [Value, Value, Value]
tag?: Value
} => {
const firstArg = callExp.arguments[0]
@ -1688,15 +1695,18 @@ const getAngledLineThatIntersects = (
const offset = firstArg.properties.find(
(p) => p.key.name === 'offset'
)?.value
if (angle && offset) {
return { val: [angle, offset], tag }
const intersectTag = firstArg.properties.find(
(p) => p.key.name === 'intersectTag'
)?.value
if (angle && offset && intersectTag) {
return { val: [angle, offset, intersectTag], tag }
}
}
throw new Error('expected ArrayExpression or ObjectExpression')
}
export function getFirstArg(callExp: CallExpression): {
val: Value | [Value, Value]
val: Value | [Value, Value] | [Value, Value, Value]
tag?: Value
} {
const name = callExp?.callee?.name

View File

@ -9,6 +9,7 @@ import {
import { recast } from '../recast'
import { initPromise } from '../rust'
import { getSketchSegmentFromSourceRange } from './sketchConstraints'
import { Selection } from '../../useStore'
beforeAll(() => initPromise)
@ -26,23 +27,30 @@ function testingSwapSketchFnCall({
originalRange: [number, number]
} {
const startIndex = inputCode.indexOf(callToSwap)
const range: [number, number] = [startIndex, startIndex + callToSwap.length]
const range: Selection = {
type: 'default',
range: [startIndex, startIndex + callToSwap.length],
}
const tokens = lexer(inputCode)
const ast = abstractSyntaxTree(tokens)
const programMemory = executor(ast)
const transformInfos = getTransformInfos([range], ast, constraintType)
const selections = {
codeBasedSelections: [range],
otherSelections: [],
}
const transformInfos = getTransformInfos(selections, ast, constraintType)
if (!transformInfos) throw new Error('nope')
const { modifiedAst } = transformAstSketchLines({
ast,
programMemory,
selectionRanges: [range],
selectionRanges: selections,
transformInfos,
referenceSegName: '',
})
return {
newCode: recast(modifiedAst),
originalRange: range,
originalRange: range.range,
}
}

View File

@ -1,16 +1,16 @@
import { getAngle } from '../../lib/utils'
import { Range, TooTip, toolTips } from '../../useStore'
import { Selection, TooTip, toolTips } from '../../useStore'
import {
Program,
VariableDeclarator,
CallExpression,
} from '../abstractSyntaxTree'
import { SketchGroup } from '../executor'
import { SketchGroup, SourceRange } from '../executor'
import { InternalFn } from './stdTypes'
export function getSketchSegmentFromSourceRange(
sketchGroup: SketchGroup,
[rangeStart, rangeEnd]: Range
[rangeStart, rangeEnd]: SourceRange
): SketchGroup['value'][number] {
const startSourceRange = sketchGroup.start?.__geoMeta.sourceRange
if (

View File

@ -9,7 +9,7 @@ import {
getConstraintLevelFromSourceRange,
} from './sketchcombos'
import { initPromise } from '../rust'
import { TooTip } from '../../useStore'
import { Selections, TooTip } from '../../useStore'
import { executor } from '../../lang/executor'
import { recast } from '../../lang/recast'
@ -80,6 +80,15 @@ function getConstraintTypeFromSourceHelper2(
return getConstraintType(arg, fnName)
}
function makeSelections(
codeBaseSelections: Selections['codeBasedSelections']
): Selections {
return {
codeBasedSelections: codeBaseSelections,
otherSelections: [],
}
}
describe('testing transformAstForSketchLines for equal length constraint', () => {
const inputScript = `const myVar = 3
const myVar2 = 5
@ -189,25 +198,28 @@ const part001 = startSketchAt([0, 0])
show(part001)`
it('It should transform the ast', () => {
const ast = abstractSyntaxTree(lexer(inputScript))
const selectionRanges = inputScript
const selectionRanges: Selections['codeBasedSelections'] = inputScript
.split('\n')
.filter((ln) => ln.includes('//'))
.map((ln) => {
const comment = ln.split('//')[1]
const start = inputScript.indexOf('//' + comment) - 7
return [start, start]
}) as [number, number][]
return {
type: 'default',
range: [start, start],
}
})
const programMemory = executor(ast)
const transformInfos = getTransformInfos(
selectionRanges.slice(1),
makeSelections(selectionRanges.slice(1)),
ast,
'equalLength'
)
const newAst = transformSecondarySketchLinesTagFirst({
ast,
selectionRanges,
selectionRanges: makeSelections(selectionRanges),
transformInfos,
programMemory,
})?.modifiedAst
@ -271,21 +283,28 @@ const part001 = startSketchAt([0, 0])
|> angledLineToY([301, myVar], %) // select for vertical constraint 10
show(part001)`
const ast = abstractSyntaxTree(lexer(inputScript))
const selectionRanges = inputScript
const selectionRanges: Selections['codeBasedSelections'] = inputScript
.split('\n')
.filter((ln) => ln.includes('// select for horizontal constraint'))
.map((ln) => {
const comment = ln.split('//')[1]
const start = inputScript.indexOf('//' + comment) - 7
return [start, start]
}) as [number, number][]
return {
type: 'default',
range: [start, start],
}
})
const programMemory = executor(ast)
const transformInfos = getTransformInfos(selectionRanges, ast, 'horizontal')
const transformInfos = getTransformInfos(
makeSelections(selectionRanges),
ast,
'horizontal'
)
const newAst = transformAstSketchLines({
ast,
selectionRanges,
selectionRanges: makeSelections(selectionRanges),
transformInfos,
programMemory,
referenceSegName: '',
@ -321,21 +340,28 @@ const part001 = startSketchAt([0, 0])
|> yLineTo(myVar, %) // select for vertical constraint 10
show(part001)`
const ast = abstractSyntaxTree(lexer(inputScript))
const selectionRanges = inputScript
const selectionRanges: Selections['codeBasedSelections'] = inputScript
.split('\n')
.filter((ln) => ln.includes('// select for vertical constraint'))
.map((ln) => {
const comment = ln.split('//')[1]
const start = inputScript.indexOf('//' + comment) - 7
return [start, start]
}) as [number, number][]
return {
type: 'default',
range: [start, start],
}
})
const programMemory = executor(ast)
const transformInfos = getTransformInfos(selectionRanges, ast, 'vertical')
const transformInfos = getTransformInfos(
makeSelections(selectionRanges),
ast,
'vertical'
)
const newAst = transformAstSketchLines({
ast,
selectionRanges,
selectionRanges: makeSelections(selectionRanges),
transformInfos,
programMemory,
referenceSegName: '',
@ -404,7 +430,7 @@ function helperThing(
constraint: ConstraintType
): string {
const ast = abstractSyntaxTree(lexer(inputScript))
const selectionRanges = inputScript
const selectionRanges: Selections['codeBasedSelections'] = inputScript
.split('\n')
.filter((ln) =>
linesOfInterest.some((lineOfInterest) => ln.includes(lineOfInterest))
@ -412,19 +438,22 @@ function helperThing(
.map((ln) => {
const comment = ln.split('//')[1]
const start = inputScript.indexOf('//' + comment) - 7
return [start, start]
}) as [number, number][]
return {
type: 'default',
range: [start, start],
}
})
const programMemory = executor(ast)
const transformInfos = getTransformInfos(
selectionRanges.slice(1),
makeSelections(selectionRanges.slice(1)),
ast,
constraint
)
const newAst = transformSecondarySketchLinesTagFirst({
ast,
selectionRanges,
selectionRanges: makeSelections(selectionRanges),
transformInfos,
programMemory,
})?.modifiedAst

View File

@ -1,5 +1,5 @@
import { TransformCallback } from './stdTypes'
import { Ranges, toolTips, TooTip, Range } from '../../useStore'
import { Selections, toolTips, TooTip, Selection } from '../../useStore'
import {
BinaryPart,
CallExpression,
@ -127,7 +127,6 @@ const xyLineSetLength =
: referenceSeg
? segRef
: args[0]
// console.log({ lineVal, segRef, forceValueUsedInTransform, args })
return createCallWrapper(xOrY, lineVal, tag, getArgLiteralVal(args[0]))
}
@ -279,7 +278,6 @@ const setHorzVertDistanceForAngleLineCreateNode =
(forceValueUsedInTransform as BinaryPart) ||
createLiteral(valueUsedInTransform),
])
console.log('here or no?', binExp)
return createCallWrapper(
xOrY === 'x' ? 'angledLineToX' : 'angledLineToY',
[varValA, binExp],
@ -1099,7 +1097,7 @@ export function getTransformInfo(
}
export function getConstraintType(
val: Value | [Value, Value],
val: Value | [Value, Value] | [Value, Value, Value],
fnName: TooTip
): LineInputsType | null {
// this function assumes that for two val sketch functions that one arg is locked down not both
@ -1133,12 +1131,12 @@ export function getConstraintType(
}
export function getTransformInfos(
selectionRanges: Ranges,
selectionRanges: Selections,
ast: Program,
constraintType: ConstraintType
): TransformInfo[] {
const paths = selectionRanges.map((selectionRange) =>
getNodePathFromSourceRange(ast, selectionRange)
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(ast, range)
)
const nodes = paths.map(
(pathToNode) =>
@ -1160,13 +1158,13 @@ export function getTransformInfos(
}
export function getRemoveConstraintsTransforms(
selectionRanges: Ranges,
selectionRanges: Selections,
ast: Program,
constraintType: ConstraintType
): TransformInfo[] {
// return ()
const paths = selectionRanges.map((selectionRange) =>
getNodePathFromSourceRange(ast, selectionRange)
const paths = selectionRanges.codeBasedSelections.map((selectionRange) =>
getNodePathFromSourceRange(ast, selectionRange.range)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node
@ -1190,7 +1188,7 @@ export function transformSecondarySketchLinesTagFirst({
forceValueUsedInTransform,
}: {
ast: Program
selectionRanges: Ranges
selectionRanges: Selections
transformInfos: TransformInfo[]
programMemory: ProgramMemory
forceSegName?: string
@ -1204,7 +1202,7 @@ export function transformSecondarySketchLinesTagFirst({
}
} {
// let node = JSON.parse(JSON.stringify(ast))
const primarySelection = selectionRanges[0]
const primarySelection = selectionRanges.codeBasedSelections[0].range
const { modifiedAst, tag, isTagExisting } = giveSketchFnCallTag(
ast,
@ -1215,7 +1213,10 @@ export function transformSecondarySketchLinesTagFirst({
return {
...transformAstSketchLines({
ast: modifiedAst,
selectionRanges: selectionRanges.slice(1),
selectionRanges: {
...selectionRanges,
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
},
referencedSegmentRange: primarySelection,
transformInfos,
programMemory,
@ -1239,18 +1240,18 @@ export function transformAstSketchLines({
referencedSegmentRange,
}: {
ast: Program
selectionRanges: Ranges
selectionRanges: Selections
transformInfos: TransformInfo[]
programMemory: ProgramMemory
referenceSegName: string
forceValueUsedInTransform?: Value
referencedSegmentRange?: Range
referencedSegmentRange?: Selection['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))
let _valueUsedInTransform // TODO should this be an array?
selectionRanges.forEach((range, index) => {
selectionRanges.codeBasedSelections.forEach(({ range }, index) => {
const callBack = transformInfos?.[index].createNode
const transformTo = transformInfos?.[index].tooltip
if (!callBack || !transformTo) throw new Error('no callback helper')
@ -1343,7 +1344,7 @@ function getArgLiteralVal(arg: Value): number {
}
export function getConstraintLevelFromSourceRange(
cursorRange: Range,
cursorRange: Selection['range'],
ast: Program
): 'free' | 'partial' | 'full' {
const { node: sketchFnExp } = getNodeFromPath<CallExpression>(

View File

@ -1,5 +1,5 @@
import { isOverlap, roundOff } from './utils'
import { Range } from '../useStore'
import { SourceRange } from '../lang/executor'
describe('testing isOverlapping', () => {
testBothOrders([0, 3], [3, 10])
@ -11,7 +11,7 @@ describe('testing isOverlapping', () => {
testBothOrders([0, 5], [-2, -1], false)
})
function testBothOrders(a: Range, b: Range, result = true) {
function testBothOrders(a: SourceRange, b: SourceRange, result = true) {
it(`test is overlapping ${a} ${b}`, () => {
expect(isOverlap(a, b)).toBe(result)
expect(isOverlap(b, a)).toBe(result)

View File

@ -1,6 +1,6 @@
import { Range } from '../useStore'
import { SourceRange } from '../lang/executor'
export function isOverlap(a: Range, b: Range) {
export function isOverlap(a: SourceRange, b: SourceRange) {
const [startingRange, secondRange] = a[0] < b[0] ? [a, b] : [b, a]
const [lastOfFirst, firstOfSecond] = [startingRange[1], secondRange[0]]
return lastOfFirst >= firstOfSecond

View File

@ -3,13 +3,25 @@ import { persist } from 'zustand/middleware'
import { addLineHighlight, EditorView } from './editor/highlightextension'
import { Program, abstractSyntaxTree } from './lang/abstractSyntaxTree'
import { getNodeFromPath } from './lang/queryAst'
import { ProgramMemory, Position, PathToNode, Rotation } from './lang/executor'
import {
ProgramMemory,
Position,
PathToNode,
Rotation,
SourceRange,
} from './lang/executor'
import { recast } from './lang/recast'
import { asyncLexer } from './lang/tokeniser'
import { EditorSelection } from '@codemirror/state'
export type Range = [number, number]
export type Ranges = Range[]
export type Selection = {
type: 'default' | 'line-end' | 'line-mid'
range: SourceRange
}
export type Selections = {
otherSelections: ('y-axis' | 'x-axis' | 'z-axis')[]
codeBasedSelections: Selection[]
}
export type TooTip =
| 'lineTo'
| 'line'
@ -80,10 +92,11 @@ interface StoreState {
editorView: EditorView | null
setEditorView: (editorView: EditorView) => void
highlightRange: [number, number]
setHighlightRange: (range: Range) => void
setCursor: (selections: Ranges) => void
selectionRanges: Ranges
setSelectionRanges: (range: Ranges) => void
setHighlightRange: (range: Selection['range']) => void
setCursor: (selections: Selections) => void
selectionRanges: Selections
selectionRangeTypeMap: { [key: number]: Selection['type'] }
setSelectionRanges: (range: Selections) => void
guiMode: GuiModes
lastGuiMode: GuiModes
setGuiMode: (guiMode: GuiModes) => void
@ -118,27 +131,39 @@ export const useStore = create<StoreState>()(
set({ editorView })
},
highlightRange: [0, 0],
setHighlightRange: (highlightRange) => {
set({ highlightRange })
setHighlightRange: (selection) => {
set({ highlightRange: selection })
const editorView = get().editorView
if (editorView) {
editorView.dispatch({ effects: addLineHighlight.of(highlightRange) })
editorView.dispatch({ effects: addLineHighlight.of(selection) })
}
},
setCursor: (ranges: Ranges) => {
setCursor: (selections) => {
const { editorView } = get()
if (!editorView) return
const ranges: ReturnType<typeof EditorSelection.cursor>[] = []
const selectionRangeTypeMap: { [key: number]: Selection['type'] } = {}
set({ selectionRangeTypeMap })
selections.codeBasedSelections.forEach(({ range, type }) => {
ranges.push(EditorSelection.cursor(range[1]))
selectionRangeTypeMap[range[1]] = type
})
setTimeout(() => {
editorView.dispatch({
selection: EditorSelection.create(
[...ranges.map(([start, end]) => EditorSelection.cursor(end))],
ranges.length - 1
ranges,
selections.codeBasedSelections.length - 1
),
})
})
},
selectionRanges: [[0, 0]],
setSelectionRanges: (selectionRanges) => {
set({ selectionRanges })
selectionRangeTypeMap: {},
selectionRanges: {
otherSelections: [],
codeBasedSelections: [],
},
setSelectionRanges: (selectionRanges) =>
set({ selectionRanges, selectionRangeTypeMap: {} }),
guiMode: { mode: 'default' },
lastGuiMode: { mode: 'default' },
setGuiMode: (guiMode) => {
@ -162,7 +187,6 @@ export const useStore = create<StoreState>()(
},
updateAst: async (ast, focusPath) => {
const newCode = recast(ast)
console.log('running update Ast', ast)
const astWithUpdatedSource = abstractSyntaxTree(
await asyncLexer(newCode)
)
@ -173,7 +197,15 @@ export const useStore = create<StoreState>()(
const { start, end } = node
if (!start || !end) return
setTimeout(() => {
get().setCursor([[start, end]])
get().setCursor({
codeBasedSelections: [
{
type: 'default',
range: [start, end],
},
],
otherSelections: [],
})
})
}
},