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

View File

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

View File

@ -32,7 +32,7 @@ export const Toolbar = () => {
sketchMode: 'selectFace',
})
}}
className="border m-1 px-1 rounded"
className="border m-1 px-1 rounded text-xs"
>
Start Sketch
</button>
@ -52,7 +52,7 @@ export const Toolbar = () => {
)
updateAst(modifiedAst)
}}
className="border m-1 px-1 rounded"
className="border m-1 px-1 rounded text-xs"
>
SketchOnFace
</button>
@ -68,7 +68,7 @@ export const Toolbar = () => {
position: guiMode.position,
})
}}
className="border m-1 px-1 rounded"
className="border m-1 px-1 rounded text-xs"
>
Edit Sketch
</button>
@ -88,7 +88,7 @@ export const Toolbar = () => {
)
updateAst(modifiedAst, pathToExtrudeArg)
}}
className="border m-1 px-1 rounded"
className="border m-1 px-1 rounded text-xs"
>
ExtrudeSketch
</button>
@ -106,7 +106,7 @@ export const Toolbar = () => {
)
updateAst(modifiedAst, pathToExtrudeArg)
}}
className="border m-1 px-1 rounded"
className="border m-1 px-1 rounded text-xs"
>
ExtrudeSketch (w/o pipe)
</button>
@ -116,7 +116,7 @@ export const Toolbar = () => {
{guiMode.mode === 'sketch' && (
<button
onClick={() => setGuiMode({ mode: 'default' })}
className="border m-1 px-1 rounded"
className="border m-1 px-1 rounded text-xs"
>
Exit sketch
</button>
@ -130,7 +130,7 @@ export const Toolbar = () => {
return (
<button
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'
}`}
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)
useEffect(() => {
if (
typeof window === 'undefined' ||
typeof RTCPeerConnection === 'undefined'
)
return
const url = 'wss://dev.api.kittycad.io/ws/channel'
const [pc, socket] = [new RTCPeerConnection(), new WebSocket(url)]
// Connection opened
@ -41,15 +46,15 @@ export const Stream = () => {
iceServers: message.ice_servers,
})
pc.ontrack = function (event) {
const el = document.createElement(
event.track.kind
) as HTMLVideoElement
console.log('has element?', videoRef.current)
setTimeout(() => {
console.log('has element in timeout?', videoRef.current)
if (videoRef.current) {
videoRef.current.srcObject = event.streams[0]
videoRef.current.autoplay = true
videoRef.current.controls = true
}
})
}
pc.oniceconnectionstatechange = (e) =>
console.log(pc.iceConnectionState)

View File

@ -8,7 +8,7 @@ import {
import { isSketchVariablesLinked } from '../../lang/std/sketchConstraints'
import {
TransformInfo,
transformAstForSketchLines,
transformSecondarySketchLinesTagFirst,
getTransformInfos,
} from '../../lang/std/sketchcombos'
@ -73,7 +73,7 @@ export const Equal = () => {
transformInfos &&
ast &&
updateAst(
transformAstForSketchLines({
transformSecondarySketchLinesTagFirst({
ast,
selectionRanges,
transformInfos,
@ -81,7 +81,7 @@ export const Equal = () => {
})?.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'
}`}
disabled={!enableEqual}

View File

@ -8,7 +8,7 @@ import {
import {
TransformInfo,
getTransformInfos,
transformAstForHorzVert,
transformAstSketchLines,
} from '../../lang/std/sketchcombos'
export const HorzVert = ({
@ -55,15 +55,16 @@ export const HorzVert = ({
transformInfos &&
ast &&
updateAst(
transformAstForHorzVert({
transformAstSketchLines({
ast,
selectionRanges,
transformInfos,
programMemory,
referenceSegName: '',
})?.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'
}`}
disabled={!enableHorz}

View File

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

View File

@ -121,16 +121,16 @@ show(part001)`)
function giveSketchFnCallTagTestHelper(
code: 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
// 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
const ast = abstractSyntaxTree(lexer(code))
const start = code.indexOf(searchStr)
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)
return { tag, newCode }
return { tag, newCode, isTagExisting }
}
describe('Testing giveSketchFnCallTag', () => {
@ -140,33 +140,36 @@ describe('Testing giveSketchFnCallTag', () => {
|> line([0.82, 0.34], %)
show(part001)`
it('Should add tag to a sketch function call', () => {
const { newCode, tag } = giveSketchFnCallTagTestHelper(
const { newCode, tag, isTagExisting } = giveSketchFnCallTagTestHelper(
code,
'line([0, 0.83], %)'
)
expect(newCode).toContain("line({ to: [0, 0.83], tag: 'seg01' }, %)")
expect(tag).toBe('seg01')
expect(isTagExisting).toBe(false)
})
it('Should create a unique tag if seg01 already exists', () => {
let _code = code.replace(
'line([-2.57, -0.13], %)',
"line({ to: [-2.57, -0.13], tag: 'seg01' }, %)"
)
const { newCode, tag } = giveSketchFnCallTagTestHelper(
const { newCode, tag, isTagExisting } = giveSketchFnCallTagTestHelper(
_code,
'line([0, 0.83], %)'
)
expect(newCode).toContain("line({ to: [0, 0.83], tag: 'seg02' }, %)")
expect(tag).toBe('seg02')
expect(isTagExisting).toBe(false)
})
it('Should return existing tag if it already exists', () => {
const lineButWithTag = "line({ to: [-2.57, -0.13], tag: 'butts' }, %)"
let _code = code.replace('line([-2.57, -0.13], %)', lineButWithTag)
const { newCode, tag } = giveSketchFnCallTagTestHelper(
const { newCode, tag, isTagExisting } = giveSketchFnCallTagTestHelper(
_code,
lineButWithTag
)
expect(newCode).toContain(lineButWithTag) // no change
expect(tag).toBe('butts')
expect(isTagExisting).toBe(true)
})
})

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1559,6 +1559,13 @@
minimatch "^3.1.2"
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":
version "0.11.7"
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:
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:
version "7.0.4"
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"
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:
version "0.27.0"
resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.27.0.tgz#360124fdf2d76447c7491ee5f0e04503ed9acf5b"