Horz/Vert distance constraint with modal workflow (#43)

* button style tweak

* Remove duplication constraint ast transforms

* giveSketchFnCallTag to return if line already had a tag

* remove duplication

* update tag name to referenceSegName

* Update transform shape to return key details about the transform

* add modal to hor vert distance workflow

* fix browser env stuff from breaking tests

* fmt
This commit is contained in:
Kurt Hutten
2023-03-07 15:45:59 +11:00
committed by GitHub
parent a0518c556f
commit 2ac24bcd95
16 changed files with 376 additions and 203 deletions

View File

@ -4,6 +4,7 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"@codemirror/lang-javascript": "^6.1.1", "@codemirror/lang-javascript": "^6.1.1",
"@headlessui/react": "^1.7.13",
"@react-three/drei": "^9.42.0", "@react-three/drei": "^9.42.0",
"@react-three/fiber": "^8.9.1", "@react-three/fiber": "^8.9.1",
"@testing-library/jest-dom": "^5.14.1", "@testing-library/jest-dom": "^5.14.1",
@ -19,6 +20,7 @@
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-json-view": "^1.21.3", "react-json-view": "^1.21.3",
"react-modal-promise": "^1.0.2",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
"swr": "^2.0.4", "swr": "^2.0.4",
"three": "^0.146.0", "three": "^0.146.0",

View File

@ -23,6 +23,7 @@ import { PanelHeader } from './components/PanelHeader'
import { MemoryPanel } from './components/MemoryPanel' import { MemoryPanel } from './components/MemoryPanel'
import { useHotKeyListener } from './hooks/useHotKeyListener' import { useHotKeyListener } from './hooks/useHotKeyListener'
import { Stream } from './components/Stream' import { Stream } from './components/Stream'
import ModalContainer from 'react-modal-promise'
const OrrthographicCamera = OrthographicCamera as any const OrrthographicCamera = OrthographicCamera as any
@ -140,6 +141,7 @@ function App() {
}, [code]) }, [code])
return ( return (
<div className="h-screen"> <div className="h-screen">
<ModalContainer />
<Allotment snap={true}> <Allotment snap={true}>
<Allotment vertical defaultSizes={[4, 1, 1]} minSize={20}> <Allotment vertical defaultSizes={[4, 1, 1]} minSize={20}>
<div className="h-full flex flex-col items-start"> <div className="h-full flex flex-col items-start">

View File

@ -32,7 +32,7 @@ export const Toolbar = () => {
sketchMode: 'selectFace', sketchMode: 'selectFace',
}) })
}} }}
className="border m-1 px-1 rounded" className="border m-1 px-1 rounded text-xs"
> >
Start Sketch Start Sketch
</button> </button>
@ -52,7 +52,7 @@ export const Toolbar = () => {
) )
updateAst(modifiedAst) updateAst(modifiedAst)
}} }}
className="border m-1 px-1 rounded" className="border m-1 px-1 rounded text-xs"
> >
SketchOnFace SketchOnFace
</button> </button>
@ -68,7 +68,7 @@ export const Toolbar = () => {
position: guiMode.position, position: guiMode.position,
}) })
}} }}
className="border m-1 px-1 rounded" className="border m-1 px-1 rounded text-xs"
> >
Edit Sketch Edit Sketch
</button> </button>
@ -88,7 +88,7 @@ export const Toolbar = () => {
) )
updateAst(modifiedAst, pathToExtrudeArg) updateAst(modifiedAst, pathToExtrudeArg)
}} }}
className="border m-1 px-1 rounded" className="border m-1 px-1 rounded text-xs"
> >
ExtrudeSketch ExtrudeSketch
</button> </button>
@ -106,7 +106,7 @@ export const Toolbar = () => {
) )
updateAst(modifiedAst, pathToExtrudeArg) updateAst(modifiedAst, pathToExtrudeArg)
}} }}
className="border m-1 px-1 rounded" className="border m-1 px-1 rounded text-xs"
> >
ExtrudeSketch (w/o pipe) ExtrudeSketch (w/o pipe)
</button> </button>
@ -116,7 +116,7 @@ export const Toolbar = () => {
{guiMode.mode === 'sketch' && ( {guiMode.mode === 'sketch' && (
<button <button
onClick={() => setGuiMode({ mode: 'default' })} onClick={() => setGuiMode({ mode: 'default' })}
className="border m-1 px-1 rounded" className="border m-1 px-1 rounded text-xs"
> >
Exit sketch Exit sketch
</button> </button>
@ -130,7 +130,7 @@ export const Toolbar = () => {
return ( return (
<button <button
key={sketchFnName} key={sketchFnName}
className={`border m-1 px-1 rounded ${ className={`border m-0.5 px-0.5 rounded text-xs ${
guiMode.sketchMode === sketchFnName && 'bg-gray-400' guiMode.sketchMode === sketchFnName && 'bg-gray-400'
}`} }`}
onClick={() => onClick={() =>

View File

@ -0,0 +1,110 @@
import { Dialog, Transition } from '@headlessui/react'
import { Fragment, useState } from 'react'
export const GetInfoModal = ({
isOpen,
onResolve,
onReject,
// fields: initialFields,
segName: initialSegName,
isSegNameEditable,
value: initialValue,
}: {
isOpen: boolean
onResolve: (a: any) => void
onReject: (a: any) => void
segName: string
isSegNameEditable: boolean
value: number
}) => {
const [segName, setSegName] = useState(initialSegName)
const [value, setValue] = useState(initialValue)
return (
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={onResolve}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black bg-opacity-25" />
</Transition.Child>
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-gray-900"
>
Constraint details
</Dialog.Title>
<label
htmlFor="val"
className="block text-sm font-medium text-gray-700 mt-3 font-mono"
>
Distance
</label>
<div className="mt-1">
<input
type="number"
name="val"
id="val"
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md font-mono"
value={value}
onChange={(e) => {
setValue(Number(e.target.value))
}}
/>
</div>
<label
htmlFor="segName"
className="block text-sm font-medium text-gray-700 mt-3 font-mono"
>
Segment Name
</label>
<div className="mt-1">
<input
type="text"
name="segName"
id="segName"
disabled={!isSegNameEditable}
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md font-mono"
value={segName}
onChange={(e) => {
setSegName(e.target.value)
}}
/>
</div>
<div className="mt-4">
<button
type="button"
className="inline-flex justify-center rounded-md border border-transparent bg-blue-100 px-4 py-2 text-sm font-medium text-blue-900 hover:bg-blue-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={() => onResolve({ segName, value })}
>
Add constraining value
</button>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
)
}

View File

@ -5,6 +5,11 @@ export const Stream = () => {
const videoRef = useRef<HTMLVideoElement>(null) const videoRef = useRef<HTMLVideoElement>(null)
useEffect(() => { useEffect(() => {
if (
typeof window === 'undefined' ||
typeof RTCPeerConnection === 'undefined'
)
return
const url = 'wss://dev.api.kittycad.io/ws/channel' const url = 'wss://dev.api.kittycad.io/ws/channel'
const [pc, socket] = [new RTCPeerConnection(), new WebSocket(url)] const [pc, socket] = [new RTCPeerConnection(), new WebSocket(url)]
// Connection opened // Connection opened
@ -41,15 +46,15 @@ export const Stream = () => {
iceServers: message.ice_servers, iceServers: message.ice_servers,
}) })
pc.ontrack = function (event) { pc.ontrack = function (event) {
const el = document.createElement( console.log('has element?', videoRef.current)
event.track.kind setTimeout(() => {
) as HTMLVideoElement console.log('has element in timeout?', videoRef.current)
if (videoRef.current) {
if (videoRef.current) { videoRef.current.srcObject = event.streams[0]
videoRef.current.srcObject = event.streams[0] videoRef.current.autoplay = true
videoRef.current.autoplay = true videoRef.current.controls = true
videoRef.current.controls = true }
} })
} }
pc.oniceconnectionstatechange = (e) => pc.oniceconnectionstatechange = (e) =>
console.log(pc.iceConnectionState) console.log(pc.iceConnectionState)

View File

@ -8,7 +8,7 @@ import {
import { isSketchVariablesLinked } from '../../lang/std/sketchConstraints' import { isSketchVariablesLinked } from '../../lang/std/sketchConstraints'
import { import {
TransformInfo, TransformInfo,
transformAstForSketchLines, transformSecondarySketchLinesTagFirst,
getTransformInfos, getTransformInfos,
} from '../../lang/std/sketchcombos' } from '../../lang/std/sketchcombos'
@ -73,7 +73,7 @@ export const Equal = () => {
transformInfos && transformInfos &&
ast && ast &&
updateAst( updateAst(
transformAstForSketchLines({ transformSecondarySketchLinesTagFirst({
ast, ast,
selectionRanges, selectionRanges,
transformInfos, transformInfos,
@ -81,7 +81,7 @@ export const Equal = () => {
})?.modifiedAst })?.modifiedAst
) )
} }
className={`border m-1 px-1 rounded ${ className={`border m-1 px-1 rounded text-xs ${
enableEqual ? 'bg-gray-50 text-gray-800' : 'bg-gray-200 text-gray-400' enableEqual ? 'bg-gray-50 text-gray-800' : 'bg-gray-200 text-gray-400'
}`} }`}
disabled={!enableEqual} disabled={!enableEqual}

View File

@ -8,7 +8,7 @@ import {
import { import {
TransformInfo, TransformInfo,
getTransformInfos, getTransformInfos,
transformAstForHorzVert, transformAstSketchLines,
} from '../../lang/std/sketchcombos' } from '../../lang/std/sketchcombos'
export const HorzVert = ({ export const HorzVert = ({
@ -55,15 +55,16 @@ export const HorzVert = ({
transformInfos && transformInfos &&
ast && ast &&
updateAst( updateAst(
transformAstForHorzVert({ transformAstSketchLines({
ast, ast,
selectionRanges, selectionRanges,
transformInfos, transformInfos,
programMemory, programMemory,
referenceSegName: '',
})?.modifiedAst })?.modifiedAst
) )
} }
className={`border m-1 px-1 rounded ${ className={`border m-1 px-1 rounded text-xs ${
enableHorz ? 'bg-gray-50 text-gray-800' : 'bg-gray-200 text-gray-400' enableHorz ? 'bg-gray-50 text-gray-800' : 'bg-gray-200 text-gray-400'
}`} }`}
disabled={!enableHorz} disabled={!enableHorz}

View File

@ -1,4 +1,5 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { create } from 'react-modal-promise'
import { toolTips, useStore } from '../../useStore' import { toolTips, useStore } from '../../useStore'
import { Value, VariableDeclarator } from '../../lang/abstractSyntaxTree' import { Value, VariableDeclarator } from '../../lang/abstractSyntaxTree'
import { import {
@ -8,9 +9,13 @@ import {
import { isSketchVariablesLinked } from '../../lang/std/sketchConstraints' import { isSketchVariablesLinked } from '../../lang/std/sketchConstraints'
import { import {
TransformInfo, TransformInfo,
transformAstForSketchLines, transformSecondarySketchLinesTagFirst,
getTransformInfos, getTransformInfos,
} from '../../lang/std/sketchcombos' } from '../../lang/std/sketchcombos'
import { GetInfoModal } from '../GetInfoModal'
import { createLiteral } from '../../lang/modifyAst'
const getModalInfo = create(GetInfoModal as any)
export const SetHorzDistance = ({ export const SetHorzDistance = ({
horOrVert, horOrVert,
@ -73,23 +78,41 @@ export const SetHorzDistance = ({
return ( return (
<button <button
onClick={() => onClick={async () => {
transformInfos && if (transformInfos && ast) {
ast && const { modifiedAst, tagInfo, valueUsedInTransform } =
updateAst( transformSecondarySketchLinesTagFirst({
transformAstForSketchLines({ ast: JSON.parse(JSON.stringify(ast)),
ast, selectionRanges,
selectionRanges, transformInfos,
transformInfos, programMemory,
programMemory, })
})?.modifiedAst const { segName, value }: { segName: string; value: number } =
) await getModalInfo({
} segName: tagInfo?.tag,
className={`border m-1 px-1 rounded ${ isSegNameEditable: !tagInfo?.isTagExisting,
value: valueUsedInTransform,
} as any)
if (segName === tagInfo?.tag && value === valueUsedInTransform) {
updateAst(modifiedAst)
} else {
// transform again but forcing certain values
const { modifiedAst } = transformSecondarySketchLinesTagFirst({
ast,
selectionRanges,
transformInfos,
programMemory,
forceSegName: segName,
forceValueUsedInTransform: createLiteral(value),
})
updateAst(modifiedAst)
}
}
}}
className={`border m-1 px-1 rounded text-xs ${
enable ? 'bg-gray-50 text-gray-800' : 'bg-gray-200 text-gray-400' enable ? 'bg-gray-50 text-gray-800' : 'bg-gray-200 text-gray-400'
}`} }`}
disabled={!enable} disabled={!enable}
title="yo dawg"
> >
{horOrVert} {horOrVert}
</button> </button>

View File

@ -121,16 +121,16 @@ show(part001)`)
function giveSketchFnCallTagTestHelper( function giveSketchFnCallTagTestHelper(
code: string, code: string,
searchStr: string searchStr: string
): { tag: string; newCode: string } { ): { tag: string; newCode: string; isTagExisting: boolean } {
// giveSketchFnCallTag inputs and outputs an ast, which is very verbose for testing // giveSketchFnCallTag inputs and outputs an ast, which is very verbose for testing
// this wrapper changes the input and output to code // this wrapper changes the input and output to code
// making it more of an integration test, but easier to read the test intention is the goal // making it more of an integration test, but easier to read the test intention is the goal
const ast = abstractSyntaxTree(lexer(code)) const ast = abstractSyntaxTree(lexer(code))
const start = code.indexOf(searchStr) const start = code.indexOf(searchStr)
const range: [number, number] = [start, start + searchStr.length] const range: [number, number] = [start, start + searchStr.length]
const { modifiedAst, tag } = giveSketchFnCallTag(ast, range) const { modifiedAst, tag, isTagExisting } = giveSketchFnCallTag(ast, range)
const newCode = recast(modifiedAst) const newCode = recast(modifiedAst)
return { tag, newCode } return { tag, newCode, isTagExisting }
} }
describe('Testing giveSketchFnCallTag', () => { describe('Testing giveSketchFnCallTag', () => {
@ -140,33 +140,36 @@ describe('Testing giveSketchFnCallTag', () => {
|> line([0.82, 0.34], %) |> line([0.82, 0.34], %)
show(part001)` show(part001)`
it('Should add tag to a sketch function call', () => { it('Should add tag to a sketch function call', () => {
const { newCode, tag } = giveSketchFnCallTagTestHelper( const { newCode, tag, isTagExisting } = giveSketchFnCallTagTestHelper(
code, code,
'line([0, 0.83], %)' 'line([0, 0.83], %)'
) )
expect(newCode).toContain("line({ to: [0, 0.83], tag: 'seg01' }, %)") expect(newCode).toContain("line({ to: [0, 0.83], tag: 'seg01' }, %)")
expect(tag).toBe('seg01') expect(tag).toBe('seg01')
expect(isTagExisting).toBe(false)
}) })
it('Should create a unique tag if seg01 already exists', () => { it('Should create a unique tag if seg01 already exists', () => {
let _code = code.replace( let _code = code.replace(
'line([-2.57, -0.13], %)', 'line([-2.57, -0.13], %)',
"line({ to: [-2.57, -0.13], tag: 'seg01' }, %)" "line({ to: [-2.57, -0.13], tag: 'seg01' }, %)"
) )
const { newCode, tag } = giveSketchFnCallTagTestHelper( const { newCode, tag, isTagExisting } = giveSketchFnCallTagTestHelper(
_code, _code,
'line([0, 0.83], %)' 'line([0, 0.83], %)'
) )
expect(newCode).toContain("line({ to: [0, 0.83], tag: 'seg02' }, %)") expect(newCode).toContain("line({ to: [0, 0.83], tag: 'seg02' }, %)")
expect(tag).toBe('seg02') expect(tag).toBe('seg02')
expect(isTagExisting).toBe(false)
}) })
it('Should return existing tag if it already exists', () => { it('Should return existing tag if it already exists', () => {
const lineButWithTag = "line({ to: [-2.57, -0.13], tag: 'butts' }, %)" const lineButWithTag = "line({ to: [-2.57, -0.13], tag: 'butts' }, %)"
let _code = code.replace('line([-2.57, -0.13], %)', lineButWithTag) let _code = code.replace('line([-2.57, -0.13], %)', lineButWithTag)
const { newCode, tag } = giveSketchFnCallTagTestHelper( const { newCode, tag, isTagExisting } = giveSketchFnCallTagTestHelper(
_code, _code,
lineButWithTag lineButWithTag
) )
expect(newCode).toContain(lineButWithTag) // no change expect(newCode).toContain(lineButWithTag) // no change
expect(tag).toBe('butts') expect(tag).toBe('butts')
expect(isTagExisting).toBe(true)
}) })
}) })

View File

@ -481,15 +481,17 @@ export function createBinaryExpression([left, operator, right]: [
export function giveSketchFnCallTag( export function giveSketchFnCallTag(
ast: Program, ast: Program,
range: Range range: Range,
): { modifiedAst: Program; tag: string } { tag?: string
): { modifiedAst: Program; tag: string; isTagExisting: boolean } {
const { node: primaryCallExp } = getNodeFromPath<CallExpression>( const { node: primaryCallExp } = getNodeFromPath<CallExpression>(
ast, ast,
getNodePathFromSourceRange(ast, range) getNodePathFromSourceRange(ast, range)
) )
const firstArg = getFirstArg(primaryCallExp) const firstArg = getFirstArg(primaryCallExp)
const isTagExisting = !!firstArg.tag
const tagValue = (firstArg.tag || const tagValue = (firstArg.tag ||
createLiteral(findUniqueName(ast, 'seg', 2))) as Literal createLiteral(tag || findUniqueName(ast, 'seg', 2))) as Literal
const tagStr = String(tagValue.value) const tagStr = String(tagValue.value)
const newFirstArg = createFirstArg( const newFirstArg = createFirstArg(
primaryCallExp.callee.name as TooTip, primaryCallExp.callee.name as TooTip,
@ -500,5 +502,6 @@ export function giveSketchFnCallTag(
return { return {
modifiedAst: ast, modifiedAst: ast,
tag: tagStr, tag: tagStr,
isTagExisting,
} }
} }

View File

@ -154,15 +154,19 @@ export const lineTo: SketchLineHelper = {
createLiteral(roundOff(to[1], 2)), createLiteral(roundOff(to[1], 2)),
] ]
const newLine = createCallback const newLine = createCallExpression('lineTo', [
? createCallback(newVals, referencedSegment) createArrayExpression(newVals),
: createCallExpression('lineTo', [ createPipeSubstitution(),
createArrayExpression(newVals), ])
createPipeSubstitution(),
])
const callIndex = getLastIndex(pathToNode) const callIndex = getLastIndex(pathToNode)
if (replaceExisting) { if (replaceExisting && createCallback) {
pipe.body[callIndex] = newLine const boop = createCallback(newVals, referencedSegment)
pipe.body[callIndex] = boop.callExp
return {
modifiedAst: _node,
pathToNode,
valueUsedInTransform: boop.valueUsedInTransform,
}
} else { } else {
pipe.body = [...pipe.body, newLine] pipe.body = [...pipe.body, newLine]
} }
@ -269,7 +273,7 @@ export const line: SketchLineHelper = {
const newYVal = createLiteral(roundOff(to[1] - from[1], 2)) const newYVal = createLiteral(roundOff(to[1] - from[1], 2))
const newLine = createCallback const newLine = createCallback
? createCallback([newXVal, newYVal]) ? createCallback([newXVal, newYVal]).callExp
: createCallExpression('line', [ : createCallExpression('line', [
createArrayExpression([newXVal, newYVal]), createArrayExpression([newXVal, newYVal]),
createPipeSubstitution(), createPipeSubstitution(),
@ -336,7 +340,7 @@ export const xLineTo: SketchLineHelper = {
const newVal = createLiteral(roundOff(to[0], 2)) const newVal = createLiteral(roundOff(to[0], 2))
const newLine = createCallback const newLine = createCallback
? createCallback([newVal, newVal]) ? createCallback([newVal, newVal]).callExp
: createCallExpression('xLineTo', [newVal, createPipeSubstitution()]) : createCallExpression('xLineTo', [newVal, createPipeSubstitution()])
const callIndex = getLastIndex(pathToNode) const callIndex = getLastIndex(pathToNode)
@ -397,7 +401,7 @@ export const yLineTo: SketchLineHelper = {
const newVal = createLiteral(roundOff(to[1], 2)) const newVal = createLiteral(roundOff(to[1], 2))
const newLine = createCallback const newLine = createCallback
? createCallback([newVal, newVal]) ? createCallback([newVal, newVal]).callExp
: createCallExpression('yLineTo', [newVal, createPipeSubstitution()]) : createCallExpression('yLineTo', [newVal, createPipeSubstitution()])
const callIndex = getLastIndex(pathToNode) const callIndex = getLastIndex(pathToNode)
if (replaceExisting) { if (replaceExisting) {
@ -454,7 +458,7 @@ export const xLine: SketchLineHelper = {
const newVal = createLiteral(roundOff(to[0] - from[0], 2)) const newVal = createLiteral(roundOff(to[0] - from[0], 2))
const firstArg = newVal const firstArg = newVal
const newLine = createCallback const newLine = createCallback
? createCallback([firstArg, firstArg]) ? createCallback([firstArg, firstArg]).callExp
: createCallExpression('xLine', [firstArg, createPipeSubstitution()]) : createCallExpression('xLine', [firstArg, createPipeSubstitution()])
const callIndex = getLastIndex(pathToNode) const callIndex = getLastIndex(pathToNode)
if (replaceExisting) { if (replaceExisting) {
@ -507,7 +511,7 @@ export const yLine: SketchLineHelper = {
const { node: pipe } = getNode<PipeExpression>('PipeExpression') const { node: pipe } = getNode<PipeExpression>('PipeExpression')
const newVal = createLiteral(roundOff(to[1] - from[1], 2)) const newVal = createLiteral(roundOff(to[1] - from[1], 2))
const newLine = createCallback const newLine = createCallback
? createCallback([newVal, newVal]) ? createCallback([newVal, newVal]).callExp
: createCallExpression('yLine', [newVal, createPipeSubstitution()]) : createCallExpression('yLine', [newVal, createPipeSubstitution()])
const callIndex = getLastIndex(pathToNode) const callIndex = getLastIndex(pathToNode)
if (replaceExisting) { if (replaceExisting) {
@ -596,7 +600,7 @@ export const angledLine: SketchLineHelper = {
const newAngleVal = createLiteral(roundOff(getAngle(from, to), 0)) const newAngleVal = createLiteral(roundOff(getAngle(from, to), 0))
const newLengthVal = createLiteral(roundOff(getLength(from, to), 2)) const newLengthVal = createLiteral(roundOff(getLength(from, to), 2))
const newLine = createCallback const newLine = createCallback
? createCallback([newAngleVal, newLengthVal]) ? createCallback([newAngleVal, newLengthVal]).callExp
: createCallExpression('angledLine', [ : createCallExpression('angledLine', [
createArrayExpression([newAngleVal, newLengthVal]), createArrayExpression([newAngleVal, newLengthVal]),
createPipeSubstitution(), createPipeSubstitution(),
@ -686,7 +690,7 @@ export const angledLineOfXLength: SketchLineHelper = {
const angle = createLiteral(roundOff(getAngle(from, to), 0)) const angle = createLiteral(roundOff(getAngle(from, to), 0))
const xLength = createLiteral(roundOff(Math.abs(from[0] - to[0]), 2) || 0.1) const xLength = createLiteral(roundOff(Math.abs(from[0] - to[0]), 2) || 0.1)
const newLine = createCallback const newLine = createCallback
? createCallback([angle, xLength]) ? createCallback([angle, xLength]).callExp
: createCallExpression('angledLineOfXLength', [ : createCallExpression('angledLineOfXLength', [
createArrayExpression([angle, xLength]), createArrayExpression([angle, xLength]),
createPipeSubstitution(), createPipeSubstitution(),
@ -780,7 +784,7 @@ export const angledLineOfYLength: SketchLineHelper = {
const angle = createLiteral(roundOff(getAngle(from, to), 0)) const angle = createLiteral(roundOff(getAngle(from, to), 0))
const yLength = createLiteral(roundOff(Math.abs(from[1] - to[1]), 2) || 0.1) const yLength = createLiteral(roundOff(Math.abs(from[1] - to[1]), 2) || 0.1)
const newLine = createCallback const newLine = createCallback
? createCallback([angle, yLength]) ? createCallback([angle, yLength]).callExp
: createCallExpression('angledLineOfYLength', [ : createCallExpression('angledLineOfYLength', [
createArrayExpression([angle, yLength]), createArrayExpression([angle, yLength]),
createPipeSubstitution(), createPipeSubstitution(),
@ -864,7 +868,7 @@ export const angledLineToX: SketchLineHelper = {
const angle = createLiteral(roundOff(getAngle(from, to), 0)) const angle = createLiteral(roundOff(getAngle(from, to), 0))
const xArg = createLiteral(roundOff(to[0], 2)) const xArg = createLiteral(roundOff(to[0], 2))
const newLine = createCallback const newLine = createCallback
? createCallback([angle, xArg]) ? createCallback([angle, xArg]).callExp
: createCallExpression('angledLineToX', [ : createCallExpression('angledLineToX', [
createArrayExpression([angle, xArg]), createArrayExpression([angle, xArg]),
createPipeSubstitution(), createPipeSubstitution(),
@ -945,7 +949,7 @@ export const angledLineToY: SketchLineHelper = {
const angle = createLiteral(roundOff(getAngle(from, to), 0)) const angle = createLiteral(roundOff(getAngle(from, to), 0))
const yArg = createLiteral(roundOff(to[1], 2)) const yArg = createLiteral(roundOff(to[1], 2))
const newLine = createCallback const newLine = createCallback
? createCallback([angle, yArg]) ? createCallback([angle, yArg]).callExp
: createCallExpression('angledLineToY', [ : createCallExpression('angledLineToY', [
createArrayExpression([angle, yArg]), createArrayExpression([angle, yArg]),
createPipeSubstitution(), createPipeSubstitution(),
@ -1088,13 +1092,13 @@ export function replaceSketchLine({
from: [number, number] from: [number, number]
createCallback: TransformCallback createCallback: TransformCallback
referencedSegment?: Path referencedSegment?: Path
}): { modifiedAst: Program } { }): { modifiedAst: Program; valueUsedInTransform?: number } {
if (!toolTips.includes(fnName)) throw new Error('not a tooltip') if (!toolTips.includes(fnName)) throw new Error('not a tooltip')
const _node = { ...node } const _node = { ...node }
const thePath = getNodePathFromSourceRange(_node, sourceRange) const thePath = getNodePathFromSourceRange(_node, sourceRange)
const { add } = sketchLineHelperMap[fnName] const { add } = sketchLineHelperMap[fnName]
const { modifiedAst } = add({ const { modifiedAst, valueUsedInTransform } = add({
node: _node, node: _node,
previousProgramMemory: programMemory, previousProgramMemory: programMemory,
pathToNode: thePath, pathToNode: thePath,
@ -1104,7 +1108,7 @@ export function replaceSketchLine({
replaceExisting: true, replaceExisting: true,
createCallback, createCallback,
}) })
return { modifiedAst } return { modifiedAst, valueUsedInTransform }
} }
export function addTagForSketchOnFace( export function addTagForSketchOnFace(

View File

@ -4,7 +4,7 @@ import { lexer } from '../tokeniser'
import { import {
ConstraintType, ConstraintType,
getTransformInfos, getTransformInfos,
transformAstForHorzVert, transformAstSketchLines,
} from './sketchcombos' } from './sketchcombos'
import { recast } from '../recast' import { recast } from '../recast'
import { initPromise } from '../rust' import { initPromise } from '../rust'
@ -32,11 +32,12 @@ function testingSwapSketchFnCall({
const transformInfos = getTransformInfos([range], ast, constraintType) const transformInfos = getTransformInfos([range], ast, constraintType)
if (!transformInfos) throw new Error('nope') if (!transformInfos) throw new Error('nope')
const { modifiedAst } = transformAstForHorzVert({ const { modifiedAst } = transformAstSketchLines({
ast, ast,
programMemory, programMemory,
selectionRanges: [range], selectionRanges: [range],
transformInfos, transformInfos,
referenceSegName: '',
}) })
return { return {
newCode: recast(modifiedAst), newCode: recast(modifiedAst),

View File

@ -3,8 +3,8 @@ import { lexer } from '../tokeniser'
import { import {
getConstraintType, getConstraintType,
getTransformInfos, getTransformInfos,
transformAstForSketchLines, transformAstSketchLines,
transformAstForHorzVert, transformSecondarySketchLinesTagFirst,
ConstraintType, ConstraintType,
} from './sketchcombos' } from './sketchcombos'
import { initPromise } from '../rust' import { initPromise } from '../rust'
@ -204,7 +204,7 @@ show(part001)`
'equalLength' 'equalLength'
) )
const newAst = transformAstForSketchLines({ const newAst = transformSecondarySketchLinesTagFirst({
ast, ast,
selectionRanges, selectionRanges,
transformInfos, transformInfos,
@ -282,11 +282,12 @@ show(part001)`
const programMemory = executor(ast) const programMemory = executor(ast)
const transformInfos = getTransformInfos(selectionRanges, ast, 'horizontal') const transformInfos = getTransformInfos(selectionRanges, ast, 'horizontal')
const newAst = transformAstForHorzVert({ const newAst = transformAstSketchLines({
ast, ast,
selectionRanges, selectionRanges,
transformInfos, transformInfos,
programMemory, programMemory,
referenceSegName: '',
})?.modifiedAst })?.modifiedAst
const newCode = recast(newAst) const newCode = recast(newAst)
expect(newCode).toBe(expectModifiedScript) expect(newCode).toBe(expectModifiedScript)
@ -331,11 +332,12 @@ show(part001)`
const programMemory = executor(ast) const programMemory = executor(ast)
const transformInfos = getTransformInfos(selectionRanges, ast, 'vertical') const transformInfos = getTransformInfos(selectionRanges, ast, 'vertical')
const newAst = transformAstForHorzVert({ const newAst = transformAstSketchLines({
ast, ast,
selectionRanges, selectionRanges,
transformInfos, transformInfos,
programMemory, programMemory,
referenceSegName: '',
})?.modifiedAst })?.modifiedAst
const newCode = recast(newAst) const newCode = recast(newAst)
expect(newCode).toBe(expectModifiedScript) expect(newCode).toBe(expectModifiedScript)
@ -419,7 +421,7 @@ function helperThing(
constraint constraint
) )
const newAst = transformAstForSketchLines({ const newAst = transformSecondarySketchLinesTagFirst({
ast, ast,
selectionRanges, selectionRanges,
transformInfos, transformInfos,

View File

@ -1,5 +1,5 @@
import { TransformCallback } from './stdTypes' import { TransformCallback } from './stdTypes'
import { Range, Ranges, toolTips, TooTip } from '../../useStore' import { Ranges, toolTips, TooTip } from '../../useStore'
import { import {
BinaryPart, BinaryPart,
CallExpression, CallExpression,
@ -21,7 +21,7 @@ import {
giveSketchFnCallTag, giveSketchFnCallTag,
} from '../modifyAst' } from '../modifyAst'
import { createFirstArg, getFirstArg, replaceSketchLine } from './sketch' import { createFirstArg, getFirstArg, replaceSketchLine } from './sketch'
import { Path, ProgramMemory } from '../executor' import { ProgramMemory } from '../executor'
import { getSketchSegmentIndexFromSourceRange } from './sketchConstraints' import { getSketchSegmentIndexFromSourceRange } from './sketchConstraints'
import { roundOff } from '../../lib/utils' import { roundOff } from '../../lib/utils'
@ -44,46 +44,16 @@ export type ConstraintType =
function createCallWrapper( function createCallWrapper(
a: TooTip, a: TooTip,
val: [Value, Value] | Value, val: [Value, Value] | Value,
tag?: Value tag?: Value,
) { valueUsedInTransform?: number
return createCallExpression(a, [ ): ReturnType<TransformCallback> {
createFirstArg(a, val, tag), return {
createPipeSubstitution(), callExp: createCallExpression(a, [
]) createFirstArg(a, val, tag),
} createPipeSubstitution(),
]),
export function replaceSketchCall( valueUsedInTransform,
programMemory: ProgramMemory, }
ast: Program,
range: Range,
transformTo: TooTip,
createCallback: TransformCallback,
referenceSegName: string
): { modifiedAst: Program } {
const path = getNodePathFromSourceRange(ast, range)
const getNode = getNodeFromPathCurry(ast, path)
const varDec = getNode<VariableDeclarator>('VariableDeclarator').node
const callExp = getNode<CallExpression>('CallExpression').node
const varName = varDec.id.name
const sketchGroup = programMemory.root?.[varName]
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,
createCallback,
})
return { modifiedAst }
} }
export type TransformInfo = { export type TransformInfo = {
@ -93,7 +63,8 @@ export type TransformInfo = {
varValB: Value // y / length or x y for angledLineOfXlength etc varValB: Value // y / length or x y for angledLineOfXlength etc
referenceSegName: string referenceSegName: string
tag?: Value tag?: Value
}) => (args: [Value, Value], referencedSegment?: Path) => Value forceValueUsedInTransform?: Value
}) => TransformCallback
} }
type TransformMap = { type TransformMap = {
@ -172,23 +143,27 @@ const getAngleLengthSign = (arg: Value, legAngleVal: BinaryPart) => {
} }
const setHorzVertDistanceCreateNode = const setHorzVertDistanceCreateNode =
(isX = true): TransformInfo['createNode'] => (
({ referenceSegName, tag }) => { xOrY: 'x' | 'y',
index = xOrY === 'x' ? 0 : 1
): TransformInfo['createNode'] =>
({ referenceSegName, tag, forceValueUsedInTransform }) => {
return (args, referencedSegment) => { return (args, referencedSegment) => {
const makeBinExp = (index: 0 | 1) => { const valueUsedInTransform = roundOff(
const arg = getArgLiteralVal(args?.[index]) getArgLiteralVal(args?.[index]) - (referencedSegment?.to?.[index] || 0),
return createBinaryExpression([ 2
createSegEnd(referenceSegName, isX), )
'+', const makeBinExp = createBinaryExpression([
createLiteral( createSegEnd(referenceSegName, !index),
roundOff(arg - (referencedSegment?.to?.[index] || 0), 2) '+',
), (forceValueUsedInTransform as BinaryPart) ||
]) createLiteral(valueUsedInTransform),
} ])
return createCallWrapper( return createCallWrapper(
'lineTo', 'lineTo',
isX ? [makeBinExp(0), args[1]] : [args[0], makeBinExp(1)], !index ? [makeBinExp, args[1]] : [args[0], makeBinExp],
tag tag,
valueUsedInTransform
) )
} }
} }
@ -301,11 +276,11 @@ const transformMap: TransformMap = {
}, },
setHorzDistance: { setHorzDistance: {
tooltip: 'lineTo', tooltip: 'lineTo',
createNode: setHorzVertDistanceCreateNode(true), createNode: setHorzVertDistanceCreateNode('x'),
}, },
setVertDistance: { setVertDistance: {
tooltip: 'lineTo', tooltip: 'lineTo',
createNode: setHorzVertDistanceCreateNode(false), createNode: setHorzVertDistanceCreateNode('y'),
}, },
}, },
}, },
@ -826,99 +801,120 @@ export function getTransformInfos(
return theTransforms return theTransforms
} }
export function transformAstForSketchLines({ export function transformSecondarySketchLinesTagFirst({
ast, ast,
selectionRanges, selectionRanges,
transformInfos, transformInfos,
programMemory, programMemory,
forceSegName,
forceValueUsedInTransform,
}: { }: {
ast: Program ast: Program
selectionRanges: Ranges selectionRanges: Ranges
transformInfos: TransformInfo[] transformInfos: TransformInfo[]
programMemory: ProgramMemory programMemory: ProgramMemory
}): { modifiedAst: Program } { forceSegName?: string
// deep clone since we are mutating in a loop, of which any could fail forceValueUsedInTransform?: Value
let node = JSON.parse(JSON.stringify(ast)) }): {
modifiedAst: Program
valueUsedInTransform?: number
tagInfo: {
tag: string
isTagExisting: boolean
}
} {
// let node = JSON.parse(JSON.stringify(ast))
const primarySelection = selectionRanges[0] const primarySelection = selectionRanges[0]
const { modifiedAst, tag } = giveSketchFnCallTag(node, primarySelection) const { modifiedAst, tag, isTagExisting } = giveSketchFnCallTag(
node = modifiedAst ast,
primarySelection,
forceSegName
)
selectionRanges.slice(1).forEach((range, index) => { return {
...transformAstSketchLines({
ast: modifiedAst,
selectionRanges: selectionRanges.slice(1),
transformInfos,
programMemory,
referenceSegName: tag,
forceValueUsedInTransform,
}),
tagInfo: {
tag,
isTagExisting,
},
}
}
export function transformAstSketchLines({
ast,
selectionRanges,
transformInfos,
programMemory,
referenceSegName,
forceValueUsedInTransform,
}: {
ast: Program
selectionRanges: Ranges
transformInfos: TransformInfo[]
programMemory: ProgramMemory
referenceSegName: string
forceValueUsedInTransform?: Value
}): { 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) => {
const callBack = transformInfos?.[index].createNode const callBack = transformInfos?.[index].createNode
const transformTo = transformInfos?.[index].tooltip const transformTo = transformInfos?.[index].tooltip
if (!callBack || !transformTo) throw new Error('no callback helper') if (!callBack || !transformTo) throw new Error('no callback helper')
const callExpPath = getNodePathFromSourceRange(node, range) const getNode = getNodeFromPathCurry(
const callExp = getNodeFromPath<CallExpression>(
node, node,
callExpPath, getNodePathFromSourceRange(node, range)
'CallExpression' )
)?.node
const { val } = getFirstArg(callExp) const callExp = getNode<CallExpression>('CallExpression')?.node
const varDec = getNode<VariableDeclarator>('VariableDeclarator').node
const { val, tag: callBackTag } = getFirstArg(callExp)
const [varValA, varValB] = Array.isArray(val) ? val : [val, val] const [varValA, varValB] = Array.isArray(val) ? val : [val, val]
const { modifiedAst } = replaceSketchCall( const varName = varDec.id.name
const sketchGroup = programMemory.root?.[varName]
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, valueUsedInTransform } = replaceSketchLine({
node: node,
programMemory, programMemory,
node, sourceRange: range,
range, referencedSegment,
transformTo, fnName: transformTo || (callExp.callee.name as TooTip),
callBack({ to,
referenceSegName: tag, from,
createCallback: callBack({
referenceSegName,
varValA, varValA,
varValB, varValB,
tag: callBackTag,
forceValueUsedInTransform,
}), }),
tag })
)
node = modifiedAst node = modifiedAst
if (typeof valueUsedInTransform === 'number') {
_valueUsedInTransform = valueUsedInTransform
}
}) })
return { modifiedAst: node } return { modifiedAst: node, valueUsedInTransform: _valueUsedInTransform }
}
export function transformAstForHorzVert({
ast,
selectionRanges,
transformInfos,
programMemory,
}: {
ast: Program
selectionRanges: Ranges
transformInfos: TransformInfo[]
programMemory: ProgramMemory
}): { modifiedAst: Program } {
// deep clone since we are mutating in a loop, of which any could fail
let node = JSON.parse(JSON.stringify(ast))
selectionRanges.forEach((range, index) => {
const callBack = transformInfos?.[index]?.createNode
const transformTo = transformInfos?.[index].tooltip
if (!callBack || !transformTo) throw new Error('no callback helper')
const callExpPath = getNodePathFromSourceRange(node, range)
const callExp = getNodeFromPath<CallExpression>(
node,
callExpPath,
'CallExpression'
)?.node
const { val, tag } = getFirstArg(callExp)
const [varValA, varValB] = Array.isArray(val) ? val : [val, val]
const { modifiedAst } = replaceSketchCall(
programMemory,
node,
range,
transformTo,
callBack({
referenceSegName: '',
varValA,
varValB,
tag,
}),
tag?.type === 'Literal' ? String(tag.value) : ''
)
node = modifiedAst
})
return { modifiedAst: node }
} }
function createSegLen(referenceSegName: string): Value { function createSegLen(referenceSegName: string): Value {

View File

@ -70,7 +70,10 @@ interface updateArgs extends ModifyAstBase {
export type TransformCallback = ( export type TransformCallback = (
args: [Value, Value], args: [Value, Value],
referencedSegment?: Path referencedSegment?: Path
) => Value ) => {
callExp: Value
valueUsedInTransform?: number
}
export type SketchCallTransfromMap = { export type SketchCallTransfromMap = {
[key in TooTip]: TransformCallback [key in TooTip]: TransformCallback
@ -81,6 +84,7 @@ export interface SketchLineHelper {
add: (a: addCall) => { add: (a: addCall) => {
modifiedAst: Program modifiedAst: Program
pathToNode: (string | number)[] pathToNode: (string | number)[]
valueUsedInTransform?: number
} }
updateArgs: (a: updateArgs) => { updateArgs: (a: updateArgs) => {
modifiedAst: Program modifiedAst: Program

View File

@ -1559,6 +1559,13 @@
minimatch "^3.1.2" minimatch "^3.1.2"
strip-json-comments "^3.1.1" strip-json-comments "^3.1.1"
"@headlessui/react@^1.7.13":
version "1.7.13"
resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.7.13.tgz#fd150b394954e9f1d86ed2340cffd1217d6e7628"
integrity sha512-9n+EQKRtD9266xIHXdY5MfiXPDfYwl7zBM7KOx2Ae3Gdgxy8QML1FkCMjq6AsOf0l6N9uvI4HcFtuFlenaldKg==
dependencies:
client-only "^0.0.1"
"@humanwhocodes/config-array@^0.11.6": "@humanwhocodes/config-array@^0.11.6":
version "0.11.7" version "0.11.7"
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.7.tgz#38aec044c6c828f6ed51d5d7ae3d9b9faf6dbb0f" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.7.tgz#38aec044c6c828f6ed51d5d7ae3d9b9faf6dbb0f"
@ -3752,6 +3759,11 @@ clean-css@^5.2.2:
dependencies: dependencies:
source-map "~0.6.0" source-map "~0.6.0"
client-only@^0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1"
integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==
cliui@^7.0.2: cliui@^7.0.2:
version "7.0.4" version "7.0.4"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f"
@ -8708,6 +8720,11 @@ react-merge-refs@^1.1.0:
resolved "https://registry.yarnpkg.com/react-merge-refs/-/react-merge-refs-1.1.0.tgz#73d88b892c6c68cbb7a66e0800faa374f4c38b06" resolved "https://registry.yarnpkg.com/react-merge-refs/-/react-merge-refs-1.1.0.tgz#73d88b892c6c68cbb7a66e0800faa374f4c38b06"
integrity sha512-alTKsjEL0dKH/ru1Iyn7vliS2QRcBp9zZPGoWxUOvRGWPUYgjo+V01is7p04It6KhgrzhJGnIj9GgX8W4bZoCQ== integrity sha512-alTKsjEL0dKH/ru1Iyn7vliS2QRcBp9zZPGoWxUOvRGWPUYgjo+V01is7p04It6KhgrzhJGnIj9GgX8W4bZoCQ==
react-modal-promise@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/react-modal-promise/-/react-modal-promise-1.0.2.tgz#122620b7f19eec73683affadfa77c543d88edc40"
integrity sha512-dqT618ROhG8qh1+O6EZkia5ELw3zaZWGpMX2YfEH4bgwYENPuFonqKw1W70LFx3K/SCZvVBcD6UYEI12yzYXzg==
react-reconciler@^0.27.0: react-reconciler@^0.27.0:
version "0.27.0" version "0.27.0"
resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.27.0.tgz#360124fdf2d76447c7491ee5f0e04503ed9acf5b" resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.27.0.tgz#360124fdf2d76447c7491ee5f0e04503ed9acf5b"